diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..859cee34
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,18 @@
+# Ignore frontend directory as it has its own prettier config and plugins
+web/admin-spa/
+
+# Ignore node_modules
+node_modules/
+
+# Ignore build outputs
+dist/
+build/
+coverage/
+
+# Ignore logs
+logs/
+*.log
+
+# Ignore temporary files
+*.tmp
+*.temp
\ No newline at end of file
diff --git a/VERSION b/VERSION
index 88cf65eb..93599c69 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.1.137
+1.1.138
diff --git a/web/admin-spa/.eslintrc.cjs b/web/admin-spa/.eslintrc.cjs
index 2a625058..59ceab17 100644
--- a/web/admin-spa/.eslintrc.cjs
+++ b/web/admin-spa/.eslintrc.cjs
@@ -18,7 +18,8 @@ module.exports = {
rules: {
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'off',
- 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ // 允许在所有环境中使用 console 语句以避免构建警告
+ 'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'prettier/prettier': 'error',
'vue/attributes-order': [
diff --git a/web/admin-spa/auto-imports.d.ts b/web/admin-spa/auto-imports.d.ts
new file mode 100644
index 00000000..9954071d
--- /dev/null
+++ b/web/admin-spa/auto-imports.d.ts
@@ -0,0 +1,102 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// noinspection JSUnusedGlobalSymbols
+// Generated by unplugin-auto-import
+export {}
+declare global {
+ const EffectScope: (typeof import('vue'))['EffectScope']
+ const acceptHMRUpdate: (typeof import('pinia'))['acceptHMRUpdate']
+ const computed: (typeof import('vue'))['computed']
+ const createApp: (typeof import('vue'))['createApp']
+ const createPinia: (typeof import('pinia'))['createPinia']
+ const customRef: (typeof import('vue'))['customRef']
+ const defineAsyncComponent: (typeof import('vue'))['defineAsyncComponent']
+ const defineComponent: (typeof import('vue'))['defineComponent']
+ const defineStore: (typeof import('pinia'))['defineStore']
+ const effectScope: (typeof import('vue'))['effectScope']
+ const getActivePinia: (typeof import('pinia'))['getActivePinia']
+ const getCurrentInstance: (typeof import('vue'))['getCurrentInstance']
+ const getCurrentScope: (typeof import('vue'))['getCurrentScope']
+ const h: (typeof import('vue'))['h']
+ const inject: (typeof import('vue'))['inject']
+ const isProxy: (typeof import('vue'))['isProxy']
+ const isReactive: (typeof import('vue'))['isReactive']
+ const isReadonly: (typeof import('vue'))['isReadonly']
+ const isRef: (typeof import('vue'))['isRef']
+ const mapActions: (typeof import('pinia'))['mapActions']
+ const mapGetters: (typeof import('pinia'))['mapGetters']
+ const mapState: (typeof import('pinia'))['mapState']
+ const mapStores: (typeof import('pinia'))['mapStores']
+ const mapWritableState: (typeof import('pinia'))['mapWritableState']
+ const markRaw: (typeof import('vue'))['markRaw']
+ const nextTick: (typeof import('vue'))['nextTick']
+ const onActivated: (typeof import('vue'))['onActivated']
+ const onBeforeMount: (typeof import('vue'))['onBeforeMount']
+ const onBeforeRouteLeave: (typeof import('vue-router'))['onBeforeRouteLeave']
+ const onBeforeRouteUpdate: (typeof import('vue-router'))['onBeforeRouteUpdate']
+ const onBeforeUnmount: (typeof import('vue'))['onBeforeUnmount']
+ const onBeforeUpdate: (typeof import('vue'))['onBeforeUpdate']
+ const onDeactivated: (typeof import('vue'))['onDeactivated']
+ const onErrorCaptured: (typeof import('vue'))['onErrorCaptured']
+ const onMounted: (typeof import('vue'))['onMounted']
+ const onRenderTracked: (typeof import('vue'))['onRenderTracked']
+ const onRenderTriggered: (typeof import('vue'))['onRenderTriggered']
+ const onScopeDispose: (typeof import('vue'))['onScopeDispose']
+ const onServerPrefetch: (typeof import('vue'))['onServerPrefetch']
+ const onUnmounted: (typeof import('vue'))['onUnmounted']
+ const onUpdated: (typeof import('vue'))['onUpdated']
+ const onWatcherCleanup: (typeof import('vue'))['onWatcherCleanup']
+ const provide: (typeof import('vue'))['provide']
+ const reactive: (typeof import('vue'))['reactive']
+ const readonly: (typeof import('vue'))['readonly']
+ const ref: (typeof import('vue'))['ref']
+ const resolveComponent: (typeof import('vue'))['resolveComponent']
+ const setActivePinia: (typeof import('pinia'))['setActivePinia']
+ const setMapStoreSuffix: (typeof import('pinia'))['setMapStoreSuffix']
+ const shallowReactive: (typeof import('vue'))['shallowReactive']
+ const shallowReadonly: (typeof import('vue'))['shallowReadonly']
+ const shallowRef: (typeof import('vue'))['shallowRef']
+ const storeToRefs: (typeof import('pinia'))['storeToRefs']
+ const toRaw: (typeof import('vue'))['toRaw']
+ const toRef: (typeof import('vue'))['toRef']
+ const toRefs: (typeof import('vue'))['toRefs']
+ const toValue: (typeof import('vue'))['toValue']
+ const triggerRef: (typeof import('vue'))['triggerRef']
+ const unref: (typeof import('vue'))['unref']
+ const useAttrs: (typeof import('vue'))['useAttrs']
+ const useCssModule: (typeof import('vue'))['useCssModule']
+ const useCssVars: (typeof import('vue'))['useCssVars']
+ const useId: (typeof import('vue'))['useId']
+ const useLink: (typeof import('vue-router'))['useLink']
+ const useModel: (typeof import('vue'))['useModel']
+ const useRoute: (typeof import('vue-router'))['useRoute']
+ const useRouter: (typeof import('vue-router'))['useRouter']
+ const useSlots: (typeof import('vue'))['useSlots']
+ const useTemplateRef: (typeof import('vue'))['useTemplateRef']
+ const watch: (typeof import('vue'))['watch']
+ const watchEffect: (typeof import('vue'))['watchEffect']
+ const watchPostEffect: (typeof import('vue'))['watchPostEffect']
+ const watchSyncEffect: (typeof import('vue'))['watchSyncEffect']
+}
+// for type re-export
+declare global {
+ // @ts-ignore
+ export type {
+ Component,
+ ComponentPublicInstance,
+ ComputedRef,
+ DirectiveBinding,
+ ExtractDefaultPropTypes,
+ ExtractPropTypes,
+ ExtractPublicPropTypes,
+ InjectionKey,
+ PropType,
+ Ref,
+ MaybeRef,
+ MaybeRefOrGetter,
+ VNode,
+ WritableComputedRef
+ } from 'vue'
+ import('vue')
+}
diff --git a/web/admin-spa/components.d.ts b/web/admin-spa/components.d.ts
new file mode 100644
index 00000000..4eb0ab56
--- /dev/null
+++ b/web/admin-spa/components.d.ts
@@ -0,0 +1,16 @@
+/* eslint-disable */
+/* prettier-ignore */
+// @ts-nocheck
+// Generated by unplugin-vue-components
+// Read more: https://github.com/vuejs/core/pull/3399
+export {}
+
+declare module 'vue' {
+ export interface GlobalComponents {
+ ElConfigProvider: (typeof import('element-plus/es'))['ElConfigProvider']
+ ElDatePicker: (typeof import('element-plus/es'))['ElDatePicker']
+ ElTooltip: (typeof import('element-plus/es'))['ElTooltip']
+ RouterLink: (typeof import('vue-router'))['RouterLink']
+ RouterView: (typeof import('vue-router'))['RouterView']
+ }
+}
diff --git a/web/admin-spa/package-lock.json b/web/admin-spa/package-lock.json
index efe90dff..0b959aa3 100644
--- a/web/admin-spa/package-lock.json
+++ b/web/admin-spa/package-lock.json
@@ -15,6 +15,7 @@
"element-plus": "^2.4.4",
"pinia": "^2.1.7",
"vue": "^3.3.4",
+ "vue-i18n": "^9.14.5",
"vue-router": "^4.2.5",
"xlsx": "^0.18.5",
"xlsx-js-style": "^1.2.0"
@@ -657,6 +658,50 @@
"dev": true,
"license": "BSD-3-Clause"
},
+ "node_modules/@intlify/core-base": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+ "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/message-compiler": "9.14.5",
+ "@intlify/shared": "9.14.5"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/message-compiler": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+ "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "9.14.5",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/shared": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+ "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -3789,9 +3834,10 @@
},
"node_modules/prettier-plugin-tailwindcss": {
"version": "0.6.14",
- "resolved": "https://registry.npmmirror.com/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
+ "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
"integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
"dev": true,
+ "license": "MIT",
"engines": {
"node": ">=14.21.3"
},
@@ -5181,6 +5227,26 @@
"eslint": ">=6.0.0"
}
},
+ "node_modules/vue-i18n": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+ "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "9.14.5",
+ "@intlify/shared": "9.14.5",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
"node_modules/vue-router": {
"version": "4.5.1",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
@@ -5378,7 +5444,7 @@
},
"node_modules/xlsx-js-style": {
"version": "1.2.0",
- "resolved": "https://registry.npmmirror.com/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz",
+ "resolved": "https://registry.npmjs.org/xlsx-js-style/-/xlsx-js-style-1.2.0.tgz",
"integrity": "sha512-DDT4FXFSWfT4DXMSok/m3TvmP1gvO3dn0Eu/c+eXHW5Kzmp7IczNkxg/iEPnImbG9X0Vb8QhROda5eatSR/97Q==",
"license": "Apache-2.0",
"dependencies": {
diff --git a/web/admin-spa/package.json b/web/admin-spa/package.json
index af353d80..5feb58d8 100644
--- a/web/admin-spa/package.json
+++ b/web/admin-spa/package.json
@@ -18,6 +18,7 @@
"element-plus": "^2.4.4",
"pinia": "^2.1.7",
"vue": "^3.3.4",
+ "vue-i18n": "^9.14.5",
"vue-router": "^4.2.5",
"xlsx": "^0.18.5",
"xlsx-js-style": "^1.2.0"
diff --git a/web/admin-spa/src/App.vue b/web/admin-spa/src/App.vue
index c68bfba7..2b41d216 100644
--- a/web/admin-spa/src/App.vue
+++ b/web/admin-spa/src/App.vue
@@ -1,25 +1,39 @@