create-lumal-admin 0.0.5-beta → 0.0.6-beta

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.
Files changed (194) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +39 -10
  3. package/dist/cli.d.ts +9 -3
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +236 -19
  6. package/dist/cli.js.map +1 -1
  7. package/dist/create.d.ts +10 -1
  8. package/dist/create.d.ts.map +1 -1
  9. package/dist/create.js +18 -5
  10. package/dist/create.js.map +1 -1
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/presets.d.ts +29 -3
  16. package/dist/presets.d.ts.map +1 -1
  17. package/dist/presets.js +38 -10
  18. package/dist/presets.js.map +1 -1
  19. package/dist/project-cli.d.ts +3 -0
  20. package/dist/project-cli.d.ts.map +1 -0
  21. package/dist/project-cli.js +13 -0
  22. package/dist/project-cli.js.map +1 -0
  23. package/dist/projects.d.ts +21 -0
  24. package/dist/projects.d.ts.map +1 -0
  25. package/dist/projects.js +124 -0
  26. package/dist/projects.js.map +1 -0
  27. package/dist/templates.d.ts +2 -0
  28. package/dist/templates.d.ts.map +1 -1
  29. package/dist/templates.js +42 -13
  30. package/dist/templates.js.map +1 -1
  31. package/dist/types.d.ts +39 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +11 -3
  34. package/templates/base/API-CONTRACT.md +67 -20
  35. package/templates/base/README.md +20 -3
  36. package/templates/base/src/App.vue +71 -79
  37. package/templates/base/src/app/components/AppHeaderActions.vue +46 -0
  38. package/templates/base/src/{components/app → app/components}/AppSettingsDrawer.vue +15 -15
  39. package/templates/base/src/app/menu-policy.ts +163 -0
  40. package/templates/base/src/app/module.ts +46 -0
  41. package/templates/base/src/app/router/index.ts +107 -0
  42. package/templates/base/src/app/router/views.ts +12 -0
  43. package/templates/base/src/{services → app/services}/access.ts +1 -1
  44. package/templates/base/src/app/services/contracts.ts +54 -0
  45. package/templates/base/src/app/services/local-contracts.ts +18 -0
  46. package/templates/base/src/{services → app/services}/preferences.ts +8 -8
  47. package/templates/base/src/app/services/session-runtime.ts +186 -0
  48. package/templates/base/src/{services → app/services}/session.ts +4 -4
  49. package/templates/base/src/app/stored-menus.ts +72 -0
  50. package/templates/base/src/app/views/SafeIframeView.vue +34 -0
  51. package/templates/base/src/{views → app/views}/auth/LoginView.vue +29 -34
  52. package/templates/base/src/app/views/error/ForbiddenView.vue +20 -0
  53. package/templates/base/src/{views → app/views}/error/NotFoundView.vue +5 -7
  54. package/templates/base/src/main.ts +21 -22
  55. package/templates/base/src/modules/business/README.md +33 -0
  56. package/templates/base/src/{views → modules}/dashboard/DashboardView.vue +3 -3
  57. package/templates/base/src/modules/dashboard/module.ts +14 -0
  58. package/templates/base/src/shared/api.ts +53 -0
  59. package/templates/base/src/shared/components/BaseEntityDialog.vue +39 -0
  60. package/templates/base/src/shared/composables/use-async-data.ts +35 -0
  61. package/templates/base/src/shared/composables/use-entity-editor.ts +77 -0
  62. package/templates/base/src/shared/status.ts +4 -0
  63. package/templates/base/src/shared/tree.ts +41 -0
  64. package/templates/base/src/styles.scss +1 -6
  65. package/templates/base/tests/menu-policy.test.ts.template +89 -0
  66. package/templates/base/tests/session.test.ts.template +38 -27
  67. package/templates/base/vite.config.ts +4 -4
  68. package/templates/blank/README.md +6 -0
  69. package/templates/blank/src/App.vue +11 -0
  70. package/templates/common/AGENTS.md +13 -0
  71. package/templates/common/_editorconfig +15 -0
  72. package/templates/common/_gitattributes +6 -0
  73. package/templates/{base → common}/_gitignore +1 -0
  74. package/templates/common/_prettierignore +6 -0
  75. package/templates/common/docs/DEVELOPMENT.md +26 -0
  76. package/templates/common/eslint.config.mjs +60 -0
  77. package/templates/common/package.json +39 -0
  78. package/templates/common/prettier.config.mjs +8 -0
  79. package/templates/{base → common}/tsconfig.json +2 -5
  80. package/templates/common/vitest.config.ts +3 -0
  81. package/templates/features/examples/src/modules/examples/CrudExampleView.vue +51 -0
  82. package/templates/features/examples/src/{views → modules}/examples/FormExampleView.vue +15 -8
  83. package/templates/features/examples/src/modules/examples/module.ts +27 -0
  84. package/templates/features/system/src/modules/system/config/ConfigView.vue +104 -0
  85. package/templates/features/system/src/modules/system/config/api.ts +38 -0
  86. package/templates/features/system/src/modules/system/config/routes.ts +21 -0
  87. package/templates/features/system/src/modules/system/dictionary/DictionaryItemView.vue +141 -0
  88. package/templates/features/system/src/modules/system/dictionary/DictionaryTypeView.vue +107 -0
  89. package/templates/features/system/src/modules/system/dictionary/api.ts +61 -0
  90. package/templates/features/system/src/modules/system/dictionary/cache.ts +9 -0
  91. package/templates/features/system/src/modules/system/dictionary/routes.ts +32 -0
  92. package/templates/features/system/src/modules/system/dictionary/types.ts +22 -0
  93. package/templates/features/system/src/modules/system/local.ts +348 -0
  94. package/templates/features/system/src/modules/system/menu/MenuView.vue +133 -0
  95. package/templates/features/system/src/modules/system/menu/api.ts +73 -0
  96. package/templates/features/system/src/modules/system/menu/components/MenuEditorDialog.vue +106 -0
  97. package/templates/features/system/src/modules/system/menu/routes.ts +23 -0
  98. package/templates/features/system/src/modules/system/menu/types.ts +18 -0
  99. package/templates/features/system/src/modules/system/module.ts +22 -0
  100. package/templates/features/system/src/modules/system/organization/OrganizationView.vue +134 -0
  101. package/templates/features/system/src/modules/system/organization/api.ts +38 -0
  102. package/templates/features/system/src/modules/system/organization/routes.ts +23 -0
  103. package/templates/features/system/src/modules/system/organization/types.ts +17 -0
  104. package/templates/features/system/src/modules/system/role/RoleView.vue +90 -0
  105. package/templates/features/system/src/modules/system/role/api.ts +40 -0
  106. package/templates/features/system/src/modules/system/role/components/RoleAuthorizationDialog.vue +110 -0
  107. package/templates/features/system/src/modules/system/role/permission-tree.ts +31 -0
  108. package/templates/features/system/src/modules/system/role/routes.ts +24 -0
  109. package/templates/features/system/src/modules/system/role/types.ts +12 -0
  110. package/templates/features/system/src/modules/system/user/UserView.vue +297 -0
  111. package/templates/features/system/src/modules/system/user/api.ts +45 -0
  112. package/templates/features/system/src/modules/system/user/components/UserOrganizationFilter.vue +61 -0
  113. package/templates/features/system/src/modules/system/user/components/UserRoleDialog.vue +93 -0
  114. package/templates/features/system/src/modules/system/user/routes.ts +27 -0
  115. package/templates/features/system/src/modules/system/user/types.ts +28 -0
  116. package/templates/features/system/tests/system.test.ts.template +266 -0
  117. package/templates/modes/http/src/app/services/data-source.ts +124 -0
  118. package/templates/modes/http/src/app/services/http-adapter.ts +128 -0
  119. package/templates/modes/http/tests/http.test.ts.template +186 -133
  120. package/templates/modes/local/src/app/services/data-source.ts +113 -0
  121. package/templates/modes/local/src/app/services/local-database.ts +67 -0
  122. package/templates/modes/local/tests/local.test.ts.template +117 -77
  123. package/templates/presets/standard/src/modules/dashboard/DashboardView.vue +52 -0
  124. package/templates/presets/standard/src/modules/dashboard/components/DashboardSessionStatus.vue +41 -0
  125. package/templates/presets/standard/src/modules/dashboard/components/DashboardShortcuts.vue +51 -0
  126. package/templates/presets/standard/src/modules/profile/ProfileView.vue +109 -0
  127. package/templates/presets/standard/src/modules/profile/api.ts +49 -0
  128. package/templates/presets/standard/src/modules/profile/components/ProfilePasswordForm.vue +68 -0
  129. package/templates/presets/standard/src/modules/profile/local.ts +40 -0
  130. package/templates/presets/standard/src/modules/profile/module.ts +11 -0
  131. package/templates/presets/standard/tests/profile.test.ts.template +72 -0
  132. package/templates/screen/README.md +6 -0
  133. package/templates/screen/src/App.vue +7 -0
  134. package/templates/screen/src/screen/ScreenEntry.vue +18 -0
  135. package/templates/screen/src/screen/ScreenView.vue +48 -0
  136. package/templates/screen/src/screen/components/ScreenMetrics.vue +40 -0
  137. package/templates/screen-auth/src/screen/ScreenEntry.vue +52 -0
  138. package/templates/screen-auth/src/screen/components/ScreenLoginForm.vue +44 -0
  139. package/templates/screen-auth/src/services/session.ts +61 -0
  140. package/templates/screen-auth/tests/session.test.ts.template +54 -0
  141. package/templates/screen-auth-http/src/services/auth-adapter.ts +14 -0
  142. package/templates/screen-auth-http/tests/auth-adapter.test.ts.template +17 -0
  143. package/templates/screen-auth-local-demo/src/services/auth-adapter.ts +15 -0
  144. package/templates/screen-auth-local-demo/tests/auth-adapter.test.ts.template +18 -0
  145. package/templates/screen-data/src/screen/ScreenView.vue +69 -0
  146. package/templates/screen-data/src/services/metrics-types.ts +4 -0
  147. package/templates/screen-data-http/src/services/metrics.ts +20 -0
  148. package/templates/screen-data-http/src/services/request.ts +3 -0
  149. package/templates/screen-data-http/tests/metrics.test.ts.template +21 -0
  150. package/templates/screen-data-local/src/services/metrics.ts +11 -0
  151. package/templates/screen-data-local/tests/metrics.test.ts.template +9 -0
  152. package/templates/screen-http/API-CONTRACT.md +20 -0
  153. package/templates/screen-http/src/services/http.ts +17 -0
  154. package/templates/screen-request-auth/src/services/request.ts +15 -0
  155. package/templates/screen-router/src/App.vue +7 -0
  156. package/templates/screen-router/src/main.ts +13 -0
  157. package/templates/screen-router/src/router.ts +7 -0
  158. package/templates/screen-router/tests/app.test.ts.template +20 -0
  159. package/templates/screen-viewport/src/screen/ScreenViewport.vue +30 -0
  160. package/templates/screen-visualization-charts/src/screen/components/ScreenVisualization.vue +22 -0
  161. package/templates/screen-visualization-cockpit/src/screen/components/ScreenVisualization.vue +33 -0
  162. package/templates/screen-visualization-cockpit/src/screen/components/ScreenWidget.vue +10 -0
  163. package/templates/screen-visualization-datav/src/screen/components/ScreenVisualization.vue +11 -0
  164. package/templates/vben-compatible/MIGRATION.md +2 -0
  165. package/templates/vben-compatible/src/{views/migration → modules/vben-migration}/VbenFormView.vue +2 -1
  166. package/templates/vben-compatible/src/{views/migration → modules/vben-migration}/VbenUsersView.vue +3 -2
  167. package/templates/vben-compatible/src/modules/vben-migration/components/VbenFormExample.vue +64 -0
  168. package/templates/vben-compatible/src/{components/migration → modules/vben-migration/components}/VbenUsers.vue +73 -64
  169. package/templates/vben-compatible/src/modules/vben-migration/local.ts +68 -0
  170. package/templates/vben-compatible/src/modules/vben-migration/module.ts +42 -0
  171. package/templates/vue/index.html +12 -0
  172. package/templates/vue/src/main.ts +5 -0
  173. package/templates/vue/src/styles.css +19 -0
  174. package/templates/vue/tests/app.test.ts.template +10 -0
  175. package/templates/vue/vite.config.ts +9 -0
  176. package/templates/vue/vitest.config.ts +4 -0
  177. package/templates/base/eslint.config.mjs +0 -8
  178. package/templates/base/src/components/app/AppHeaderActions.vue +0 -34
  179. package/templates/base/src/router/index.ts +0 -86
  180. package/templates/base/src/router/views.ts +0 -16
  181. package/templates/base/src/services/contracts.ts +0 -29
  182. package/templates/base/src/services/session-runtime.ts +0 -137
  183. package/templates/base/src/views/error/ForbiddenView.vue +0 -25
  184. package/templates/base/vitest.config.ts +0 -3
  185. package/templates/features/examples/src/modules/examples.ts +0 -10
  186. package/templates/features/examples/src/views/examples/CrudExampleView.vue +0 -30
  187. package/templates/features/system/src/business/system/entities.ts +0 -93
  188. package/templates/features/system/src/modules/system.ts +0 -28
  189. package/templates/features/system/src/views/system/SystemEntityView.vue +0 -85
  190. package/templates/modes/http/src/services/data-source.ts +0 -120
  191. package/templates/modes/http/src/services/http-adapter.ts +0 -124
  192. package/templates/modes/local/src/services/data-source.ts +0 -176
  193. package/templates/vben-compatible/src/components/migration/VbenFormExample.vue +0 -34
  194. package/templates/vben-compatible/src/modules/vben.ts +0 -17
