@liujitcn/kratos-admin-core 0.0.65 → 0.0.67

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 (59) hide show
  1. package/dist/package/declarations/src/api/base/v1/login.d.ts +1 -3
  2. package/dist/package/declarations/src/api/base/v1/secret_crypto.d.ts +9 -0
  3. package/dist/package/declarations/src/components/CodeBlock/index.vue.d.ts +15 -0
  4. package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +3 -7
  5. package/dist/package/declarations/src/components/ProForm/components/ProFormItem.vue.d.ts +6 -6
  6. package/dist/package/declarations/src/components/ProForm/components/SecretInput.vue.d.ts +23 -0
  7. package/dist/package/declarations/src/components/ProForm/interface/index.d.ts +14 -1
  8. package/dist/package/declarations/src/components/YamlEditor/index.vue.d.ts +7 -1
  9. package/dist/package/declarations/src/locales/generated.d.ts +388 -0
  10. package/dist/package/declarations/src/rpc/base/v1/login.d.ts +2 -39
  11. package/dist/package/declarations/src/rpc/base/v1/mfa.d.ts +3 -3
  12. package/dist/package/declarations/src/rpc/base/v1/oauth.d.ts +2 -2
  13. package/dist/package/declarations/src/rpc/base/v1/secret_crypto.d.ts +37 -0
  14. package/dist/package/declarations/src/rpc/common/v1/types.d.ts +4 -4
  15. package/dist/package/declarations/src/rpc/system/admin/v1/auth.d.ts +3 -3
  16. package/dist/package/declarations/src/security.d.ts +1 -1
  17. package/dist/package/declarations/src/utils/secretCrypto.d.ts +19 -0
  18. package/dist/package/declarations/src/views/demo/components/DemoDetailDrawer.vue.d.ts +15 -0
  19. package/dist/package/declarations/src/views/demo/components/DemoFormDialog.vue.d.ts +69 -0
  20. package/dist/package/declarations/src/views/demo/demo-data.d.ts +145 -0
  21. package/dist/package/declarations/src/views/demo/table/index.vue.d.ts +3 -0
  22. package/dist/package/declarations/src/views/demo/tree-grid/index.vue.d.ts +3 -0
  23. package/dist/package/declarations/src/views/demo/tree-table/index.vue.d.ts +3 -0
  24. package/dist/package/src/api/base/v1/login.ts +0 -12
  25. package/dist/package/src/api/base/v1/secret_crypto.ts +35 -0
  26. package/dist/package/src/components/CodeBlock/index.vue +156 -0
  27. package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +4 -8
  28. package/dist/package/src/components/ProForm/components/ProFormItem.vue +29 -2
  29. package/dist/package/src/components/ProForm/components/SecretInput.vue +117 -0
  30. package/dist/package/src/components/ProForm/interface/index.ts +16 -0
  31. package/dist/package/src/components/YamlEditor/index.vue +103 -7
  32. package/dist/package/src/locales/README.md +1 -1
  33. package/dist/package/src/locales/en-US.json +98 -1
  34. package/dist/package/src/locales/ja-JP.json +98 -1
  35. package/dist/package/src/locales/zh-CN.json +98 -1
  36. package/dist/package/src/locales/zh-TW.json +98 -1
  37. package/dist/package/src/modules/kratosAdmin.ts +11 -1
  38. package/dist/package/src/rpc/base/v1/login.ts +2 -42
  39. package/dist/package/src/rpc/base/v1/mfa.ts +3 -3
  40. package/dist/package/src/rpc/base/v1/oauth.ts +2 -2
  41. package/dist/package/src/rpc/base/v1/secret_crypto.ts +49 -0
  42. package/dist/package/src/rpc/common/v1/types.ts +4 -4
  43. package/dist/package/src/rpc/system/admin/v1/auth.ts +3 -3
  44. package/dist/package/src/security.ts +1 -1
  45. package/dist/package/src/utils/index.ts +2 -1
  46. package/dist/package/src/utils/request.ts +5 -4
  47. package/dist/package/src/utils/{passwordCrypto.ts → secretCrypto.ts} +112 -50
  48. package/dist/package/src/views/demo/components/DemoDetailDrawer.vue +108 -0
  49. package/dist/package/src/views/demo/components/DemoFormDialog.vue +470 -0
  50. package/dist/package/src/views/demo/demo-data.ts +279 -0
  51. package/dist/package/src/views/demo/table/index.vue +391 -0
  52. package/dist/package/src/views/demo/tree-grid/index.vue +272 -0
  53. package/dist/package/src/views/demo/tree-table/index.vue +346 -0
  54. package/dist/package/src/views/login/components/LoginForm.vue +2 -2
  55. package/dist/package/types/generated/components.d.ts +4 -1
  56. package/package.json +14 -2
  57. package/src/runtime-messages.ts +39 -0
  58. package/types/generated/components.d.ts +4 -1
  59. package/dist/package/declarations/src/utils/passwordCrypto.d.ts +0 -6
