mirror of
https://github.com/ayangweb/BongoCat.git
synced 2026-03-12 17:51:48 +08:00
feat: 全新升级偏好设置 UI (#89)
This commit is contained in:
@@ -21,6 +21,7 @@ export default antfu({
|
||||
},
|
||||
],
|
||||
'vue/attributes-order': ['error', { alphabetical: true }],
|
||||
'vue/max-attributes-per-line': 'error',
|
||||
},
|
||||
ignores: ['**/*.toml'],
|
||||
})
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"release": "release-it"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@tauri-apps/api": "^2.5.0",
|
||||
"@tauri-apps/plugin-log": "~2.3.1",
|
||||
"@tauri-apps/plugin-opener": "~2.2.6",
|
||||
|
||||
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@@ -13,6 +13,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@ant-design/icons-vue':
|
||||
specifier: ^7.0.1
|
||||
version: 7.0.1(vue@3.5.13(typescript@5.6.3))
|
||||
'@tauri-apps/api':
|
||||
specifier: ^2.5.0
|
||||
version: 2.5.0
|
||||
|
||||
@@ -10,40 +10,30 @@
|
||||
"DisplayInfo": "demomodel2.cdi3.json",
|
||||
"Expressions": [
|
||||
{
|
||||
"Name": "live2d_expression0.exp3.json",
|
||||
"Name": "默认喵",
|
||||
"File": "live2d_expression0.exp3.json"
|
||||
},
|
||||
{
|
||||
"Name": "live2d_expression1.exp3.json",
|
||||
"File": "live2d_expression1.exp3.json"
|
||||
"Name": "社会喵",
|
||||
"File": "live2d_expression1.exp3.json",
|
||||
"Description": "喵喵我叼根小烟,耍个帅气俏皮的wink~超有范儿!但直播里用这招,怕是会让铲屎官和平台瞪大眼,本喵还是低调点,偷偷耍酷好啦!"
|
||||
},
|
||||
{
|
||||
"Name": "live2d_expression2.exp3.json",
|
||||
"Name": "天使喵",
|
||||
"File": "live2d_expression2.exp3.json"
|
||||
}
|
||||
],
|
||||
"Motions": {
|
||||
"CAT_motion": [
|
||||
{
|
||||
"Name": "雷霆喵",
|
||||
"File": "live2d_motion1.motion3.json",
|
||||
"Sound": "live2d_motion1.flac",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
},
|
||||
{
|
||||
"File": "live2d_motion2.motion3.json",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
}
|
||||
],
|
||||
"CAT_motion_lock": [
|
||||
{
|
||||
"File": "live2d_motion1.motion3.json",
|
||||
"Sound": "live2d_motion1.flac",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
},
|
||||
{
|
||||
"Name": "摇摆喵",
|
||||
"File": "live2d_motion2.motion3.json",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
|
||||
@@ -10,34 +10,31 @@
|
||||
"DisplayInfo": "demomodel.cdi3.json",
|
||||
"Expressions": [
|
||||
{
|
||||
"Key": "",
|
||||
"Name": "去掉表情",
|
||||
"Name": "默认喵",
|
||||
"File": "live2d_expression0.exp3.json"
|
||||
},
|
||||
{
|
||||
"Key": "",
|
||||
"Name": "戴上墨镜",
|
||||
"File": "live2d_expression1.exp3.json"
|
||||
"Name": "社会喵",
|
||||
"File": "live2d_expression1.exp3.json",
|
||||
"Description": "喵喵我叼根小烟,耍个帅气俏皮的wink~超有范儿!但直播里用这招,怕是会让铲屎官和平台瞪大眼,本喵还是低调点,偷偷耍酷好啦!"
|
||||
|
||||
},
|
||||
{
|
||||
"Key": "",
|
||||
"Name": "升天",
|
||||
"Name": "天使喵",
|
||||
"File": "live2d_expression2.exp3.json"
|
||||
}
|
||||
],
|
||||
"Motions": {
|
||||
"CAT_motion": [
|
||||
{
|
||||
"Key": "",
|
||||
"Name": "打雷",
|
||||
"Name": "雷霆喵",
|
||||
"File": "live2d_motion1.motion3.json",
|
||||
"Sound": "live2d_motion1.flac",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
},
|
||||
{
|
||||
"Key": "",
|
||||
"Name": "左手摇摆",
|
||||
"Name": "摇摆喵",
|
||||
"File": "live2d_motion2.motion3.json",
|
||||
"FadeInTime": 0,
|
||||
"FadeOutTime": 0
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
"label": "preference",
|
||||
"title": "偏好设置",
|
||||
"url": "index.html/#/preference",
|
||||
"width": 600,
|
||||
"height": 450,
|
||||
"visible": false,
|
||||
"resizable": false,
|
||||
"maximizable": false,
|
||||
|
||||
@@ -22,17 +22,23 @@ const hasDescription = computed(() => {
|
||||
<template>
|
||||
<Flex
|
||||
:align="vertical ? void 0 : 'center'"
|
||||
class="b b-color-2 rounded-lg b-solid p-4"
|
||||
class="b b-color-2 rounded-lg b-solid bg-white p-4"
|
||||
gap="middle"
|
||||
justify="space-between"
|
||||
:vertical="vertical"
|
||||
>
|
||||
<Flex align="center">
|
||||
<slot name="icon">
|
||||
<div class="text-4" :class="icon" />
|
||||
<div
|
||||
class="text-4"
|
||||
:class="icon"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<Flex :class="{ 'ml-4': hasIcon }" vertical>
|
||||
<Flex
|
||||
:class="{ 'ml-4': hasIcon }"
|
||||
vertical
|
||||
>
|
||||
<div class="text-sm font-medium">
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
@@ -7,12 +7,22 @@ const { title } = defineProps<{
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Flex class="mb-4" gap="small" vertical>
|
||||
<div class="text-base font-medium" data-tauri-drag-region>
|
||||
<Flex
|
||||
class="mb-4"
|
||||
gap="small"
|
||||
vertical
|
||||
>
|
||||
<div
|
||||
class="text-base font-medium"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
{{ title }}
|
||||
</div>
|
||||
|
||||
<Flex gap="middle" vertical>
|
||||
<Flex
|
||||
gap="middle"
|
||||
vertical
|
||||
>
|
||||
<slot />
|
||||
</Flex>
|
||||
</FLex>
|
||||
|
||||
@@ -107,12 +107,24 @@ async function handleOk() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model:open="open" cancel-text="稍后更新" centered :closable="false" :mask-closable="false" title="发现新版本🥳" @ok="handleOk">
|
||||
<Modal
|
||||
v-model:open="open"
|
||||
cancel-text="稍后更新"
|
||||
centered
|
||||
:closable="false"
|
||||
:mask-closable="false"
|
||||
title="发现新版本🥳"
|
||||
@ok="handleOk"
|
||||
>
|
||||
<template #okText>
|
||||
{{ loading ? downloadProgress : "立即更新" }}
|
||||
</template>
|
||||
|
||||
<Flex class="pt-1" gap="small" vertical>
|
||||
<Flex
|
||||
class="pt-1"
|
||||
gap="small"
|
||||
vertical
|
||||
>
|
||||
<Flex align="center">
|
||||
<span>更新版本:</span>
|
||||
<span>
|
||||
|
||||
@@ -5,19 +5,28 @@ import { watch } from 'vue'
|
||||
import live2d from '../utils/live2d'
|
||||
import { getCursorMonitor } from '../utils/monitor'
|
||||
|
||||
import { useTauriListen } from './useTauriListen'
|
||||
|
||||
import { LISTEN_KEY } from '@/constants'
|
||||
import { useCatStore } from '@/stores/cat'
|
||||
import { useModelStore } from '@/stores/model'
|
||||
|
||||
export function useModel() {
|
||||
const carStore = useCatStore()
|
||||
const catStore = useCatStore()
|
||||
const modelStore = useModelStore()
|
||||
|
||||
watch(() => carStore.mode, handleLoad)
|
||||
watch(() => catStore.mode, handleLoad)
|
||||
|
||||
useTauriListen<number>(LISTEN_KEY.PLAY_EXPRESSION, ({ payload }) => {
|
||||
live2d.playExpressions(payload)
|
||||
})
|
||||
|
||||
async function handleLoad() {
|
||||
const data = await live2d.load(`/models/${carStore.mode}/cat.model3.json`)
|
||||
const data = await live2d.load(`/models/${catStore.mode}/cat.model3.json`)
|
||||
|
||||
handleResize()
|
||||
|
||||
Object.assign(carStore, data)
|
||||
Object.assign(modelStore, data)
|
||||
}
|
||||
|
||||
function handleDestroy() {
|
||||
@@ -49,7 +58,7 @@ export function useModel() {
|
||||
}
|
||||
|
||||
async function handleMouseMove() {
|
||||
if (carStore.mode !== 'standard' || !live2d.model) return
|
||||
if (catStore.mode !== 'standard' || !live2d.model) return
|
||||
|
||||
const monitor = await getCursorMonitor()
|
||||
|
||||
|
||||
@@ -5,4 +5,5 @@ export const LISTEN_KEY = {
|
||||
HIDE_WINDOW: 'hide-window',
|
||||
DEVICE_CHANGED: 'device-changed',
|
||||
UPDATE_APP: 'update-app',
|
||||
PLAY_EXPRESSION: 'play-expression',
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { Flex } from 'ant-design-vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
import UpdateApp from '@/components/update-app/index.vue'
|
||||
import { useTray } from '@/composables/useTray'
|
||||
import { preferenceRoutes } from '@/router'
|
||||
import { isMac } from '@/utils/platform'
|
||||
|
||||
const { createTray } = useTray()
|
||||
|
||||
onMounted(async () => {
|
||||
createTray()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Flex class="h-screen">
|
||||
<div class="h-full w-40 bg-color-8" :class="[isMac ? 'pt-8' : 'pt-4']" data-tauri-drag-region>
|
||||
<Flex class="px-2" gap="small" vertical>
|
||||
<RouterLink v-for="item in preferenceRoutes" :key="item.path" active-class="bg-primary! text-white! font-bold" class="h-10 flex items-center gap-2 rounded-lg hover:bg-color-6 px-4 text-color-1! transition" :to="item.path" @click.stop>
|
||||
<div class="size-5" :class="item.meta?.icon" />
|
||||
|
||||
<span>{{ item.meta?.title }}</span>
|
||||
</RouterLink>
|
||||
</Flex>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-4" data-tauri-drag-region>
|
||||
<RouterView />
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<UpdateApp />
|
||||
</template>
|
||||
@@ -38,7 +38,7 @@ watch(pressedKeys, handleKeyDown)
|
||||
|
||||
watch(() => catStore.penetrable, (value) => {
|
||||
appWindow.setIgnoreCursorEvents(value)
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
function handleWindowDrag() {
|
||||
appWindow.startDragging()
|
||||
@@ -60,9 +60,16 @@ function resolveImageURL(key: string) {
|
||||
|
||||
<canvas id="live2dCanvas" />
|
||||
|
||||
<img v-for="key in pressedKeys" :key="key" :src="resolveImageURL(key)">
|
||||
<img
|
||||
v-for="key in pressedKeys"
|
||||
:key="key"
|
||||
:src="resolveImageURL(key)"
|
||||
>
|
||||
|
||||
<div v-show="resizing" class="flex items-center justify-center bg-black">
|
||||
<div
|
||||
v-show="resizing"
|
||||
class="flex items-center justify-center bg-black"
|
||||
>
|
||||
<span class="text-center text-5xl text-white">
|
||||
重绘中...
|
||||
</span>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
敬请期待...
|
||||
</template>
|
||||
@@ -21,18 +21,32 @@ function feedbackIssue() {
|
||||
|
||||
<template>
|
||||
<ProList title="关于软件">
|
||||
<ProListItem :description="`版本:v${appStore.version}`" :title="appStore.name">
|
||||
<Button type="primary" @click="handleUpdate">
|
||||
<ProListItem
|
||||
:description="`版本:v${appStore.version}`"
|
||||
:title="appStore.name"
|
||||
>
|
||||
<Button
|
||||
type="primary"
|
||||
@click="handleUpdate"
|
||||
>
|
||||
检查更新
|
||||
</Button>
|
||||
|
||||
<template #icon>
|
||||
<img class="size-12 drop-shadow" src="/images/logo.png">
|
||||
<div class="b b-color-2 rounded-xl b-solid">
|
||||
<img
|
||||
class="size-12"
|
||||
src="/images/logo.png"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</ProListItem>
|
||||
|
||||
<ProListItem title="开源地址">
|
||||
<Button danger @click="feedbackIssue">
|
||||
<Button
|
||||
danger
|
||||
@click="feedbackIssue"
|
||||
>
|
||||
反馈问题
|
||||
</Button>
|
||||
|
||||
@@ -24,17 +24,30 @@ const modeList: SelectProps['options'] = [
|
||||
<template>
|
||||
<ProList title="模式设置">
|
||||
<ProListItem title="选择模式">
|
||||
<Select v-model:value="catStore.mode" :options="modeList" title="选择模式" />
|
||||
<Select
|
||||
v-model:value="catStore.mode"
|
||||
:options="modeList"
|
||||
title="选择模式"
|
||||
/>
|
||||
</ProListItem>
|
||||
</ProList>
|
||||
|
||||
<ProList title="窗口设置">
|
||||
<ProListItem description="启用后,窗口不影响对其他应用程序的操作" title="窗口穿透">
|
||||
<ProListItem
|
||||
description="启用后,窗口不影响对其他应用程序的操作"
|
||||
title="窗口穿透"
|
||||
>
|
||||
<Switch v-model:checked="catStore.penetrable" />
|
||||
</ProListItem>
|
||||
|
||||
<ProListItem title="透明度" vertical>
|
||||
<Slider v-model:value="catStore.opacity" class="m-0!" />
|
||||
<ProListItem
|
||||
title="透明度"
|
||||
vertical
|
||||
>
|
||||
<Slider
|
||||
v-model:value="catStore.opacity"
|
||||
class="m-0!"
|
||||
/>
|
||||
</ProListItem>
|
||||
|
||||
<ProListItem title="镜像模式">
|
||||
7
src/pages/preference/components/model/index.vue
Normal file
7
src/pages/preference/components/model/index.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
敬请期待
|
||||
</div>
|
||||
</template>
|
||||
96
src/pages/preference/index.vue
Normal file
96
src/pages/preference/index.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
import { Flex } from 'ant-design-vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
|
||||
import About from './components/about/index.vue'
|
||||
import Cat from './components/cat/index.vue'
|
||||
import General from './components/general/index.vue'
|
||||
import Model from './components/model/index.vue'
|
||||
|
||||
import UpdateApp from '@/components/update-app/index.vue'
|
||||
import { useTray } from '@/composables/useTray'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { isMac } from '@/utils/platform'
|
||||
|
||||
const { createTray } = useTray()
|
||||
const appStore = useAppStore()
|
||||
const current = ref(0)
|
||||
|
||||
onMounted(async () => {
|
||||
createTray()
|
||||
})
|
||||
|
||||
const menus = [
|
||||
{
|
||||
label: '猫咪设置',
|
||||
icon: 'i-solar:cat-bold',
|
||||
component: Cat,
|
||||
},
|
||||
{
|
||||
label: '通用设置',
|
||||
icon: 'i-solar:settings-minimalistic-bold',
|
||||
component: General,
|
||||
},
|
||||
{
|
||||
label: '模型管理',
|
||||
icon: 'i-solar:magic-stick-3-bold',
|
||||
component: Model,
|
||||
},
|
||||
{
|
||||
label: '关于',
|
||||
icon: 'i-solar:info-circle-bold',
|
||||
component: About,
|
||||
},
|
||||
]
|
||||
|
||||
const currentComponent = computed(() => {
|
||||
return menus[current.value].component
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Flex class="h-screen">
|
||||
<div
|
||||
class="h-full w-30 flex flex-col items-center gap-4 bg-gradient-from-primary-1 bg-gradient-to-black/1 bg-gradient-linear"
|
||||
:class="[isMac ? 'pt-8' : 'pt-4']"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="b b-color-2 rounded-2xl b-solid">
|
||||
<img
|
||||
class="size-15"
|
||||
src="/images/logo.png"
|
||||
>
|
||||
</div>
|
||||
|
||||
<span class="font-bold">{{ appStore.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div
|
||||
v-for="(item, index) in menus"
|
||||
:key="item.label"
|
||||
class="size-20 flex flex-col cursor-pointer items-center justify-center gap-2 rounded-lg hover:bg-color-7 text-color-3 transition"
|
||||
:class="{ 'bg-white! text-primary-5 font-bold': current === index }"
|
||||
@mousedown="current = index"
|
||||
>
|
||||
<div
|
||||
class="size-8"
|
||||
:class="item.icon"
|
||||
/>
|
||||
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex-1 bg-color-8 p-4"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<component :is="currentComponent" />
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<UpdateApp />
|
||||
</template>
|
||||
@@ -1,46 +1,9 @@
|
||||
// @unocss-include
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import Preference from '../layouts/preference/index.vue'
|
||||
import General from '../pages/general/index.vue'
|
||||
import Main from '../pages/main/index.vue'
|
||||
|
||||
export const preferenceRoutes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: 'cat',
|
||||
component: () => import('../pages/cat/index.vue'),
|
||||
meta: {
|
||||
title: '猫咪设置',
|
||||
icon: 'i-solar:cat-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
component: General,
|
||||
meta: {
|
||||
title: '通用设置',
|
||||
icon: 'i-solar:settings-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'model',
|
||||
component: () => import('../pages/model/index.vue'),
|
||||
meta: {
|
||||
title: '模型管理',
|
||||
icon: 'i-solar:magic-stick-3-outline',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: () => import('../pages/about/index.vue'),
|
||||
meta: {
|
||||
title: '关于',
|
||||
icon: 'i-solar:info-circle-outline',
|
||||
},
|
||||
},
|
||||
]
|
||||
import Preference from '../pages/preference/index.vue'
|
||||
|
||||
const routes: Readonly<RouteRecordRaw[]> = [
|
||||
{
|
||||
@@ -50,8 +13,6 @@ const routes: Readonly<RouteRecordRaw[]> = [
|
||||
{
|
||||
path: '/preference',
|
||||
component: Preference,
|
||||
redirect: '/preference/cat',
|
||||
children: preferenceRoutes,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -2,22 +2,24 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface Motion {
|
||||
Key: string
|
||||
Name: string
|
||||
File: string
|
||||
Sound?: string
|
||||
FadeInTime: number
|
||||
FadeOutTime: number
|
||||
Description?: string
|
||||
}
|
||||
|
||||
type MotionGroup = Record<string, Motion[]>
|
||||
|
||||
interface Expression {
|
||||
Key: string
|
||||
Name: string
|
||||
File: string
|
||||
Description?: string
|
||||
}
|
||||
|
||||
export const useModelStore = defineStore('model', () => {
|
||||
const motions = ref<Motion[]>([])
|
||||
const motions = ref<MotionGroup>({})
|
||||
const expressions = ref<Expression[]>([])
|
||||
|
||||
return {
|
||||
|
||||
@@ -39,7 +39,7 @@ class Live2d {
|
||||
this.model = model
|
||||
|
||||
return {
|
||||
motions: Object.values(definitions).flat(),
|
||||
motions: definitions,
|
||||
expressions: expressionManager?.definitions ?? [],
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,14 @@ class Live2d {
|
||||
this.model?.destroy()
|
||||
}
|
||||
|
||||
public playMotion(group: string, index: number) {
|
||||
return this.model?.motion(group, index)
|
||||
}
|
||||
|
||||
public playExpressions(index: number) {
|
||||
return this.model?.expression(index)
|
||||
}
|
||||
|
||||
public setParameterValue(id: string, value: number | boolean) {
|
||||
return this.model?.internalModel.coreModel.setParameterValueById(id, Number(value))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user