@lacqjs/nuxt-dict 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-PRESENT miaozhongfei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -0,0 +1,79 @@
1
+ <p align="center">
2
+ <h1 align="center">@lacqjs/nuxt-dict</h1>
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://npmjs.com/package/@lacqjs/nuxt-dict"><img src="https://img.shields.io/npm/v/@lacqjs/nuxt-dict?style=flat-square&colorA=202128&colorB=36936A" alt="Version"></a>
7
+ <a href="https://npmjs.com/package/@lacqjs/nuxt-dict"><img src="https://img.shields.io/npm/dm/@lacqjs/nuxt-dict?style=flat-square&colorA=202128&colorB=36936A" alt="Downloads"></a>
8
+ <a href="https://github.com/miaozhongfei/nuxt-dict/blob/main/LICENSE"><img src="https://img.shields.io/github/license/miaozhongfei/nuxt-dict?style=flat-square&colorA=202128&colorB=36936A" alt="License"></a>
9
+ <a href="https://miaozhongfei.github.io/nuxt-dict/"><img src="https://img.shields.io/badge/Docs-202128?style=flat-square&logo=gitbook&logoColor=DDDDD4" alt="Docs"></a>
10
+ </p>
11
+
12
+ > Nuxt 数据字典模块,提供扁平 / 树形字典翻译、多语言国际化、三级缓存与 SSR 预取。
13
+
14
+ ## 快速开始
15
+
16
+ ### 安装
17
+
18
+ ```bash
19
+ pnpm add @lacqjs/nuxt-dict
20
+ ```
21
+
22
+ ### 注册模块
23
+
24
+ ```ts
25
+ // nuxt.config.ts
26
+ export default defineNuxtConfig({
27
+ modules: ['@lacqjs/nuxt-dict'],
28
+ });
29
+ ```
30
+
31
+ ```vue
32
+ <template>
33
+ <el-select v-model="value" placeholder="请选择">
34
+ <el-option v-for="o in options" :key="o.value" :label="o.label" :value="o.value" />
35
+ </el-select>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ const { data: options } = useDict('gender');
40
+ const value = ref('');
41
+ </script>
42
+ ```
43
+
44
+ ## 特性
45
+
46
+ - **扁平字典** — `useDict(type)` 自动加载,返回 `[{ value, label }]`,直接绑定 UI 组件
47
+ - **树形字典** — `useDictTree(type)` 支持任意深度树形结构 + `findPath()` 路径回溯
48
+ - **多语言** — `useLocale()` 切换语言,所有活跃字典实例自动重新请求
49
+ - **多仓库** — 通过 `stores` 配置从不同 API 端点加载,实现数据隔离
50
+ - **三级缓存** — 内存 LRU → IndexedDB → 网络(版本校验),最大化减少请求
51
+ - **SSR 预取** — `ssr.prefetch` 配置服务端预加载,加速首屏渲染
52
+ - **自定义适配器** — 实现 `DictAdapter` 接口即可对接任意数据源
53
+ - **同步翻译** — `$dict.translate()` / `$dict.translatePath()` / `$dict.translateData()` 全场景覆盖
54
+
55
+ 完整用法与配置请查看 → [文档](https://miaozhongfei.github.io/nuxt-dict/)
56
+
57
+ ## 开发
58
+
59
+ ```bash
60
+ pnpm dev # 启动开发服务器
61
+ pnpm prepack # 构建产物
62
+ pnpm lint # 代码检查
63
+ pnpm typecheck # 类型检查
64
+ pnpm e2e # E2E 测试
65
+ ```
66
+
67
+ ## 感谢 / 致谢
68
+
69
+ 本项目受益于以下优秀开源项目:
70
+
71
+ - [Nuxt](https://nuxt.com/) — Vue 全栈框架
72
+ - [@nuxt/kit](https://github.com/nuxt/nuxt) — Nuxt 模块开发工具包
73
+ - [defu](https://github.com/unjs/defu) — 深度合并配置
74
+ - [consola](https://github.com/unjs/consola) — 日志工具
75
+ - [compare-versions](https://github.com/omichelsen/compare-versions) — 版本比较
76
+
77
+ ## 许可证
78
+
79
+ [MIT](./LICENSE) License © 2026-PRESENT [miaozhongfei](https://github.com/miaozhongfei)
package/dist/module.d.mts CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import * as __runtime_types from '../dist/runtime/types/index.js';
3
3
  import { ModuleOptions } from '../dist/runtime/types/index.js';
4
- export { ModuleOptions } from '../dist/runtime/types/index.js';
4
+ export { DictItem, DictTranslator, GetDictItemOptions, ModuleOptions, TranslateOptions, TranslatePathOptions, TreeNode } from '../dist/runtime/types/index.js';
5
+ export { DictManager } from '../dist/runtime/core/dict-manager.js';
6
+ export { createDictTranslator } from '../dist/runtime/utils/dict-translator.js';
5
7
 
6
8
  /**
7
9
  * Nuxt Dict 模块入口。
package/dist/module.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
  import * as __runtime_types from '../dist/runtime/types/index.js';
3
3
  import { ModuleOptions } from '../dist/runtime/types/index.js';
4
- export { ModuleOptions } from '../dist/runtime/types/index.js';
4
+ export { DictItem, DictTranslator, GetDictItemOptions, ModuleOptions, TranslateOptions, TranslatePathOptions, TreeNode } from '../dist/runtime/types/index.js';
5
+ export { DictManager } from '../dist/runtime/core/dict-manager.js';
6
+ export { createDictTranslator } from '../dist/runtime/utils/dict-translator.js';
5
7
 
6
8
  /**
7
9
  * Nuxt Dict 模块入口。
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lacqjs/nuxt-dict",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "configKey": "dict",
5
5
  "compatibility": {
6
6
  "nuxt": "^4.4.8"
package/dist/module.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { fileURLToPath } from 'url';
2
1
  import { defineNuxtModule, useLogger, createResolver, addPlugin, addImportsDir, addTypeTemplate } from '@nuxt/kit';
3
2
  import { defaultOptions } from '../dist/runtime/options.js';
4
3
  import { createLogger } from '../dist/runtime/utils/logger.js';
4
+ export { createDictTranslator } from '../dist/runtime/utils/dict-translator.js';
5
5
 
6
6
  const name = "@lacqjs/nuxt-dict";
7
- const version = "0.0.2";
7
+ const version = "0.0.4";
8
8
  const devDependencies = {
9
9
  nuxt: "^4.4.8"};
10
10
  const pkg = {
@@ -15,17 +15,139 @@ const pkg = {
15
15
  function registerTypeTemplates(resolver, stores) {
16
16
  addTypeTemplate({
17
17
  filename: "types/nuxt-dict.d.ts",
18
+ // eslint-disable max-lines-per-function
18
19
  getContents: () => `
19
- import type { DictManager } from '${pkg.name}'
20
+ import type { StoreKey } from '#build/types/nuxt-dict-store-names'
20
21
 
21
22
  declare module '#app' {
22
23
  interface NuxtApp {
23
- $dict: ReturnType<typeof import('${pkg.name}').createDictTranslator>
24
- $dictManager: DictManager
25
- }
26
- }
24
+ $dict: {
25
+ /**
26
+ * \u540C\u6B65\u7FFB\u8BD1\u5B57\u5178\u7F16\u7801 \u2192 \u6587\u672C\u3002
27
+ * @description \u540C\u6B65\u7FFB\u8BD1\u5B57\u5178\u7F16\u7801 \u2192 \u6587\u672C\u3002\u4ECE\u5168\u5C40\u5185\u5B58\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u6587\u672C\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837\u3002\u5148\u901A\u8FC7 useDict \u7B49\u52A0\u8F7D\u6570\u636E\u540E\u8C03\u7528\u3002
28
+ * @param type - \u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'gender'
29
+ * @param code - \u7F16\u7801\u503C
30
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
31
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
32
+ * @param {string} [opts.field] - \u6307\u5B9A\u53D6\u503C\u5B57\u6BB5\uFF0C\u9ED8\u8BA4 'label'
33
+ * @returns {string} \u7FFB\u8BD1\u6587\u672C\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837
34
+ * @example
35
+ * $dict.translate('gender', 'male')
36
+ * $dict.translate('gender', 'male', { storeName: 'dicts2' })
37
+ */
38
+ translate(type: string, code: string | number, opts?: { storeName?: StoreKey; field?: string }): string
39
+ /**
40
+ * \u6811\u5F62\u5B57\u5178\u4E2D\u67E5\u627E\u7F16\u7801\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\u3002
41
+ * @description \u6811\u5F62\u5B57\u5178\u4E2D\u67E5\u627E\u7F16\u7801\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\u3002\u4ECE\u5185\u5B58\u7F13\u5B58\u4E2D\u52A0\u8F7D\u7684\u6811\u5F62\u5B57\u5178\u6570\u636E\u91CC\uFF0C\u901A\u8FC7 DFS \u67E5\u627E\u76EE\u6807\u7F16\u7801\u5E76\u56DE\u6EAF\u5B8C\u6574\u8DEF\u5F84\uFF0C\u7528\u5206\u9694\u7B26\u62FC\u63A5\u540E\u8FD4\u56DE\u3002
42
+ * @param type - \u6811\u5F62\u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'region'
43
+ * @param code - \u53F6\u5B50\u8282\u70B9\u7F16\u7801\u503C
44
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
45
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
46
+ * @param {string} [opts.field] - \u6307\u5B9A\u8282\u70B9\u53D6\u503C\u5B57\u6BB5\uFF0C\u9ED8\u8BA4 'label'
47
+ * @param {string} [opts.separator] - \u5C42\u7EA7\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u9ED8\u8BA4 ' / '
48
+ * @returns {string} \u7528\u5206\u9694\u7B26\u8FDE\u63A5\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837
49
+ * @example
50
+ * $dict.translatePath('region', '440104')
51
+ * $dict.translatePath('region', '440104', { separator: ' \u2192 ' })
52
+ */
53
+ translatePath(type: string, code: string | number, opts?: { storeName?: StoreKey; field?: string; separator?: string }): string
54
+ /**
55
+ * \u6279\u91CF\u7FFB\u8BD1\u6570\u636E\u5BF9\u8C61\u4E2D\u7684\u591A\u4E2A\u7F16\u7801\u5B57\u6BB5\u3002
56
+ * @description \u6279\u91CF\u7FFB\u8BD1\u6570\u636E\u5BF9\u8C61\u4E2D\u7684\u591A\u4E2A\u7F16\u7801\u5B57\u6BB5\u3002\u4F20\u5165\u4E00\u4E2A\u6570\u636E\u5BF9\u8C61\u548C\u5B57\u6BB5\u2192\u5B57\u5178\u7C7B\u578B\u6620\u5C04\u8868\uFF0C\u8FD4\u56DE\u8FFD\u52A0\u4E86\u7FFB\u8BD1\u5B57\u6BB5\u7684\u65B0\u5BF9\u8C61\uFF08\u4E0D\u4FEE\u6539\u539F\u5BF9\u8C61\uFF09\u3002
57
+ * @param data - \u9700\u8981\u7FFB\u8BD1\u7684\u6570\u636E\u5BF9\u8C61\uFF0C\u5982 { gender: 'male', status: 1 }
58
+ * @param mapping - \u5B57\u6BB5\u2192\u5B57\u5178\u7C7B\u578B\u6620\u5C04\u8868\uFF0C\u503C\u4E3A string\uFF08\u9ED8\u8BA4\u4ED3\u5E93\uFF09\u6216 { type, storeName? }\uFF08\u6307\u5B9A\u4ED3\u5E93\uFF09
59
+ * @param suffix - \u7FFB\u8BD1\u5B57\u6BB5\u540E\u7F00\uFF0C\u9ED8\u8BA4 '_label'\uFF0C\u7ED3\u679C\u5199\u5165 \u539F\u5B57\u6BB5\u540D + suffix
60
+ * @returns {Record<string, unknown>} \u65B0\u5BF9\u8C61\uFF0C\u5305\u542B\u539F\u6570\u636E\u6240\u6709\u5B57\u6BB5 + \u4EE5 suffix \u4E3A\u540E\u7F00\u7684\u7FFB\u8BD1\u5B57\u6BB5
61
+ * @example
62
+ * $dict.translateData(
63
+ * { gender: 'male', status: 1, name: '\u5F20\u4E09' },
64
+ * { gender: 'gender', status: 'status' }
65
+ * )
66
+ * // \u2192 { ..., gender_label: '\u7537', status_label: '\u7981\u7528', ... }
67
+ */
68
+ translateData(data: Record<string, unknown>, mapping: Record<string, string | { type: string; storeName?: StoreKey }>, suffix?: string): Record<string, unknown>
69
+ /**
70
+ * \u4ECE\u5185\u5B58\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u5B8C\u6574\u5B57\u5178\u9879\u5BF9\u8C61\u3002
71
+ * @description \u4ECE\u5DF2\u52A0\u8F7D\u7684\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u5B8C\u6574 DictItem \u5BF9\u8C61\uFF08\u975E\u5B57\u7B26\u4E32\u7FFB\u8BD1\uFF09\u3002\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE undefined\u3002
72
+ * @param type - \u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'gender'
73
+ * @param code - \u7F16\u7801\u503C
74
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
75
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
76
+ * @returns {DictItem | undefined} \u5B8C\u6574\u7684\u5B57\u5178\u9879\u5BF9\u8C61\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE undefined
77
+ * @example
78
+ * $dict.getDictItem('gender', 'male')
79
+ * $dict.getDictItem('gender', 'male', { storeName: 'dicts2' })
80
+ */
81
+ getDictItem(type: string, code: string | number, opts?: { storeName?: StoreKey }): DictItem | undefined
82
+ }
83
+ }
84
+ }
85
+
86
+ declare module '@vue/runtime-core' {
87
+ interface ComponentCustomProperties {
88
+ $dict: {
89
+ /**
90
+ * \u540C\u6B65\u7FFB\u8BD1\u5B57\u5178\u7F16\u7801 \u2192 \u6587\u672C\u3002
91
+ * @description \u540C\u6B65\u7FFB\u8BD1\u5B57\u5178\u7F16\u7801 \u2192 \u6587\u672C\u3002\u4ECE\u5168\u5C40\u5185\u5B58\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u6587\u672C\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837\u3002
92
+ * @param type - \u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'gender'
93
+ * @param code - \u7F16\u7801\u503C
94
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
95
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
96
+ * @param {string} [opts.field] - \u6307\u5B9A\u53D6\u503C\u5B57\u6BB5\uFF0C\u9ED8\u8BA4 'label'
97
+ * @returns {string} \u7FFB\u8BD1\u6587\u672C\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837
98
+ * @example
99
+ * $dict.translate('gender', 'male')
100
+ * $dict.translate('gender', 'male', { storeName: 'dicts2' })
101
+ */
102
+ translate(type: string, code: string | number, opts?: { storeName?: StoreKey; field?: string }): string
103
+ /**
104
+ * \u6811\u5F62\u5B57\u5178\u4E2D\u67E5\u627E\u7F16\u7801\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\u3002
105
+ * @description \u6811\u5F62\u5B57\u5178\u4E2D\u67E5\u627E\u7F16\u7801\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\u3002\u901A\u8FC7 DFS \u67E5\u627E\u76EE\u6807\u7F16\u7801\u5E76\u56DE\u6EAF\u5B8C\u6574\u8DEF\u5F84\uFF0C\u7528\u5206\u9694\u7B26\u62FC\u63A5\u540E\u8FD4\u56DE\u3002
106
+ * @param type - \u6811\u5F62\u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'region'
107
+ * @param code - \u53F6\u5B50\u8282\u70B9\u7F16\u7801\u503C
108
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
109
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
110
+ * @param {string} [opts.field] - \u6307\u5B9A\u8282\u70B9\u53D6\u503C\u5B57\u6BB5\uFF0C\u9ED8\u8BA4 'label'
111
+ * @param {string} [opts.separator] - \u5C42\u7EA7\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u9ED8\u8BA4 ' / '
112
+ * @returns {string} \u7528\u5206\u9694\u7B26\u8FDE\u63A5\u7684\u5B8C\u6574\u5C42\u7EA7\u8DEF\u5F84\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE code \u539F\u6837
113
+ * @example
114
+ * $dict.translatePath('region', '440104')
115
+ * $dict.translatePath('region', '440104', { separator: ' \u2192 ' })
116
+ */
117
+ translatePath(type: string, code: string | number, opts?: { storeName?: StoreKey; field?: string; separator?: string }): string
118
+ /**
119
+ * \u6279\u91CF\u7FFB\u8BD1\u6570\u636E\u5BF9\u8C61\u4E2D\u7684\u591A\u4E2A\u7F16\u7801\u5B57\u6BB5\u3002
120
+ * @description \u6279\u91CF\u7FFB\u8BD1\u6570\u636E\u5BF9\u8C61\u4E2D\u7684\u591A\u4E2A\u7F16\u7801\u5B57\u6BB5\u3002\u4F20\u5165\u5B57\u6BB5\u2192\u5B57\u5178\u7C7B\u578B\u6620\u5C04\u8868\uFF0C\u8FD4\u56DE\u542B\u7FFB\u8BD1\u5B57\u6BB5\u7684\u65B0\u5BF9\u8C61\uFF08\u4E0D\u4FEE\u6539\u539F\u5BF9\u8C61\uFF09\u3002
121
+ * @param data - \u9700\u8981\u7FFB\u8BD1\u7684\u6570\u636E\u5BF9\u8C61\uFF0C\u5982 { gender: 'male', status: 1 }
122
+ * @param mapping - \u5B57\u6BB5\u2192\u5B57\u5178\u7C7B\u578B\u6620\u5C04\u8868\uFF0C\u503C\u4E3A string\uFF08\u9ED8\u8BA4\u4ED3\u5E93\uFF09\u6216 { type, storeName? }\uFF08\u6307\u5B9A\u4ED3\u5E93\uFF09
123
+ * @param suffix - \u7FFB\u8BD1\u5B57\u6BB5\u540E\u7F00\uFF0C\u9ED8\u8BA4 '_label'
124
+ * @returns {Record<string, unknown>} \u65B0\u5BF9\u8C61\uFF0C\u5305\u542B\u539F\u6570\u636E\u6240\u6709\u5B57\u6BB5 + \u4EE5 suffix \u4E3A\u540E\u7F00\u7684\u7FFB\u8BD1\u5B57\u6BB5
125
+ * @example
126
+ * $dict.translateData(
127
+ * { gender: 'male', status: 1, name: '\u5F20\u4E09' },
128
+ * { gender: 'gender', status: 'status' }
129
+ * )
130
+ * // \u2192 { ..., gender_label: '\u7537', status_label: '\u7981\u7528', ... }
131
+ */
132
+ translateData(data: Record<string, unknown>, mapping: Record<string, string | { type: string; storeName?: StoreKey }>, suffix?: string): Record<string, unknown>
133
+ /**
134
+ * \u4ECE\u5185\u5B58\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u5B8C\u6574\u5B57\u5178\u9879\u5BF9\u8C61\u3002
135
+ * @description \u4ECE\u5DF2\u52A0\u8F7D\u7684\u7F13\u5B58\u4E2D\u67E5\u627E\u7F16\u7801\u5BF9\u5E94\u7684\u5B8C\u6574 DictItem \u5BF9\u8C61\uFF08\u975E\u5B57\u7B26\u4E32\u7FFB\u8BD1\uFF09\u3002\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE undefined\u3002
136
+ * @param type - \u5B57\u5178\u7C7B\u578B\u540D\uFF0C\u5982 'gender'
137
+ * @param code - \u7F16\u7801\u503C
138
+ * @param opts - \u53EF\u9009\u914D\u7F6E\u5BF9\u8C61
139
+ * @param {StoreKey} [opts.storeName] - \u6307\u5B9A\u4ED3\u5E93\u540D
140
+ * @returns {DictItem | undefined} \u5B8C\u6574\u7684\u5B57\u5178\u9879\u5BF9\u8C61\uFF0C\u7F13\u5B58\u672A\u547D\u4E2D\u65F6\u8FD4\u56DE undefined
141
+ * @example
142
+ * $dict.getDictItem('gender', 'male')
143
+ * $dict.getDictItem('gender', 'male', { storeName: 'dicts2' })
144
+ */
145
+ getDictItem(type: string, code: string | number, opts?: { storeName?: StoreKey }): DictItem | undefined
146
+ }
147
+ }
148
+ }
27
149
 
28
- export {}
150
+ export {}
29
151
  `
30
152
  });
31
153
  addTypeTemplate({
@@ -58,11 +180,10 @@ const module$1 = defineNuxtModule().with({
58
180
  return;
59
181
  }
60
182
  const resolver = createResolver(import.meta.url);
61
- const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
62
183
  _nuxt.options.runtimeConfig.public.dict = _options;
63
- _nuxt.options.build.transpile.push(resolver.resolve(runtimeDir));
64
- addPlugin({ src: resolver.resolve(runtimeDir, "plugins", "dict.ts") });
65
- addImportsDir(resolver.resolve(runtimeDir, "composables"));
184
+ _nuxt.options.build.transpile.push(resolver.resolve("./runtime"));
185
+ addPlugin(resolver.resolve("./runtime/plugins/dict"));
186
+ addImportsDir(resolver.resolve("./runtime/composables"));
66
187
  registerTypeTemplates(resolver, _options.stores);
67
188
  _nuxt.hook("prepare:types", ({ references }) => {
68
189
  references.push({ types: pkg.name });
@@ -4,6 +4,12 @@ import type { UseDictReturn, StoreKey } from '../types/index.js';
4
4
  * 组件挂载时自动加载,卸载时自动清理引用。
5
5
  * 返回翻译函数、加载状态和手动刷新方法。
6
6
  *
7
+ * @description 从字典管理器获取扁平字典数据,挂载后自动触发网络请求(或缓存)。
8
+ * @param {string} type - 字典类型名(如 'gender'),一参形式:useDict(type),使用默认仓库 'dicts'
9
+ * @param {StoreKey} storeName - 仓库名(如 'dicts2'),二参形式:useDict(storeName, type)
10
+ * @param {string} type - 字典类型名,二参形式时作为第二参数传入
11
+ * @returns {UseDictReturn} 包含 data(字典项数组)、translate(翻译函数)、loading、error、refresh
12
+ *
7
13
  * @example
8
14
  * // 默认存储库 'dicts'
9
15
  * const { data, translate } = useDict('gender')
@@ -2,12 +2,18 @@ import { shallowRef, ref, watch, onMounted, onBeforeUnmount } from "vue";
2
2
  import { useNuxtApp } from "#imports";
3
3
  import { DEFAULT_STORE_NAME } from "../core/cache/indexeddb-cache.js";
4
4
  const activeInstances = /* @__PURE__ */ new Map();
5
- async function fetchDictData(manager, dictType, storeName, data, loading, error, mode = "load") {
5
+ async function fetchDictData(manager, dictType, storeName, data, loading, error, mode = "load", itemMap) {
6
6
  loading.value = true;
7
7
  error.value = null;
8
8
  try {
9
9
  const entry = await (mode === "refresh" ? manager.refresh(dictType, storeName) : manager.getDict(dictType, storeName));
10
10
  data.value = entry.items;
11
+ if (itemMap) {
12
+ itemMap.clear();
13
+ for (const item of entry.items) {
14
+ itemMap.set(String(item.value), item);
15
+ }
16
+ }
11
17
  } catch (e) {
12
18
  error.value = e instanceof Error ? e.message : String(e);
13
19
  } finally {
@@ -35,25 +41,48 @@ export function useDict(storeOrType, maybeType) {
35
41
  const storeName = maybeType === void 0 ? DEFAULT_STORE_NAME : storeOrType;
36
42
  const dictType = maybeType ?? storeOrType;
37
43
  const data = shallowRef(null);
44
+ const itemMap = /* @__PURE__ */ new Map();
38
45
  const loading = ref(false);
39
46
  const error = ref(null);
40
47
  const instanceId = Symbol(dictType);
41
48
  const trackKey = `${storeName}:${dictType}`;
42
49
  trackInstance(trackKey, instanceId);
43
- function translate(code) {
44
- return manager.translate(dictType, code, storeName);
50
+ function translate(value, opts) {
51
+ const targetStore = opts?.storeName ?? storeName;
52
+ const field = opts?.field ?? "label";
53
+ if (targetStore !== storeName) {
54
+ return manager.translate(dictType, value, { storeName: targetStore, field });
55
+ }
56
+ if (!data.value) return String(value);
57
+ const item = itemMap.get(String(value));
58
+ if (!item) return String(value);
59
+ return item[field] ?? item.label;
60
+ }
61
+ function getDictItem(value, opts) {
62
+ if (opts?.storeName && opts.storeName !== storeName) {
63
+ return manager.getDictItem(dictType, value, opts);
64
+ }
65
+ if (!data.value) return void 0;
66
+ return itemMap.get(String(value));
45
67
  }
46
68
  async function refresh() {
47
- await fetchDictData(manager, dictType, storeName, data, loading, error, "refresh");
69
+ await fetchDictData(manager, dictType, storeName, data, loading, error, "refresh", itemMap);
48
70
  }
49
71
  onMounted(() => {
50
- fetchDictData(manager, dictType, storeName, data, loading, error, "load");
72
+ fetchDictData(manager, dictType, storeName, data, loading, error, "load", itemMap);
51
73
  });
52
74
  watch(
53
75
  () => manager.locale.value,
54
76
  () => {
55
- fetchDictData(manager, dictType, storeName, data, loading, error, "load");
77
+ fetchDictData(manager, dictType, storeName, data, loading, error, "load", itemMap);
56
78
  }
57
79
  );
58
- return { data, translate, loading, error, refresh };
80
+ return {
81
+ data,
82
+ translate,
83
+ getDictItem,
84
+ loading,
85
+ error,
86
+ refresh
87
+ };
59
88
  }
@@ -3,6 +3,12 @@ import type { UseDictTreeReturn, StoreKey } from '../types/index.js';
3
3
  * 使用树形字典数据,支持翻译和路径查找。
4
4
  * 组件挂载时自动加载,适用于区域选择器等级联场景。
5
5
  *
6
+ * @description 从字典管理器获取树形字典数据,挂载后自动触发网络请求(或缓存)。
7
+ * @param {string} type - 字典类型名(如 'region'),一参形式:useDictTree(type),使用默认仓库 'dicts'
8
+ * @param {StoreKey} storeName - 仓库名(如 'dicts2'),二参形式:useDictTree(storeName, type)
9
+ * @param {string} type - 字典类型名,二参形式时作为第二参数传入
10
+ * @returns {UseDictTreeReturn} 包含 tree(树节点数组)、translate(翻译函数)、findPath(路径回溯)、loading、refresh
11
+ *
6
12
  * @example
7
13
  * // 默认存储库 'dicts'
8
14
  * const { tree, translate } = useDictTree('region')
@@ -7,19 +7,30 @@ export function useDictTree(storeOrType, maybeType) {
7
7
  const storeName = maybeType === void 0 ? DEFAULT_STORE_NAME : storeOrType;
8
8
  const dictType = maybeType ?? storeOrType;
9
9
  const tree = shallowRef(null);
10
+ const nodeMap = /* @__PURE__ */ new Map();
11
+ const pathMap = /* @__PURE__ */ new Map();
10
12
  const loading = ref(false);
11
- function translate(code) {
12
- return manager.translate(dictType, code, storeName);
13
+ function translate(value, opts) {
14
+ const targetStore = opts?.storeName ?? storeName;
15
+ const field = opts?.field ?? "label";
16
+ if (targetStore !== storeName) {
17
+ return manager.translate(dictType, value, { storeName: targetStore, field });
18
+ }
19
+ if (!tree.value) return String(value);
20
+ const node = nodeMap.get(String(value));
21
+ if (!node) return String(value);
22
+ return node[field] ?? node.label;
13
23
  }
14
- function findPath(code) {
24
+ function findPath(value) {
15
25
  if (!tree.value) return [];
16
- return findPathInTree(tree.value, code);
26
+ return pathMap.get(String(value)) ?? [];
17
27
  }
18
28
  async function load() {
19
29
  loading.value = true;
20
30
  try {
21
31
  const entry = await manager.getDict(dictType, storeName);
22
32
  tree.value = entry.tree ?? null;
33
+ if (entry.tree) buildMaps(entry.tree, nodeMap, pathMap);
23
34
  } finally {
24
35
  loading.value = false;
25
36
  }
@@ -29,25 +40,29 @@ export function useDictTree(storeOrType, maybeType) {
29
40
  try {
30
41
  const entry = await manager.refresh(dictType, storeName);
31
42
  tree.value = entry.tree ?? null;
43
+ if (entry.tree) buildMaps(entry.tree, nodeMap, pathMap);
32
44
  } finally {
33
45
  loading.value = false;
34
46
  }
35
47
  }
36
48
  onMounted(load);
37
49
  watch(() => manager.locale.value, load);
38
- return { tree, translate, findPath, loading, refresh };
50
+ return {
51
+ tree,
52
+ translate,
53
+ findPath,
54
+ loading,
55
+ refresh
56
+ };
39
57
  }
40
- function findPathInTree(nodes, targetCode) {
58
+ function buildMaps(nodes, nodeMap, pathMap, ancestors = []) {
41
59
  for (const node of nodes) {
42
- if (node.code === targetCode) {
43
- return [node.label];
44
- }
60
+ const code = String(node.value);
61
+ const path = [...ancestors, node.label];
62
+ nodeMap.set(code, node);
63
+ pathMap.set(code, path);
45
64
  if (node.children && node.children.length > 0) {
46
- const childPath = findPathInTree(node.children, targetCode);
47
- if (childPath.length > 0) {
48
- return [node.label, ...childPath];
49
- }
65
+ buildMaps(node.children, nodeMap, pathMap, path);
50
66
  }
51
67
  }
52
- return [];
53
68
  }
@@ -2,6 +2,20 @@
2
2
  * 获取/切换当前语言。
3
3
  * 切换语言时会同步更新 cookie(客户端)并通知 DictManager 刷新缓存。
4
4
  * locale 为 DictManager 上的响应式 ref,语言切换后所有 useDict / useDictTree 组件自动重取。
5
+ *
6
+ * @description 从 DictManager 获取当前语言信息,提供切换语言并持久化到 cookie 的能力。
7
+ * @returns {{ locale: Ref<string>, setLocale: (newLocale: string) => void, locales: string[] }} 当前语言 ref、语言切换函数、支持的语言列表
8
+ *
9
+ * @example
10
+ * const { locale, setLocale } = useLocale()
11
+ *
12
+ * // 切换语言
13
+ * setLocale('en-US') // 切换到英文
14
+ * console.log(locale.value) // 'en-US'
15
+ *
16
+ * // 模板中直接使用
17
+ * // <p>当前语言:{{ locale }}</p>
18
+ * // <button @click="setLocale('zh-CN')">中文</button>
5
19
  */
6
20
  export declare function useLocale(): {
7
21
  locale: import("vue").ShallowRef<string, string>;
@@ -1,5 +1,16 @@
1
1
  import type { DictAdapter } from '../types/index.js';
2
- /** 创建默认适配器所需的配置参数 */
2
+ /**
3
+ * 创建默认适配器所需的配置参数。
4
+ *
5
+ * @example
6
+ * const adapter = createDefaultAdapter({
7
+ * baseURL: '/api',
8
+ * dictEndpoint: '/dict/list',
9
+ * versionEndpoint: '/dict/version',
10
+ * paramKey: 'lang',
11
+ * apiHeaderKey: 'X-Locale',
12
+ * })
13
+ */
3
14
  export interface DefaultAdapterOptions {
4
15
  baseURL: string;
5
16
  dictEndpoint: string;
@@ -13,5 +24,20 @@ export interface DefaultAdapterOptions {
13
24
  * 创建默认的 REST 字典适配器。
14
25
  * 底层使用原生 fetch,兼容浏览器和 Node.js 18+(Nuxt 4 要求)。
15
26
  * SSR 侧由 resolveBaseURL() 保证 baseURL 为绝对 origin,客户端相对路径由浏览器处理。
27
+ *
28
+ * @param {DefaultAdapterOptions} options - 适配器配置参数(baseURL、dictEndpoint、versionEndpoint、paramKey、apiHeaderKey)
29
+ * @returns {DictAdapter} 实现了 fetchDict / fetchVersion 方法的 DictAdapter 实例
30
+ *
31
+ * @example
32
+ * const adapter = createDefaultAdapter({
33
+ * baseURL: 'https://api.example.com',
34
+ * dictEndpoint: '/v1/dict/list',
35
+ * versionEndpoint: '/v1/dict/version',
36
+ * paramKey: 'lang',
37
+ * apiHeaderKey: 'X-Locale',
38
+ * })
39
+ *
40
+ * // 用于 ModuleOptions.api.adapter
41
+ * // 或 stores.<storeName>.adapter
16
42
  */
17
43
  export declare function createDefaultAdapter(options: DefaultAdapterOptions): DictAdapter;
@@ -20,7 +20,9 @@ async function fetchDictImpl(_storeName, config, types, locale) {
20
20
  }
21
21
  return response.json();
22
22
  } catch (e) {
23
- throw new Error(`Failed to fetch dictionary: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
23
+ throw new Error(`Failed to fetch dictionary: ${e instanceof Error ? e.message : String(e)}`, {
24
+ cause: e
25
+ });
24
26
  }
25
27
  }
26
28
  async function fetchVersionImpl(_storeName, config) {
@@ -36,7 +38,9 @@ async function fetchVersionImpl(_storeName, config) {
36
38
  }
37
39
  return data.version;
38
40
  } catch (e) {
39
- throw new Error(`Failed to fetch version: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
41
+ throw new Error(`Failed to fetch version: ${e instanceof Error ? e.message : String(e)}`, {
42
+ cause: e
43
+ });
40
44
  }
41
45
  }
42
46
  export function createDefaultAdapter(options) {
@@ -27,7 +27,9 @@ export class IndexedDBCache {
27
27
  resolve();
28
28
  });
29
29
  request.addEventListener("error", () => {
30
- reject(new Error("Failed to open IndexedDB: " + (request.error?.message || "unknown error")));
30
+ reject(
31
+ new Error("Failed to open IndexedDB: " + (request.error?.message || "unknown error"))
32
+ );
31
33
  });
32
34
  request.addEventListener("blocked", () => {
33
35
  reject(new Error("IndexedDB is blocked by another tab"));