feat: add settings schema
This commit is contained in:
@@ -6,6 +6,182 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
export class AppSettingDefinition {
|
||||
/**
|
||||
* Creates a new AppSettingDefinition instance.
|
||||
* @param {Partial<AppSettingDefinition>} [$$source = {}] - The source object to create the AppSettingDefinition.
|
||||
*/
|
||||
constructor($$source = {}) {
|
||||
if (!("key" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["key"] = "";
|
||||
}
|
||||
if (!("category" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["category"] = "";
|
||||
}
|
||||
if (!("label" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["label"] = "";
|
||||
}
|
||||
if (!("type" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["type"] = "";
|
||||
}
|
||||
if (!("defaultValue" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["defaultValue"] = "";
|
||||
}
|
||||
if (!("value" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["value"] = "";
|
||||
}
|
||||
if (!("description" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["description"] = "";
|
||||
}
|
||||
if (!("options" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {AppSettingOption[]}
|
||||
*/
|
||||
this["options"] = [];
|
||||
}
|
||||
if (!("readOnly" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {boolean}
|
||||
*/
|
||||
this["readOnly"] = false;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AppSettingDefinition instance from a string or object.
|
||||
* @param {any} [$$source = {}]
|
||||
* @returns {AppSettingDefinition}
|
||||
*/
|
||||
static createFrom($$source = {}) {
|
||||
const $$createField7_0 = $$createType1;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("options" in $$parsedSource) {
|
||||
$$parsedSource["options"] = $$createField7_0($$parsedSource["options"]);
|
||||
}
|
||||
return new AppSettingDefinition(/** @type {Partial<AppSettingDefinition>} */($$parsedSource));
|
||||
}
|
||||
}
|
||||
|
||||
export class AppSettingGroup {
|
||||
/**
|
||||
* Creates a new AppSettingGroup instance.
|
||||
* @param {Partial<AppSettingGroup>} [$$source = {}] - The source object to create the AppSettingGroup.
|
||||
*/
|
||||
constructor($$source = {}) {
|
||||
if (!("category" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["category"] = "";
|
||||
}
|
||||
if (!("label" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["label"] = "";
|
||||
}
|
||||
if (!("description" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["description"] = "";
|
||||
}
|
||||
if (!("items" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {AppSettingDefinition[]}
|
||||
*/
|
||||
this["items"] = [];
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AppSettingGroup instance from a string or object.
|
||||
* @param {any} [$$source = {}]
|
||||
* @returns {AppSettingGroup}
|
||||
*/
|
||||
static createFrom($$source = {}) {
|
||||
const $$createField3_0 = $$createType3;
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
if ("items" in $$parsedSource) {
|
||||
$$parsedSource["items"] = $$createField3_0($$parsedSource["items"]);
|
||||
}
|
||||
return new AppSettingGroup(/** @type {Partial<AppSettingGroup>} */($$parsedSource));
|
||||
}
|
||||
}
|
||||
|
||||
export class AppSettingOption {
|
||||
/**
|
||||
* Creates a new AppSettingOption instance.
|
||||
* @param {Partial<AppSettingOption>} [$$source = {}] - The source object to create the AppSettingOption.
|
||||
*/
|
||||
constructor($$source = {}) {
|
||||
if (!("value" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["value"] = "";
|
||||
}
|
||||
if (!("label" in $$source)) {
|
||||
/**
|
||||
* @member
|
||||
* @type {string}
|
||||
*/
|
||||
this["label"] = "";
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new AppSettingOption instance from a string or object.
|
||||
* @param {any} [$$source = {}]
|
||||
* @returns {AppSettingOption}
|
||||
*/
|
||||
static createFrom($$source = {}) {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new AppSettingOption(/** @type {Partial<AppSettingOption>} */($$parsedSource));
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthProviderOption {
|
||||
/**
|
||||
* Creates a new AuthProviderOption instance.
|
||||
@@ -176,3 +352,9 @@ export class Hotkey {
|
||||
return new Hotkey(/** @type {Partial<Hotkey>} */($$parsedSource));
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = AppSettingOption.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
const $$createType2 = AppSettingDefinition.createFrom;
|
||||
const $$createType3 = $Create.Array($$createType2);
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// @ts-ignore: Unused imports
|
||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore: Unused imports
|
||||
import * as models$0 from "../models/models.js";
|
||||
|
||||
/**
|
||||
* 根据key获取配置项
|
||||
* @param {string} key
|
||||
@@ -25,6 +29,18 @@ export function GetLanguage() {
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<models$0.AppSettingGroup[]> & { cancel(): void }}
|
||||
*/
|
||||
export function GetSettingsSchema() {
|
||||
let $resultPromise = /** @type {any} */($Call.ByID(2109450092));
|
||||
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
|
||||
return $$createType1($result);
|
||||
}));
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置或更新配置项
|
||||
* @param {string} key
|
||||
@@ -45,3 +61,7 @@ export function SetLanguage(lang) {
|
||||
let $resultPromise = /** @type {any} */($Call.ByID(538150432, lang));
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = models$0.AppSettingGroup.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
|
||||
@@ -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