feat: add settings schema
This commit is contained in:
@@ -1,18 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { setTheme, getCurrentTheme, ThemeMode } from '../../utils/ThemeManager'
|
||||
import { GetAppConfig, SetAppConfig } from '../../../bindings/cmbone/internal/services/appconfigservice'
|
||||
import { setTheme, getCurrentTheme, type ThemeMode } from '../../utils/ThemeManager'
|
||||
|
||||
const themevalue = ref<ThemeMode>('light')
|
||||
|
||||
const themeChange = (val: ThemeMode) => {
|
||||
const themeChange = async (val: ThemeMode) => {
|
||||
setTheme(val)
|
||||
await SetAppConfig('theme.mode', val)
|
||||
// 通知子窗口也可以加上 BroadcastChannel
|
||||
const bc = new BroadcastChannel('theme')
|
||||
bc.postMessage(val)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
themevalue.value = getCurrentTheme()
|
||||
onMounted(async () => {
|
||||
const savedTheme = await GetAppConfig('theme.mode')
|
||||
themevalue.value = (savedTheme || getCurrentTheme()) as ThemeMode
|
||||
setTheme(themevalue.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -25,6 +29,7 @@ onMounted(() => {
|
||||
>
|
||||
<a-select-option value="light">{{$t('components.themesetting.select.opt_light')}}</a-select-option>
|
||||
<a-select-option value="dark">{{$t('components.themesetting.select.opt_dark')}}</a-select-option>
|
||||
<a-select-option value="systemdefault">{{$t('components.themesetting.select.opt_system')}}</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"themesetting": {
|
||||
"select": {
|
||||
"opt_dark": "Dark",
|
||||
"opt_light": "Bright"
|
||||
"opt_light": "Bright",
|
||||
"opt_system": "System"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
"themesetting": {
|
||||
"select": {
|
||||
"opt_dark": "暗色模式",
|
||||
"opt_light": "亮色模式"
|
||||
"opt_light": "亮色模式",
|
||||
"opt_system": "跟隨系統"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
|
||||
@@ -52,7 +52,8 @@
|
||||
"themesetting":{
|
||||
"select":{
|
||||
"opt_light":"亮色模式",
|
||||
"opt_dark":"暗色模式"
|
||||
"opt_dark":"暗色模式",
|
||||
"opt_system":"跟随系统"
|
||||
}
|
||||
},
|
||||
"about": {
|
||||
@@ -96,4 +97,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,22 @@ import App from './App.vue'
|
||||
import './style.css'
|
||||
import 'ant-design-vue/dist/reset.css';
|
||||
import {i18n,setupI18n } from './utils/i18n'
|
||||
import { initTheme } from './utils/ThemeManager'//add
|
||||
import { initTheme, setTheme, type ThemeMode } from './utils/ThemeManager'//add
|
||||
import router from './router/index'
|
||||
import { GetLanguage } from '../bindings/cmbone/internal/services/appconfigservice'
|
||||
import { GetAppConfig, GetLanguage } from '../bindings/cmbone/internal/services/appconfigservice'
|
||||
|
||||
initTheme()
|
||||
async function bootstrap(){
|
||||
await setupI18n('zh')//默认语言或从后端读取
|
||||
createApp(App).use(Antd).use(router).use(i18n).mount('#app')
|
||||
router.isReady().then(async () => {
|
||||
const lang = await GetLanguage() //从后端读取语言设置 lang as any|| 'zh'
|
||||
const [lang, themeMode] = await Promise.all([
|
||||
GetLanguage(),
|
||||
GetAppConfig('theme.mode'),
|
||||
])
|
||||
if (themeMode) {
|
||||
setTheme(themeMode as ThemeMode)
|
||||
}
|
||||
await setupI18n(lang as any|| 'zh')
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,71 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import ModulePageShell from '../../components/template/ModulePageShell.vue'
|
||||
import GeneralSettings from '../../components/General/Index.vue'
|
||||
import { GetSettingsSchema } from '../../../bindings/cmbone/internal/services/appconfigservice'
|
||||
|
||||
const summary = [
|
||||
{ label: '主题', value: '可配置', tone: 'slate' as const },
|
||||
{ label: '语言', value: '可配置', tone: 'blue' as const },
|
||||
{ label: '快捷键', value: '已接入', tone: 'green' as const },
|
||||
{ label: '窗口行为', value: '待 schema', tone: 'amber' as const },
|
||||
]
|
||||
type SettingOption = {
|
||||
value: string
|
||||
label: string
|
||||
}
|
||||
|
||||
type SettingItem = {
|
||||
key: string
|
||||
category: string
|
||||
label: string
|
||||
type: string
|
||||
defaultValue: string
|
||||
value: string
|
||||
description: string
|
||||
options: SettingOption[]
|
||||
readOnly: boolean
|
||||
}
|
||||
|
||||
type SettingGroup = {
|
||||
category: string
|
||||
label: string
|
||||
description: string
|
||||
items: SettingItem[]
|
||||
}
|
||||
|
||||
const groups = ref<SettingGroup[]>([])
|
||||
const isLoading = ref(true)
|
||||
const loadError = ref('')
|
||||
|
||||
const summary = computed(() => [
|
||||
{ label: 'Schema 分组', value: String(groups.value.length), tone: 'slate' as const },
|
||||
{ label: '配置项', value: String(groups.value.reduce((total, group) => total + group.items.length, 0)), tone: 'blue' as const },
|
||||
{ label: '快捷键', value: findSettingValue('shortcut.open_second_window') || '已接入', tone: 'green' as const },
|
||||
{ label: '窗口行为', value: findSettingValue('window.start_state') || 'normal', tone: 'amber' as const },
|
||||
])
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
groups.value = await GetSettingsSchema()
|
||||
} catch (error) {
|
||||
loadError.value = error instanceof Error ? error.message : String(error)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function findSettingValue(key: string) {
|
||||
for (const group of groups.value) {
|
||||
const item = group.items.find((setting) => setting.key === key)
|
||||
if (item) {
|
||||
return item.value
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function displayValue(item: SettingItem) {
|
||||
if (!item.value) {
|
||||
return item.defaultValue || '-'
|
||||
}
|
||||
const option = item.options.find((candidate) => candidate.value === item.value)
|
||||
return option?.label || item.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -21,5 +79,71 @@ const summary = [
|
||||
<section class="rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-800 dark:bg-slate-900">
|
||||
<GeneralSettings />
|
||||
</section>
|
||||
|
||||
<section class="rounded-lg border border-slate-200 bg-white dark:border-slate-800 dark:bg-slate-900">
|
||||
<div class="border-b border-slate-200 p-4 dark:border-slate-800">
|
||||
<h2 class="mb-1 text-base font-semibold text-slate-950 dark:text-white">
|
||||
统一设置 Schema
|
||||
</h2>
|
||||
<p class="mb-0 text-sm text-slate-500 dark:text-slate-400">
|
||||
后端初始化默认值,前端和后续服务按 key 使用同一份定义。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="p-4 text-sm text-slate-500 dark:text-slate-400">
|
||||
加载中...
|
||||
</div>
|
||||
<div v-else-if="loadError" class="p-4 text-sm text-rose-600 dark:text-rose-300">
|
||||
{{ loadError }}
|
||||
</div>
|
||||
<div v-else class="divide-y divide-slate-200 dark:divide-slate-800">
|
||||
<div v-for="group in groups" :key="group.category" class="p-4">
|
||||
<div class="mb-3 flex flex-col gap-1 md:flex-row md:items-end md:justify-between">
|
||||
<div>
|
||||
<h3 class="mb-1 text-sm font-semibold text-slate-900 dark:text-white">
|
||||
{{ group.label }}
|
||||
</h3>
|
||||
<p class="mb-0 text-xs text-slate-500 dark:text-slate-400">
|
||||
{{ group.description }}
|
||||
</p>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">{{ group.category }}</span>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full text-left text-sm">
|
||||
<thead class="text-xs uppercase text-slate-500 dark:text-slate-400">
|
||||
<tr>
|
||||
<th class="w-[220px] py-2 pr-3 font-medium">Key</th>
|
||||
<th class="w-[160px] py-2 pr-3 font-medium">当前值</th>
|
||||
<th class="w-[160px] py-2 pr-3 font-medium">默认值</th>
|
||||
<th class="py-2 pr-3 font-medium">说明</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-100 dark:divide-slate-800">
|
||||
<tr v-for="item in group.items" :key="item.key">
|
||||
<td class="py-2 pr-3">
|
||||
<div class="font-medium text-slate-900 dark:text-white">{{ item.label }}</div>
|
||||
<div class="text-xs text-slate-500 dark:text-slate-400">{{ item.key }}</div>
|
||||
</td>
|
||||
<td class="py-2 pr-3 text-slate-700 dark:text-slate-200">
|
||||
{{ displayValue(item) }}
|
||||
</td>
|
||||
<td class="py-2 pr-3 text-slate-500 dark:text-slate-400">
|
||||
{{ item.defaultValue || '-' }}
|
||||
</td>
|
||||
<td class="py-2 pr-3 text-slate-500 dark:text-slate-400">
|
||||
<span>{{ item.description }}</span>
|
||||
<span v-if="item.readOnly" class="ml-2 rounded-md bg-slate-100 px-2 py-0.5 text-xs text-slate-600 dark:bg-slate-800 dark:text-slate-300">
|
||||
只读
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</ModulePageShell>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user