@@ -0,0 +1,272 @@
1
+ <!--
2
+ Demo 页面三:树形表格(行内树 + 静态 data 直灌)。
3
+
4
+ 与“左树右表”页的区别:
5
+ - 左树右表 = 左侧独立的树控件过滤右侧平铺表格;
6
+ - 本页 = 表格行自身带层级(children 嵌套),点行首箭头逐级展开。
7
+
8
+ 本页演示要点:
9
+ - ProTable 传静态 data(不传 requestApi 即不走请求流),pagination 关闭后
10
+ processTableData 会原样返回 data,树形结构不会被分页截断;
11
+ - tree-props / default-expand-all / indent / expand-row-keys 等属性通过
12
+ v-bind="$attrs" 直接透传给 el-table(见 ProTable/index.vue 表格主体);
13
+ - 状态列改用 tag: true + enum 展示(区别于状态开关列和纯标签列);
14
+ - 操作列 params 按行动态生成,演示“新增子级”按钮把父行 ID 带进表单。
15
+
16
+ 参照真实页面:system/base/dept/index.vue、system/base/menu/index.vue。
17
+ -->
18
+ <template>
19
+ <div class="table-box">
20
+ <ProTable
21
+ :key="expandAll ? 'expand' : 'collapse'"
22
+ ref="proTable"
23
+ row-key="id"
24
+ :indent="20"
25
+ :columns="columns"
26
+ :header-actions="headerActions"
27
+ :data="catalogList"
28
+ :pagination="false"
29
+ :default-expand-all="expandAll"
30
+ :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
31
+ />
32
+
33
+ <DemoFormDialog ref="formDialogRef" @submit="handleFormSubmit" />
34
+ <DemoDetailDrawer v-model="detailVisible" :detail="detailModel" />
35
+ </div>
36
+ </template>
37
+
38
+ <script setup lang="ts">
39
+ import { computed, ref } from "vue";
40
+ import { ElMessage, ElMessageBox } from "element-plus";
41
+ import { CirclePlus, Delete, EditPen, Plus, View } from "@element-plus/icons-vue";
42
+ import type { ColumnProps, HeaderActionProps, ProTableInstance } from "../../../components/ProTable/interface";
43
+ import ProTable from "../../../components/ProTable";
44
+ import { t } from "../../../locales";
45
+
46
+ import DemoFormDialog from "../components/DemoFormDialog.vue";
47
+ import DemoDetailDrawer from "../components/DemoDetailDrawer.vue";
48
+ import type { DemoCatalog, DemoDetailModel } from "../demo-data";
49
+ import { STATUS_DISABLE, STATUS_ENABLE, demoCatalogTree } from "../demo-data";
50
+
51
+ defineOptions({
52
+ name: "DemoTreeGrid",
53
+ inheritAttrs: false
54
+ });
55
+
56
+ const proTable = ref<ProTableInstance>();
57
+ const formDialogRef = ref<InstanceType<typeof DemoFormDialog>>();
58
+ const detailVisible = ref(false);
59
+ const detailModel = ref<DemoDetailModel | null>(null);
60
+
61
+ /**
62
+ * 目录树本地数据副本:新增子级/删除在这份副本上递归操作。
63
+ * 真实项目由后端返回树形列表,前端只负责展示。
64
+ */
65
+ const catalogList = ref<DemoCatalog[]>(structuredClone(demoCatalogTree));
66
+
67
+ /** 是否默认展开全部节点:工具按钮切换,透传给 el-table 的 default-expand-all。 */
68
+ const expandAll = ref(false);
69
+
70
+ /** 状态枚举选项:tag 形态展示(success/info 两色)。 */
71
+ const statusTagOptions = [
72
+ { label: t("common.status.enabled"), value: STATUS_ENABLE, tagType: "success" },
73
+ { label: t("common.status.disabled"), value: STATUS_DISABLE, tagType: "info" }
74
+ ];
75
+
76
+ /**
77
+ * 表格列配置:首列 name 即树形展开列(el-table 会自动在该列前加展开箭头)。
78
+ */
79
+ const columns = computed<ColumnProps[]>(() => [
80
+ { prop: "name", label: t("core.demo.grid.field.name"), minWidth: 260 },
81
+ { prop: "code", label: t("core.demo.grid.field.code"), minWidth: 160 },
82
+ { prop: "owner", label: t("core.demo.table.field.owner"), minWidth: 100 },
83
+ {
84
+ // 状态列:tag: true + enum,按枚举 tagType 渲染标签
85
+ prop: "status",
86
+ label: t("common.field.status"),
87
+ minWidth: 90,
88
+ align: "center",
89
+ tag: true,
90
+ enum: statusTagOptions
91
+ },
92
+ { prop: "doc_count", label: t("core.demo.grid.field.doc_count"), minWidth: 100, align: "right" },
93
+ {
94
+ // 操作列:params 动态携带父行 ID,演示“新增子级”
95
+ prop: "operation",
96
+ label: t("common.field.operation"),
97
+ align: "center",
98
+ cellType: "actions",
99
+ actions: [
100
+ {
101
+ label: t("common.action.detail"),
102
+ type: "primary",
103
+ link: true,
104
+ icon: View,
105
+ onClick: scope => handleOpenDetail(scope.row as DemoCatalog)
106
+ },
107
+ {
108
+ label: t("core.demo.grid.action.create_child"),
109
+ type: "success",
110
+ link: true,
111
+ icon: Plus,
112
+ params: scope => ({ parentId: scope.row.id }),
113
+ onClick: (scope, params) => handleCreateChild((scope.row as DemoCatalog).id ?? (params?.parentId as number))
114
+ },
115
+ {
116
+ label: t("common.action.edit"),
117
+ type: "primary",
118
+ link: true,
119
+ icon: EditPen,
120
+ onClick: scope => handleEdit(scope.row as DemoCatalog)
121
+ },
122
+ {
123
+ label: t("common.action.delete"),
124
+ type: "danger",
125
+ link: true,
126
+ icon: Delete,
127
+ onClick: scope => handleDelete(scope.row as DemoCatalog)
128
+ }
129
+ ]
130
+ }
131
+ ]);
132
+
133
+ /** 顶部按钮:新增根级目录 + 展开状态切换。 */
134
+ const headerActions = computed<HeaderActionProps[]>(() => [
135
+ {
136
+ label: t("common.action.create"),
137
+ type: "success",
138
+ icon: CirclePlus,
139
+ onClick: () => handleCreateChild(undefined)
140
+ },
141
+ {
142
+ // default-expand-all 非响应式,切换后通过 :key 重建表格使其生效
143
+ label: expandAll.value ? t("core.demo.grid.action.collapse_all") : t("core.demo.grid.action.expand_all"),
144
+ type: "primary",
145
+ onClick: () => {
146
+ expandAll.value = !expandAll.value;
147
+ }
148
+ }
149
+ ]);
150
+
151
+ /** 在静态树中定位节点:返回节点对象与其父容器数组。 */
152
+ function findNode(nodes: DemoCatalog[], id: number, parent: DemoCatalog[] | null): { node: DemoCatalog; siblings: DemoCatalog[] } | undefined {
153
+ for (const node of nodes) {
154
+ if (node.id === id) return { node, siblings: parent ?? nodes };
155
+ const found = node.children ? findNode(node.children, id, node.children) : undefined;
156
+ if (found) return found;
157
+ }
158
+ return undefined;
159
+ }
160
+
161
+ /** 追加计数器:保证静态演示新增的节点 ID 唯一。 */
162
+ let nextId = 900;
163
+
164
+ /** 当前待挂载的父节点 ID,undefined 表示新增根级目录。 */
165
+ const pendingParentId = ref<number>();
166
+
167
+ /**
168
+ * 新增目录:记录待挂载父级后打开表单弹窗,
169
+ * 提交回调 handleFormSubmit 里真正把节点插入本地树。
170
+ */
171
+ function handleCreateChild(parentId?: number) {
172
+ pendingParentId.value = parentId;
173
+ formDialogRef.value?.open();
174
+ }
175
+
176
+ /**
177
+ * 表单提交回调:把新目录插入本地树(根级或父级 children 末尾)。
178
+ * 真实项目调用 CreateXxx 成功后重新拉取树形列表。
179
+ */
180
+ function handleFormSubmit(data: Record<string, any>) {
181
+ const node: DemoCatalog = {
182
+ id: ++nextId,
183
+ name: String(data.name || t("core.demo.tree.new_dir", { id: nextId })),
184
+ code: `demo-${nextId}`,
185
+ owner: data.visibility === "private" ? "--" : "Demo",
186
+ status: data.enabled ? STATUS_ENABLE : STATUS_DISABLE,
187
+ doc_count: Number(data.count) || 0
188
+ };
189
+ const parent = pendingParentId.value ? findNode(catalogList.value, pendingParentId.value, null)?.node : undefined;
190
+ if (parent) {
191
+ parent.children = [...(parent.children ?? []), node];
192
+ } else {
193
+ catalogList.value = [...catalogList.value, node];
194
+ }
195
+ pendingParentId.value = undefined;
196
+ }
197
+
198
+ /** 打开编辑弹窗:回填行数据。 */
199
+ function handleEdit(row: DemoCatalog) {
200
+ formDialogRef.value?.open({ name: row.name, count: row.doc_count });
201
+ }
202
+
203
+ /**
204
+ * 删除目录:存在子级时阻止删除并提示(与真实页面的引用检查行为一致),
205
+ * 否则确认后从本地树中递归移除。
206
+ */
207
+ function handleDelete(row: DemoCatalog) {
208
+ if (row.children?.length) {
209
+ ElMessage.warning(t("core.demo.grid.message.has_children"));
210
+ return;
211
+ }
212
+ ElMessageBox.confirm(
213
+ `${t("common.dialog.delete_single", { resource: t("core.demo.resource.doc_catalog") })}\n${t("common.dialog.resource_field", {
214
+ field: t("core.demo.grid.field.name"),
215
+ value: row.name
216
+ })}`,
217
+ t("common.title.warning"),
218
+ { confirmButtonText: t("common.action.confirm"), cancelButtonText: t("common.action.cancel"), type: "warning" }
219
+ ).then(
220
+ () => {
221
+ const remove = (nodes: DemoCatalog[]): DemoCatalog[] =>
222
+ nodes.filter(node => {
223
+ if (node.children?.length) node.children = remove(node.children);
224
+ return node.id !== row.id;
225
+ });
226
+ catalogList.value = remove([...catalogList.value]);
227
+ ElMessage.success(t("common.message.delete_success", { resource: t("core.demo.resource.doc_catalog") }));
228
+ },
229
+ () => ElMessage.info(t("common.dialog.cancel_delete", { resource: t("core.demo.resource.doc_catalog") }))
230
+ );
231
+ }
232
+
233
+ /** 组装详情模型并打开抽屉:附加“层级路径”字段演示树形数据溯源。 */
234
+ function handleOpenDetail(row: DemoCatalog) {
235
+ const path: string[] = [];
236
+ const walk = (nodes: DemoCatalog[], trail: string[]) => {
237
+ for (const node of nodes) {
238
+ const next = [...trail, node.name];
239
+ if (node.id === row.id) path.push(...next);
240
+ if (node.children?.length) walk(node.children, next);
241
+ }
242
+ };
243
+ walk(catalogList.value, []);
244
+ detailModel.value = {
245
+ title: `${t("core.demo.resource.doc_catalog")} · ${row.name}`,
246
+ sections: [
247
+ {
248
+ title: t("core.demo.detail.section.basic"),
249
+ fields: [
250
+ { label: t("core.demo.grid.field.name"), value: row.name },
251
+ { label: t("core.demo.grid.field.code"), value: row.code },
252
+ { label: t("core.demo.table.field.owner"), value: row.owner },
253
+ { label: t("common.field.status"), value: row.status === STATUS_ENABLE ? t("common.status.enabled") : t("common.status.disabled"), tag: true, tagType: row.status === STATUS_ENABLE ? "success" : "info" },
254
+ { label: t("core.demo.grid.field.doc_count"), value: String(row.doc_count) },
255
+ { label: t("core.demo.grid.field.path"), value: path.join(" / ") || row.name }
256
+ ]
257
+ }
258
+ ],
259
+ steps: [
260
+ { title: t("core.demo.detail.step.create"), description: "--" },
261
+ { title: t("core.demo.detail.step.review"), description: "--" },
262
+ { title: t("core.demo.detail.step.publish"), description: "--" }
263
+ ],
264
+ timeline: [
265
+ { title: t("core.demo.detail.timeline.created"), timestamp: "2025-05-06 10:00:00", type: "primary" },
266
+ { title: t("core.demo.detail.timeline.updated"), timestamp: "2026-01-02 15:40:00", type: "success" }
267
+ ],
268
+ raw: { ...row }
269
+ };
270
+ detailVisible.value = true;
271
+ }
272
+ </script>
@@ -0,0 +1,346 @@
1
+ <!--
2
+ Demo 页面一:左树右表(TreeFilter + ProTable 经典布局)。
3
+
4
+ 布局说明:
5
+ - .main-box 是 core 全局样式(packages/core/src/styles/element.scss)定义的横向布局容器,
6
+ 左侧 TreeFilter 自适应宽度,右侧 .table-box 撑满剩余空间;
7
+ - 左树通过 @change 把选中的部门写入 initParam,触发右侧表格重新请求(本页为本地静态过滤);
8
+ - 参照真实页面:system/base/user/index.vue(部门树 + 用户表)。
9
+
10
+ 本页演示要点:
11
+ - TreeFilter 传入静态 data(不传 requestApi 即不请求接口)+ 默认插槽自定义节点内容;
12
+ - ProTable 单选列(type: "radio")、图片列(cellType: "image")、金额列(cellType: "money")、
13
+ 状态开关列(cellType: "status" + beforeChange 确认);
14
+ - requestApi 返回 Promise 模拟接口分页,真实项目替换为 RPC + buildPageRequest。
15
+ -->
16
+ <template>
17
+ <div class="main-box">
18
+ <!-- 左侧部门树:静态数据 + 自定义节点插槽(展示部门人数) -->
19
+ <TreeFilter
20
+ label="name"
21
+ :title="t('core.demo.tree.title.dept_tree')"
22
+ :data="demoDeptTree"
23
+ :default-value="deptFilterValue"
24
+ @change="handleDeptChange"
25
+ >
26
+ <!-- 默认插槽:TreeFilter 以 { row } 透传 el-tree 作用域,row.data 为原始节点数据 -->
27
+ <template #default="scope">
28
+ <span>{{ scope.row.data.name }}</span>
29
+ <el-tag class="dept-count" size="small" effect="plain" round>{{ scope.row.data.count }}</el-tag>
30
+ </template>
31
+ </TreeFilter>
32
+
33
+ <div class="table-box">
34
+ <ProTable
35
+ ref="proTable"
36
+ row-key="id"
37
+ :columns="columns"
38
+ :header-actions="headerActions"
39
+ :request-api="requestEmployeeTable"
40
+ :init-param="initParam"
41
+ />
42
+
43
+ <!-- 共享全组件表单弹窗:新增/编辑入口 -->
44
+ <DemoFormDialog ref="formDialogRef" />
45
+
46
+ <!-- 共享详情抽屉:详情入口 -->
47
+ <DemoDetailDrawer v-model="detailVisible" :detail="detailModel" />
48
+ </div>
49
+ </div>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { computed, reactive, ref } from "vue";
54
+ import { ElMessage, ElMessageBox } from "element-plus";
55
+ import { CirclePlus, Delete, EditPen, View } from "@element-plus/icons-vue";
56
+ import type { ColumnProps, HeaderActionProps, ProTableInstance } from "../../../components/ProTable/interface";
57
+ import ProTable from "../../../components/ProTable";
58
+ import TreeFilter from "../../../components/TreeFilter/index.vue";
59
+ import type { ProFormOption } from "../../../components/ProForm/interface";
60
+ import { t } from "../../../locales";
61
+
62
+ import DemoFormDialog from "../components/DemoFormDialog.vue";
63
+ import DemoDetailDrawer from "../components/DemoDetailDrawer.vue";
64
+ import type { DemoDetailModel, DemoDeptNode, DemoEmployee } from "../demo-data";
65
+ import { STATUS_DISABLE, STATUS_ENABLE, demoDeptTree, demoEmployees } from "../demo-data";
66
+
67
+ defineOptions({
68
+ name: "DemoTreeTable",
69
+ inheritAttrs: false
70
+ });
71
+
72
+ /** 左树选中的部门回显值(“全部”节点 id 为空字符串)。 */
73
+ const deptFilterValue = ref("");
74
+ /** 右侧表格的联动参数:树节点切换后触发表格重新请求。 */
75
+ const initParam = reactive({
76
+ dept_id: undefined as number | undefined
77
+ });
78
+
79
+ const proTable = ref<ProTableInstance>();
80
+ const formDialogRef = ref<InstanceType<typeof DemoFormDialog>>();
81
+ const detailVisible = ref(false);
82
+ const detailModel = ref<DemoDetailModel | null>(null);
83
+
84
+ /**
85
+ * 员工本地数据副本:删除/状态切换直接改这份副本。
86
+ * 真实项目中这里不需要副本,直接调用 RPC 后刷新表格即可。
87
+ */
88
+ const employeeList = ref<DemoEmployee[]>([...demoEmployees]);
89
+
90
+ /** 状态枚举选项:搜索下拉与表单回填共用。 */
91
+ const statusOptions: ProFormOption[] = [
92
+ { label: t("common.status.enabled"), value: STATUS_ENABLE },
93
+ { label: t("common.status.disabled"), value: STATUS_DISABLE }
94
+ ];
95
+
96
+ /** 表格列配置:演示 radio 单选列、图片列、金额列、状态开关列与搜索项。 */
97
+ const columns = computed<ColumnProps[]>(() => [
98
+ // type: "radio" 单选列;多选用 "selection",序号/展开/拖拽排序分别对应 index/expand/sort
99
+ { type: "radio", width: 55 },
100
+ { prop: "name", label: t("core.demo.tree.field.name"), minWidth: 120, search: { el: "input", order: 4 } },
101
+ { prop: "email", label: t("core.demo.tree.field.email"), minWidth: 180 },
102
+ { prop: "phone", label: t("core.demo.tree.field.phone"), minWidth: 130, search: { el: "input", order: 3 } },
103
+ {
104
+ // 图片列:cellType: "image",avatar 为 data URL 也能正常预览
105
+ prop: "avatar",
106
+ label: t("core.demo.form.field.avatar"),
107
+ width: 90,
108
+ cellType: "image",
109
+ imageProps: { width: 40, height: 40, previewWidth: 120, previewHeight: 120 }
110
+ },
111
+ {
112
+ // 金额列:cellType: "money",prefix 拼在金额前
113
+ prop: "salary",
114
+ label: t("core.demo.tree.field.salary"),
115
+ minWidth: 120,
116
+ align: "right",
117
+ cellType: "money",
118
+ moneyProps: { prefix: "¥" }
119
+ },
120
+ {
121
+ // 状态开关列:beforeChange 里确认后本地切换,返回 false 则回弹
122
+ prop: "status",
123
+ label: t("common.field.status"),
124
+ minWidth: 100,
125
+ search: { el: "select", enum: statusOptions, order: 2 },
126
+ cellType: "status",
127
+ statusProps: {
128
+ activeValue: STATUS_ENABLE,
129
+ inactiveValue: STATUS_DISABLE,
130
+ activeText: t("common.status.enabled"),
131
+ inactiveText: t("common.status.disabled"),
132
+ beforeChange: scope => handleBeforeSetStatus(scope.row as DemoEmployee)
133
+ }
134
+ },
135
+ {
136
+ prop: "entry_date",
137
+ label: t("core.demo.tree.field.entry_date"),
138
+ minWidth: 120,
139
+ align: "center",
140
+ search: { el: "date-picker", props: { type: "daterange", valueFormat: "YYYY-MM-DD", startPlaceholder: t("common.placeholder.start_date"), endPlaceholder: t("common.placeholder.end_date") }, order: 1 }
141
+ },
142
+ {
143
+ // 操作列:cellType: "actions" + actions 数组,label/icon/type/link 决定按钮外观
144
+ prop: "operation",
145
+ label: t("common.field.operation"),
146
+ align: "center",
147
+ cellType: "actions",
148
+ fixed: "right",
149
+ actions: [
150
+ {
151
+ label: t("common.action.detail"),
152
+ type: "primary",
153
+ link: true,
154
+ icon: View,
155
+ onClick: scope => handleOpenDetail(scope.row as DemoEmployee)
156
+ },
157
+ {
158
+ label: t("common.action.edit"),
159
+ type: "primary",
160
+ link: true,
161
+ icon: EditPen,
162
+ // params 演示:支持按行动态生成,onClick 第二个参数接收
163
+ params: scope => ({ employeeId: scope.row.id }),
164
+ onClick: (scope, params) => handleEdit((scope.row as DemoEmployee).id ?? (params?.employeeId as number))
165
+ },
166
+ {
167
+ label: t("common.action.delete"),
168
+ type: "danger",
169
+ link: true,
170
+ icon: Delete,
171
+ onClick: scope => handleDelete(scope.row as DemoEmployee)
172
+ }
173
+ ]
174
+ }
175
+ ]);
176
+
177
+ /** 顶部按钮:真实项目中用 useAuthButtons 的 BUTTONS 控制可见性,Demo 无权限位直接展示。 */
178
+ const headerActions = computed<HeaderActionProps[]>(() => [
179
+ {
180
+ label: t("common.action.create"),
181
+ type: "success",
182
+ icon: CirclePlus,
183
+ onClick: () => formDialogRef.value?.open()
184
+ }
185
+ ]);
186
+
187
+ /**
188
+ * 切换左侧部门树:写入 initParam 并回到第一页,
189
+ * initParam 变化会自动触发 ProTable 重新请求。
190
+ */
191
+ function handleDeptChange(value: string) {
192
+ deptFilterValue.value = value ?? "";
193
+ initParam.dept_id = value ? Number(value) : undefined;
194
+ if (proTable.value) {
195
+ proTable.value.pageable.page_num = 1;
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 静态模拟分页请求:本地完成部门过滤 + 搜索 + 分页。
201
+ * 真实项目写法对照 system/base/user/index.vue 的 requestBaseUserTable:
202
+ * 先 buildPageRequest(params) 再调用 RPC,最后返回 { data: { list, total } }。
203
+ */
204
+ async function requestEmployeeTable(params: Record<string, any>) {
205
+ // 模拟网络延迟,让 loading 效果可见
206
+ await new Promise(resolve => setTimeout(resolve, 200));
207
+ const pagination = params.pagination ?? {};
208
+ const page = Number(pagination.page_num ?? 1);
209
+ const size = Number(pagination.page_size ?? 10);
210
+ let list = [...employeeList.value];
211
+ // 左树联动:initParam 里的字段会合并进请求参数
212
+ if (initParam.dept_id) {
213
+ list = list.filter(item => item.dept_id === initParam.dept_id);
214
+ }
215
+ // 搜索区参数与列 prop 同名,直接从 params 解构
216
+ if (params.name) list = list.filter(item => item.name.includes(params.name));
217
+ if (params.phone) list = list.filter(item => item.phone.includes(params.phone));
218
+ if (params.status !== undefined && params.status !== null && params.status !== "") {
219
+ list = list.filter(item => item.status === params.status);
220
+ }
221
+ if (Array.isArray(params.entry_date) && params.entry_date.length === 2) {
222
+ const [start, end] = params.entry_date;
223
+ list = list.filter(item => item.entry_date >= start && item.entry_date <= end);
224
+ }
225
+ return { data: { list: list.slice((page - 1) * size, page * size), total: list.length } };
226
+ }
227
+
228
+ /** 状态切换前确认:真实项目在此调用 SetXxxStatus RPC。 */
229
+ async function handleBeforeSetStatus(row: DemoEmployee) {
230
+ const nextStatus = row.status === STATUS_ENABLE ? STATUS_DISABLE : STATUS_ENABLE;
231
+ const action = t(nextStatus === STATUS_ENABLE ? "common.status.enabled" : "common.status.disabled");
232
+ try {
233
+ await ElMessageBox.confirm(
234
+ t("common.dialog.status_change", {
235
+ action,
236
+ resource: t("core.demo.resource.employee"),
237
+ field: t("core.demo.tree.field.name"),
238
+ value: row.name
239
+ }),
240
+ t("common.title.notice"),
241
+ { confirmButtonText: t("common.action.confirm"), cancelButtonText: t("common.action.cancel"), type: "warning" }
242
+ );
243
+ row.status = nextStatus;
244
+ ElMessage.success(t("common.message.status_success", { action }));
245
+ return true;
246
+ } catch {
247
+ return false;
248
+ }
249
+ }
250
+
251
+ /** 打开编辑弹窗:把行数据回填进共享表单(仅回填表单中存在的字段)。 */
252
+ function handleEdit(id: number) {
253
+ const row = employeeList.value.find(item => item.id === id);
254
+ if (!row) return;
255
+ formDialogRef.value?.open({
256
+ name: row.name,
257
+ intro: `${row.name}(${t("core.demo.resource.employee")})`,
258
+ birthday: row.entry_date,
259
+ dept_id: row.dept_id,
260
+ color: "#409eff"
261
+ });
262
+ }
263
+
264
+ /**
265
+ * 组装详情模型并打开抽屉:sections/steps/timeline 均由行数据映射,
266
+ * 标签文案走语言包,记录内容保持原样。
267
+ */
268
+ function handleOpenDetail(row: DemoEmployee) {
269
+ detailModel.value = {
270
+ title: `${t("core.demo.resource.employee")} · ${row.name}`,
271
+ sections: [
272
+ {
273
+ title: t("core.demo.detail.section.basic"),
274
+ fields: [
275
+ { label: t("core.demo.tree.field.name"), value: row.name },
276
+ { label: t("common.field.status"), value: row.status === STATUS_ENABLE ? t("common.status.enabled") : t("common.status.disabled"), tag: true, tagType: row.status === STATUS_ENABLE ? "success" : "info" },
277
+ { label: t("core.demo.tree.field.email"), value: row.email },
278
+ { label: t("core.demo.tree.field.phone"), value: row.phone },
279
+ { label: t("core.demo.tree.field.entry_date"), value: row.entry_date }
280
+ ]
281
+ },
282
+ {
283
+ title: t("core.demo.detail.section.work"),
284
+ fields: [
285
+ { label: t("common.field.department"), value: findDeptName(row.dept_id) },
286
+ { label: t("core.demo.tree.field.salary"), value: `¥ ${(row.salary / 100).toLocaleString()}` }
287
+ ]
288
+ }
289
+ ],
290
+ steps: [
291
+ { title: t("core.demo.detail.step.create"), description: row.entry_date },
292
+ { title: t("core.demo.detail.step.review"), description: "--" },
293
+ { title: t("core.demo.detail.step.publish"), description: "--" }
294
+ ],
295
+ timeline: [
296
+ { title: t("core.demo.detail.timeline.created"), timestamp: row.entry_date, type: "primary" },
297
+ { title: t("core.demo.detail.timeline.updated"), timestamp: "2026-01-15 10:30:00", type: "success" },
298
+ {
299
+ title: row.status === STATUS_ENABLE ? t("core.demo.detail.timeline.enabled") : t("core.demo.detail.timeline.disabled"),
300
+ timestamp: "2026-03-20 16:00:00",
301
+ type: row.status === STATUS_ENABLE ? "success" : "warning"
302
+ }
303
+ ],
304
+ raw: { ...row }
305
+ };
306
+ detailVisible.value = true;
307
+ }
308
+
309
+ /** 删除员工:确认后从本地副本移除并刷新表格。 */
310
+ function handleDelete(row: DemoEmployee) {
311
+ ElMessageBox.confirm(
312
+ `${t("common.dialog.delete_single", { resource: t("core.demo.resource.employee") })}\n${t("common.dialog.resource_field", {
313
+ field: t("core.demo.tree.field.name"),
314
+ value: row.name
315
+ })}`,
316
+ t("common.title.warning"),
317
+ { confirmButtonText: t("common.action.confirm"), cancelButtonText: t("common.action.cancel"), type: "warning" }
318
+ ).then(
319
+ () => {
320
+ employeeList.value = employeeList.value.filter(item => item.id !== row.id);
321
+ ElMessage.success(t("common.message.delete_success", { resource: t("core.demo.resource.employee") }));
322
+ proTable.value?.getTableList();
323
+ },
324
+ () => ElMessage.info(t("common.dialog.cancel_delete", { resource: t("core.demo.resource.employee") }))
325
+ );
326
+ }
327
+
328
+ /** 根据部门 ID 在静态树中查找部门名称(两级即可覆盖 Demo 数据)。 */
329
+ function findDeptName(deptId: number): string {
330
+ for (const dept of demoDeptTree as DemoDeptNode[]) {
331
+ if (Number(dept.id) === deptId) return dept.name;
332
+ for (const child of dept.children ?? []) {
333
+ if (Number(child.id) === deptId) return `${dept.name} / ${child.name}`;
334
+ }
335
+ }
336
+ return "--";
337
+ }
338
+ </script>
339
+
340
+ <style scoped>
341
+ /* 树节点人数徽标:右浮并使用主题间距变量,兼容暗色模式 */
342
+ .dept-count {
343
+ float: right;
344
+ margin-left: 8px;
345
+ }
346
+ </style>
@@ -217,7 +217,7 @@ import { useKeepAliveStore } from "../../../stores/modules/keepAlive";
217
217
  import { initDynamicRouter } from "../../../routers/modules/dynamicRouter";
218
218
  import { isUnmatchedRoute, navigateTo, resolveFrontendRouteURL } from "../../../utils/router";
219
219
  import type { ElForm, FormRules } from "element-plus";
220
- import { PASSWORD_CRYPTO_SCENE, encryptPassword } from "../../../utils/passwordCrypto";
220
+ import { encryptPassword } from "../../../utils/secretCrypto";
221
221
  import { useConfigStore } from "../../../stores/modules/config";
222
222
  import { Click as GoCaptchaClick, Rotate as GoCaptchaRotate, Slide as GoCaptchaSlide } from "go-captcha-vue";
223
223
  import "go-captcha-vue/dist/style.css";
@@ -797,7 +797,7 @@ const verifyCaptchaToken = async (captchaCode: string) => {
797
797
  const submitLogin = async (captchaToken: string) => {
798
798
  loading.value = true;
799
799
  try {
800
- const password = await encryptPassword(loginForm.password, PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_LOGIN);
800
+ const password = await encryptPassword(loginForm.password);
801
801
  const loginRequest: LoginRequest = {
802
802
  tenant_code: configStore.showTenantCode ? loginForm.tenant_code : "0000",
803
803
  user_name: loginForm.user_name,
@@ -19,7 +19,6 @@ declare module 'vue' {
19
19
  ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
20
20
  ElButton: typeof import('element-plus/es')['ElButton']
21
21
  ElCard: typeof import('element-plus/es')['ElCard']
22
- ElCascader: typeof import('element-plus/es')['ElCascader']
23
22
  ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
24
23
  ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
25
24
  ElCol: typeof import('element-plus/es')['ElCol']
@@ -65,6 +64,8 @@ declare module 'vue' {
65
64
  ElSelect: typeof import('element-plus/es')['ElSelect']
66
65
  ElSkeleton: typeof import('element-plus/es')['ElSkeleton']
67
66
  ElSpace: typeof import('element-plus/es')['ElSpace']
67
+ ElStep: typeof import('element-plus/es')['ElStep']
68
+ ElSteps: typeof import('element-plus/es')['ElSteps']
68
69
  ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
69
70
  ElSwitch: typeof import('element-plus/es')['ElSwitch']
70
71
  ElTable: typeof import('element-plus/es')['ElTable']
@@ -72,6 +73,8 @@ declare module 'vue' {
72
73
  ElTabPane: typeof import('element-plus/es')['ElTabPane']
73
74
  ElTabs: typeof import('element-plus/es')['ElTabs']
74
75
  ElTag: typeof import('element-plus/es')['ElTag']
76
+ ElTimeline: typeof import('element-plus/es')['ElTimeline']
77
+ ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem']
75
78
  ElTooltip: typeof import('element-plus/es')['ElTooltip']
76
79
  ElTransfer: typeof import('element-plus/es')['ElTransfer']
77
80
  ElTree: typeof import('element-plus/es')['ElTree']