@@ -0,0 +1,141 @@
1
+ <script setup lang="ts">
2
+ import type { SchemaFormItem, SchemaTableColumn } from '@lumal/core/components';
3
+ import type { DictionaryItem } from './types';
4
+ import { LumalPage, LumalSchemaTable } from '@lumal/core/components';
5
+ import { LumalIcon } from '@lumal/icons-vue';
6
+ import { ElAlert, ElButton, ElColorPicker, ElOption, ElSelect } from 'element-plus';
7
+ import { computed, shallowRef, watch } from 'vue';
8
+ import { useRoute } from 'vue-router';
9
+ import { permissionStore, service } from '../../../app/services/session';
10
+ import BaseEntityDialog from '../../../shared/components/BaseEntityDialog.vue';
11
+ import { useAsyncData } from '../../../shared/composables/use-async-data';
12
+ import { useEntityEditor } from '../../../shared/composables/use-entity-editor';
13
+ import { statusOptions } from '../../../shared/status';
14
+ import { createDictionaryService, dictionaryItemInput, parseItem } from './api';
15
+ import { dictionaryStore } from './cache';
16
+
17
+ /***********************分类筛选与实时字典缓存*********************/
18
+ const route = useRoute();
19
+ const typeCode = shallowRef(typeof route.query.typeCode === 'string' ? route.query.typeCode : '');
20
+ const api = createDictionaryService(service, () => dictionaryStore.clearDictionary());
21
+ const types = useAsyncData(api.types, []);
22
+ const state = useAsyncData(() => api.items(typeCode.value), [] as DictionaryItem[]);
23
+ const can = (action: string) => permissionStore.hasPermission(`system:dict:${action}`);
24
+ watch(
25
+ () => route.query.typeCode,
26
+ (value) => {
27
+ typeCode.value = typeof value === 'string' ? value : '';
28
+ },
29
+ );
30
+ watch(typeCode, () => {
31
+ void state.reload();
32
+ });
33
+ const editor = useEntityEditor<DictionaryItem, ReturnType<typeof dictionaryItemInput>>({
34
+ create: api.createItem,
35
+ update: api.updateItem,
36
+ remove: api.removeItem,
37
+ toInput: dictionaryItemInput,
38
+ reload: state.load,
39
+ });
40
+ const columns: SchemaTableColumn<DictionaryItem>[] = [
41
+ { field: 'label', label: '显示名称', minWidth: 160 },
42
+ { field: 'value', label: '字典值', minWidth: 150 },
43
+ { field: 'typeCode', label: '分类', minWidth: 150 },
44
+ { field: 'color', label: '颜色', width: 120 },
45
+ { field: 'order', label: '排序', width: 80 },
46
+ { field: 'status', label: '状态', options: statusOptions, width: 100 },
47
+ ];
48
+ const schemas = computed<SchemaFormItem[]>(() => [
49
+ {
50
+ field: 'typeCode',
51
+ label: '分类',
52
+ component: 'select',
53
+ required: true,
54
+ options: types.data.value.map((row) => ({ label: row.name, value: row.code })),
55
+ },
56
+ { field: 'label', label: '显示名称', required: true },
57
+ { field: 'value', label: '字典值', required: true },
58
+ { field: 'color', label: '颜色' },
59
+ { field: 'order', label: '排序', component: 'number', defaultValue: 0 },
60
+ { field: 'status', label: '状态', component: 'select', options: statusOptions, defaultValue: 'enabled' },
61
+ ]);
62
+ </script>
63
+
64
+ <template>
65
+ <LumalPage class="lumal-admin-page" title="字典项管理" no-padding>
66
+ <ElAlert
67
+ v-if="editor.error.value || types.error.value"
68
+ :title="editor.error.value || types.error.value"
69
+ type="error"
70
+ :closable="false"
71
+ >
72
+ <ElButton v-if="types.error.value" link @click="types.reload"> 重试分类加载 </ElButton>
73
+ </ElAlert>
74
+ <LumalSchemaTable
75
+ :rows="state.data.value"
76
+ :columns="columns"
77
+ :loading="state.loading.value"
78
+ :error="state.error.value"
79
+ :action-width="160"
80
+ @retry="state.reload"
81
+ >
82
+ <template #toolbar>
83
+ <ElSelect v-model="typeCode" clearable aria-label="字典分类" placeholder="全部分类" style="width: 240px">
84
+ <ElOption v-for="type in types.data.value" :key="type.id" :label="type.name" :value="type.code" />
85
+ </ElSelect>
86
+ <ElButton
87
+ v-if="can('create')"
88
+ type="primary"
89
+ :disabled="!!types.error.value || !types.data.value.length"
90
+ @click="
91
+ editor.openCreate({
92
+ typeCode: typeCode || types.data.value[0]?.code,
93
+ status: 'enabled',
94
+ order: 0,
95
+ color: '',
96
+ })
97
+ "
98
+ >
99
+ 新增字典项
100
+ </ElButton>
101
+ </template>
102
+ <template #toolbar-tools>
103
+ <ElButton title="重新加载" aria-label="重新加载" @click="state.reload">
104
+ <LumalIcon name="lumal:refresh" />
105
+ </ElButton>
106
+ </template>
107
+ <template #table-color="{ value }">
108
+ <span :style="{ color: String(value || 'inherit') }">{{ value || '-' }}</span>
109
+ </template>
110
+ <template #actions="{ row }">
111
+ <ElButton v-if="can('update')" link @click="editor.openEdit(parseItem(row))"> 编辑 </ElButton>
112
+ <ElButton
113
+ v-if="can('delete')"
114
+ link
115
+ type="danger"
116
+ :disabled="editor.saving.value"
117
+ @click="editor.remove(parseItem(row))"
118
+ >
119
+ 删除
120
+ </ElButton>
121
+ </template>
122
+ </LumalSchemaTable>
123
+ <BaseEntityDialog
124
+ v-model:visible="editor.visible.value"
125
+ v-model:model="editor.model.value"
126
+ :title="editor.editing.value ? '编辑字典项' : '新增字典项'"
127
+ :schemas="schemas"
128
+ :editing="!!editor.editing.value"
129
+ :saving="editor.saving.value"
130
+ :error="editor.error.value"
131
+ @submit="editor.submit"
132
+ >
133
+ <template #field-color="{ value }">
134
+ <ElColorPicker
135
+ :model-value="String(value || '')"
136
+ @update:model-value="editor.model.value = { ...editor.model.value, color: $event || '' }"
137
+ />
138
+ </template>
139
+ </BaseEntityDialog>
140
+ </LumalPage>
141
+ </template>
@@ -0,0 +1,107 @@
1
+ <script setup lang="ts">
2
+ import type { SchemaFormItem, SchemaTableColumn } from '@lumal/core/components';
3
+ import type { DictionaryType } from './types';
4
+ import { LumalPage, LumalSchemaTable } from '@lumal/core/components';
5
+ import { LumalIcon } from '@lumal/icons-vue';
6
+ import { ElAlert, ElButton, ElInput } from 'element-plus';
7
+ import { computed, shallowRef } from 'vue';
8
+ import { useRouter } from 'vue-router';
9
+ import { permissionStore, service } from '../../../app/services/session';
10
+ import BaseEntityDialog from '../../../shared/components/BaseEntityDialog.vue';
11
+ import { useAsyncData } from '../../../shared/composables/use-async-data';
12
+ import { useEntityEditor } from '../../../shared/composables/use-entity-editor';
13
+ import { statusOptions } from '../../../shared/status';
14
+ import { createDictionaryService, dictionaryTypeInput, parseType } from './api';
15
+ import { dictionaryStore } from './cache';
16
+
17
+ /***********************字典分类与关联项入口*********************/
18
+ const api = createDictionaryService(service, () => dictionaryStore.clearDictionary());
19
+ const state = useAsyncData(api.types, [] as DictionaryType[]);
20
+ const keyword = shallowRef('');
21
+ const router = useRouter();
22
+ const can = (action: string) => permissionStore.hasPermission(`system:dict:${action}`);
23
+ const displayed = computed(() => state.data.value.filter((row) => `${row.name} ${row.code}`.includes(keyword.value)));
24
+ const editor = useEntityEditor<DictionaryType, ReturnType<typeof dictionaryTypeInput>>({
25
+ create: api.createType,
26
+ update: api.updateType,
27
+ remove: api.removeType,
28
+ toInput: dictionaryTypeInput,
29
+ reload: state.load,
30
+ removeMessage: (row) => `删除分类「${row.name}」及其所有字典项?`,
31
+ });
32
+ const columns: SchemaTableColumn<DictionaryType>[] = [
33
+ { field: 'name', label: '字典名称', minWidth: 160 },
34
+ { field: 'code', label: '编码', minWidth: 150 },
35
+ { field: 'status', label: '状态', options: statusOptions, width: 100 },
36
+ { field: 'description', label: '说明', minWidth: 200 },
37
+ ];
38
+ const schemas: SchemaFormItem[] = [
39
+ { field: 'name', label: '字典名称', required: true },
40
+ {
41
+ field: 'code',
42
+ label: '编码',
43
+ required: true,
44
+ editDisabled: true,
45
+ rules: [{ pattern: /^[a-z][\w-]*$/, message: '使用字母开头的编码' }],
46
+ },
47
+ { field: 'status', label: '状态', component: 'select', options: statusOptions, defaultValue: 'enabled' },
48
+ { field: 'description', label: '说明', component: 'textarea', span: 24 },
49
+ ];
50
+ </script>
51
+
52
+ <template>
53
+ <LumalPage class="lumal-admin-page" title="字典分类" no-padding>
54
+ <ElAlert v-if="editor.error.value" :title="editor.error.value" type="error" :closable="false" />
55
+ <LumalSchemaTable
56
+ :rows="displayed"
57
+ :columns="columns"
58
+ :loading="state.loading.value"
59
+ :error="state.error.value"
60
+ :action-width="240"
61
+ @retry="state.reload"
62
+ >
63
+ <template #toolbar>
64
+ <ElInput
65
+ v-model="keyword"
66
+ aria-label="搜索字典分类"
67
+ placeholder="搜索名称或编码"
68
+ clearable
69
+ style="max-width: 260px"
70
+ />
71
+ <ElButton v-if="can('create')" type="primary" @click="editor.openCreate({ status: 'enabled' })">
72
+ 新增分类
73
+ </ElButton>
74
+ </template>
75
+ <template #toolbar-tools>
76
+ <ElButton title="重新加载" aria-label="重新加载" @click="state.reload">
77
+ <LumalIcon name="lumal:refresh" />
78
+ </ElButton>
79
+ </template>
80
+ <template #actions="{ row }">
81
+ <ElButton link @click="router.push({ path: '/system/dictionaries/items', query: { typeCode: row.code } })">
82
+ 字典项
83
+ </ElButton>
84
+ <ElButton v-if="can('update')" link @click="editor.openEdit(parseType(row))"> 编辑 </ElButton>
85
+ <ElButton
86
+ v-if="can('delete')"
87
+ link
88
+ type="danger"
89
+ :disabled="editor.saving.value"
90
+ @click="editor.remove(parseType(row))"
91
+ >
92
+ 删除
93
+ </ElButton>
94
+ </template>
95
+ </LumalSchemaTable>
96
+ <BaseEntityDialog
97
+ v-model:visible="editor.visible.value"
98
+ v-model:model="editor.model.value"
99
+ :title="editor.editing.value ? '编辑分类' : '新增分类'"
100
+ :schemas="schemas"
101
+ :editing="!!editor.editing.value"
102
+ :saving="editor.saving.value"
103
+ :error="editor.error.value"
104
+ @submit="editor.submit"
105
+ />
106
+ </LumalPage>
107
+ </template>
@@ -0,0 +1,61 @@
1
+ import type { SchemaFormModel } from '@lumal/core/components';
2
+ import type { BusinessTransport } from '../../../app/services/contracts';
3
+ import type { DictionaryItem, DictionaryItemInput, DictionaryType, DictionaryTypeInput } from './types';
4
+ import { formStatus, integer, object, rows, text } from '../../../shared/api';
5
+
6
+ export function dictionaryTypeInput(model: SchemaFormModel): DictionaryTypeInput {
7
+ return {
8
+ name: text(model.name, '字典名称', true),
9
+ code: text(model.code, '字典编码', true),
10
+ description: text(model.description, '说明'),
11
+ status: formStatus(model),
12
+ };
13
+ }
14
+ export function dictionaryItemInput(model: SchemaFormModel): DictionaryItemInput {
15
+ const color = text(model.color, '颜色');
16
+ if (color && !/^#[\da-f]{6}$/i.test(color)) throw new Error('颜色必须是六位十六进制值');
17
+ return {
18
+ typeCode: text(model.typeCode, '字典分类', true),
19
+ label: text(model.label, '显示名称', true),
20
+ value: text(model.value, '字典值', true),
21
+ color,
22
+ order: integer(model.order ?? 0),
23
+ status: formStatus(model),
24
+ };
25
+ }
26
+ export function parseType(value: unknown): DictionaryType {
27
+ const raw = object(value);
28
+ return { ...dictionaryTypeInput(raw), id: text(raw.id, '字典 ID', true) };
29
+ }
30
+ export function parseItem(value: unknown): DictionaryItem {
31
+ const raw = object(value);
32
+ return { ...dictionaryItemInput(raw), id: text(raw.id, '字典项 ID', true) };
33
+ }
34
+ export function createDictionaryService(transport: BusinessTransport, invalidate: () => void = () => {}) {
35
+ async function mutation<T>(
36
+ method: 'POST' | 'PUT' | 'DELETE',
37
+ path: string,
38
+ input: object | undefined,
39
+ parse: (value: unknown) => T,
40
+ ) {
41
+ const result = await transport.request(method, path, { body: input });
42
+ // 先使旧请求失效再刷新消费者,防止旧字典响应回填。
43
+ invalidate();
44
+ return parse(result);
45
+ }
46
+ return {
47
+ types: async () => rows(await transport.request('GET', '/system/dictionaries/types'), parseType),
48
+ items: async (typeCode: string) =>
49
+ rows(await transport.request('GET', '/system/dictionaries/items', { query: { typeCode } }), parseItem),
50
+ createType: (input: DictionaryTypeInput) => mutation('POST', '/system/dictionaries/types', input, parseType),
51
+ updateType: (id: string, input: DictionaryTypeInput) =>
52
+ mutation('PUT', `/system/dictionaries/types/${encodeURIComponent(id)}`, input, parseType),
53
+ removeType: (id: string) =>
54
+ mutation('DELETE', `/system/dictionaries/types/${encodeURIComponent(id)}`, undefined, () => undefined),
55
+ createItem: (input: DictionaryItemInput) => mutation('POST', '/system/dictionaries/items', input, parseItem),
56
+ updateItem: (id: string, input: DictionaryItemInput) =>
57
+ mutation('PUT', `/system/dictionaries/items/${encodeURIComponent(id)}`, input, parseItem),
58
+ removeItem: (id: string) =>
59
+ mutation('DELETE', `/system/dictionaries/items/${encodeURIComponent(id)}`, undefined, () => undefined),
60
+ };
61
+ }
@@ -0,0 +1,9 @@
1
+ import { createDictionaryStore } from '@lumal/core/dictionary';
2
+ import { application, service } from '../../../app/services/session';
3
+
4
+ export const dictionaryStore = createDictionaryStore({
5
+ fetcher: async (code) => ({
6
+ items: await service.request('GET', `/system/dictionaries/options/${encodeURIComponent(code)}`),
7
+ }),
8
+ });
9
+ application.onReset(() => dictionaryStore.clearDictionary());
@@ -0,0 +1,32 @@
1
+ import type { AppModule } from '../../../app/module';
2
+
3
+ export const menus: AppModule['menus'] = [
4
+ {
5
+ path: '/system/dictionaries/types',
6
+ name: 'DictionaryTypes',
7
+ component: 'system/dictionaries/types',
8
+ meta: { title: '字典分类', authority: ['system:dict:list'], keepAlive: true, order: 5 },
9
+ },
10
+ {
11
+ path: '/system/dictionaries/items',
12
+ name: 'DictionaryItems',
13
+ component: 'system/dictionaries/items',
14
+ meta: { title: '字典项管理', authority: ['system:dict:list'], keepAlive: true, order: 6 },
15
+ },
16
+ ];
17
+ export const views: AppModule['views'] = {
18
+ 'system/dictionaries/types': () => import('./DictionaryTypeView.vue'),
19
+ 'system/dictionaries/items': () => import('./DictionaryItemView.vue'),
20
+ };
21
+ export const permissions: AppModule['permissions'] = [
22
+ {
23
+ id: 'system-dictionary',
24
+ label: '字典管理',
25
+ children: [
26
+ { id: 'system:dict:list', label: '查询字典', permission: 'system:dict:list' },
27
+ { id: 'system:dict:create', label: '新增字典', permission: 'system:dict:create' },
28
+ { id: 'system:dict:update', label: '修改字典', permission: 'system:dict:update' },
29
+ { id: 'system:dict:delete', label: '删除字典', permission: 'system:dict:delete' },
30
+ ],
31
+ },
32
+ ];
@@ -0,0 +1,22 @@
1
+ import type { Status } from '../../../shared/api';
2
+
3
+ export interface DictionaryTypeInput {
4
+ name: string;
5
+ code: string;
6
+ description: string;
7
+ status: Status;
8
+ }
9
+ export interface DictionaryType extends DictionaryTypeInput {
10
+ id: string;
11
+ }
12
+ export interface DictionaryItemInput {
13
+ typeCode: string;
14
+ label: string;
15
+ value: string;
16
+ color: string;
17
+ order: number;
18
+ status: Status;
19
+ }
20
+ export interface DictionaryItem extends DictionaryItemInput {
21
+ id: string;
22
+ }