@liujitcn/kratos-admin-system 0.0.49 → 0.0.50

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 (94) hide show
  1. package/README.md +44 -8
  2. package/dist/package/declarations/src/api/system/admin/v1/base_i18n_custom.d.ts +18 -0
  3. package/dist/package/declarations/src/api/system/admin/v1/base_tenant.d.ts +2 -2
  4. package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project.d.ts +23 -0
  5. package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project_grant.d.ts +16 -0
  6. package/dist/package/declarations/src/components/tenant-project/ProjectGrantDialog.vue.d.ts +8 -0
  7. package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +29 -0
  8. package/dist/package/declarations/src/components/tenant-project/TenantProjectSelect.vue.d.ts +31 -0
  9. package/dist/package/declarations/src/components/tenant-project/TenantProjectText.vue.d.ts +19 -0
  10. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +14 -0
  11. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +40 -0
  12. package/dist/package/declarations/src/index.d.ts +2 -0
  13. package/dist/package/declarations/src/locales/generated.d.ts +172 -0
  14. package/dist/package/declarations/src/rpc/base/v1/config.d.ts +11 -0
  15. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n_custom.d.ts +105 -0
  16. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +10 -1
  17. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project.d.ts +141 -0
  18. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project_grant.d.ts +105 -0
  19. package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +2 -2
  20. package/dist/package/declarations/src/views/base/i18n-custom/index.vue.d.ts +3 -0
  21. package/dist/package/declarations/src/views/base/project-grant/index.vue.d.ts +3 -0
  22. package/dist/package/declarations/src/views/base/tenant-project/index.vue.d.ts +3 -0
  23. package/dist/package/src/api/system/admin/v1/base_i18n_custom.ts +72 -0
  24. package/dist/package/src/api/system/admin/v1/base_tenant.ts +3 -2
  25. package/dist/package/src/api/system/admin/v1/base_tenant_project.ts +94 -0
  26. package/dist/package/src/api/system/admin/v1/base_tenant_project_grant.ts +63 -0
  27. package/dist/package/src/components/tenant-project/ProjectGrantDialog.vue +108 -0
  28. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +452 -0
  29. package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +83 -0
  30. package/dist/package/src/components/tenant-project/TenantProjectText.vue +37 -0
  31. package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +26 -0
  32. package/dist/package/src/components/tenant-project/tenant-project-manager.ts +48 -0
  33. package/dist/package/src/index.ts +12 -0
  34. package/dist/package/src/locales/README.md +1 -1
  35. package/dist/package/src/locales/en-US.json +45 -2
  36. package/dist/package/src/locales/ja-JP.json +45 -2
  37. package/dist/package/src/locales/zh-CN.json +45 -2
  38. package/dist/package/src/locales/zh-TW.json +45 -2
  39. package/dist/package/src/module.ts +3 -3
  40. package/dist/package/src/rpc/base/v1/config.ts +12 -0
  41. package/dist/package/src/rpc/system/admin/v1/base_i18n_custom.ts +126 -0
  42. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +11 -1
  43. package/dist/package/src/rpc/system/admin/v1/base_tenant_project.ts +166 -0
  44. package/dist/package/src/rpc/system/admin/v1/base_tenant_project_grant.ts +128 -0
  45. package/dist/package/src/views/ai/chat/components/XSender.vue +4 -3
  46. package/dist/package/src/views/base/api-log/index.vue +3 -3
  47. package/dist/package/src/views/base/config/index.vue +3 -3
  48. package/dist/package/src/views/base/data-access-log/index.vue +3 -3
  49. package/dist/package/src/views/base/dept/index.vue +10 -45
  50. package/dist/package/src/views/base/dict/index.vue +3 -3
  51. package/dist/package/src/views/base/dict/item.vue +3 -3
  52. package/dist/package/src/views/base/file/index.vue +12 -3
  53. package/dist/package/src/views/base/i18n-custom/index.vue +321 -0
  54. package/dist/package/src/views/base/job/index.vue +3 -3
  55. package/dist/package/src/views/base/login-log/index.vue +13 -4
  56. package/dist/package/src/views/base/login-policy/index.vue +97 -34
  57. package/dist/package/src/views/base/menu/index.vue +3 -3
  58. package/dist/package/src/views/base/message/index.vue +9 -32
  59. package/dist/package/src/views/base/message-category/index.vue +2 -4
  60. package/dist/package/src/views/base/oauth-client/index.vue +8 -42
  61. package/dist/package/src/views/base/online-session/index.vue +8 -27
  62. package/dist/package/src/views/base/operation-log/index.vue +3 -3
  63. package/dist/package/src/views/base/permission-log/index.vue +3 -3
  64. package/dist/package/src/views/base/policy-evaluation-log/index.vue +3 -3
  65. package/dist/package/src/views/base/post/index.vue +9 -42
  66. package/dist/package/src/views/base/project-grant/index.vue +336 -0
  67. package/dist/package/src/views/base/role/index.vue +13 -47
  68. package/dist/package/src/views/base/tenant/index.vue +163 -19
  69. package/dist/package/src/views/base/tenant-project/index.vue +12 -0
  70. package/dist/package/src/views/base/user/index.vue +30 -51
  71. package/dist/package/src/views/profile/components/login-log.vue +15 -2
  72. package/dist/package/src/views/profile/components/password.vue +1 -1
  73. package/dist/package/src/views/profile/components/security.vue +2 -2
  74. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +2 -2
  75. package/dist/package/src/views/tool/code-gen/config.ts +2 -1
  76. package/package.json +2 -2
  77. /package/dist/package/declarations/src/components/{ChangePasswordForm.vue.d.ts → account/ChangePasswordForm.vue.d.ts} +0 -0
  78. /package/dist/package/declarations/src/components/{ForcedPasswordDialog.vue.d.ts → account/ForcedPasswordDialog.vue.d.ts} +0 -0
  79. /package/dist/package/declarations/src/components/{Ai.vue.d.ts → ai/Ai.vue.d.ts} +0 -0
  80. /package/dist/package/declarations/src/components/{DynamicI18nCell.vue.d.ts → i18n/DynamicI18nCell.vue.d.ts} +0 -0
  81. /package/dist/package/declarations/src/components/{DynamicI18nEditor.vue.d.ts → i18n/DynamicI18nEditor.vue.d.ts} +0 -0
  82. /package/dist/package/declarations/src/components/{dynamicI18n.d.ts → i18n/dynamicI18n.d.ts} +0 -0
  83. /package/dist/package/declarations/src/components/{LogTable.vue.d.ts → log/LogTable.vue.d.ts} +0 -0
  84. /package/dist/package/declarations/src/components/{log.d.ts → log/log.d.ts} +0 -0
  85. /package/dist/package/declarations/src/components/{Notification.vue.d.ts → notification/Notification.vue.d.ts} +0 -0
  86. /package/dist/package/src/components/{ChangePasswordForm.vue → account/ChangePasswordForm.vue} +0 -0
  87. /package/dist/package/src/components/{ForcedPasswordDialog.vue → account/ForcedPasswordDialog.vue} +0 -0
  88. /package/dist/package/src/components/{Ai.vue → ai/Ai.vue} +0 -0
  89. /package/dist/package/src/components/{DynamicI18nCell.vue → i18n/DynamicI18nCell.vue} +0 -0
  90. /package/dist/package/src/components/{DynamicI18nEditor.vue → i18n/DynamicI18nEditor.vue} +0 -0
  91. /package/dist/package/src/components/{dynamicI18n.ts → i18n/dynamicI18n.ts} +0 -0
  92. /package/dist/package/src/components/{LogTable.vue → log/LogTable.vue} +0 -0
  93. /package/dist/package/src/components/{log.ts → log/log.ts} +0 -0
  94. /package/dist/package/src/components/{Notification.vue → notification/Notification.vue} +0 -0
@@ -0,0 +1,336 @@
1
+ <template>
2
+ <div class="table-box">
3
+ <ProTable
4
+ ref="proTable"
5
+ :key="isDefaultTenant ? 'default-tenant' : 'current-tenant'"
6
+ row-key="grant_key"
7
+ :columns="columns"
8
+ :header-actions="headerActions"
9
+ :request-api="requestProjectGrantTable"
10
+ />
11
+
12
+ <FormDialog
13
+ v-model="dialog.visible"
14
+ ref="formDialogRef"
15
+ :title="t(dialog.titleKey, { resource: t('system.base.tenant_project_grant.title') })"
16
+ width="640px"
17
+ :model="formData"
18
+ :fields="formFields"
19
+ :rules="rules"
20
+ :confirm-loading="loading"
21
+ @confirm="handleSubmit"
22
+ @close="handleCloseDialog"
23
+ />
24
+ </div>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { computed, reactive, ref } from "vue";
29
+ import { ElMessage, ElMessageBox } from "element-plus";
30
+ import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
31
+ import type { ColumnProps, HeaderActionProps, ProTableInstance } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
32
+ import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
33
+ import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
34
+ import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
35
+ import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
36
+ import { buildPageRequest } from "@liujitcn/kratos-admin-core/table";
37
+ import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
38
+ import { t } from "@liujitcn/kratos-admin-core";
39
+ import { defBaseDeptService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_dept";
40
+ import { defBasePostService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_post";
41
+ import { defBaseRoleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_role";
42
+ import { defBaseTenantProjectService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant_project";
43
+ import { defBaseTenantProjectGrantService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant_project_grant";
44
+ import { defBaseUserService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_user";
45
+ import type { SelectOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
46
+ import {
47
+ BaseTenantProjectGrantSubjectType,
48
+ type BaseTenantProjectGrant,
49
+ type PageBaseTenantProjectGrantRequest
50
+ } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project_grant";
51
+
52
+ defineOptions({
53
+ name: "BaseTenantProjectGrant",
54
+ inheritAttrs: false
55
+ });
56
+
57
+ type ProjectGrantFormState = {
58
+ tenant_id?: number;
59
+ subject_type: BaseTenantProjectGrantSubjectType;
60
+ subject_id?: number;
61
+ project_id: number[];
62
+ all: boolean;
63
+ };
64
+
65
+ const { BUTTONS } = useAuthButtons();
66
+ const proTable = ref<ProTableInstance>();
67
+ const formDialogRef = ref<InstanceType<typeof FormDialog>>();
68
+ const { isDefaultTenant, tenantOptions, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
69
+ const projectOptions = ref<SelectOptionResponse_Option[]>([]);
70
+ const subjectOptions = ref<ProFormOption[]>([]);
71
+ const loading = ref(false);
72
+ const dialog = reactive({
73
+ visible: false,
74
+ editing: false,
75
+ titleKey: "common.action.create_resource"
76
+ });
77
+ const DEFAULT_SUBJECT_TYPE = BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_POST;
78
+ const formData = reactive<ProjectGrantFormState>({
79
+ tenant_id: undefined,
80
+ subject_type: DEFAULT_SUBJECT_TYPE,
81
+ subject_id: undefined,
82
+ project_id: [],
83
+ all: false
84
+ });
85
+ const subjectTypeOptions = computed<ProFormOption[]>(() => [
86
+ { label: t("system.base.tenant_project_grant.subject_type.post"), value: BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_POST },
87
+ { label: t("system.base.tenant_project_grant.subject_type.role"), value: BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_ROLE },
88
+ { label: t("system.base.tenant_project_grant.subject_type.dept"), value: BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_DEPT },
89
+ { label: t("system.base.tenant_project_grant.subject_type.user"), value: BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_USER }
90
+ ]);
91
+ const subjectTypeLabel = computed(() => new Map(subjectTypeOptions.value.map(item => [item.value, item.label])));
92
+ const rules = computed(() => ({
93
+ tenant_id: [{ required: true, message: t("common.validation.required_select", { field: t("common.field.tenant") }), trigger: "change" }],
94
+ subject_type: [{ required: true, type: "number", min: 1, message: t("common.validation.required_select", { field: t("system.base.tenant_project_grant.field.subject_type") }), trigger: "change" }],
95
+ subject_id: [{ required: true, type: "number", min: 1, message: t("common.validation.required_select", { field: t("system.base.tenant_project_grant.field.subject") }), trigger: "change" }]
96
+ }));
97
+ const formFields = computed<ProFormField[]>(() => [
98
+ tenantFormField({
99
+ label: t("common.field.tenant"),
100
+ props: { disabled: dialog.editing || loading.value, onChange: handleTenantChange }
101
+ }),
102
+ {
103
+ prop: "subject_type",
104
+ label: t("system.base.tenant_project_grant.field.subject_type"),
105
+ component: "select",
106
+ options: subjectTypeOptions.value,
107
+ props: { filterable: false, disabled: dialog.editing || loading.value, onChange: handleSubjectTypeChange }
108
+ },
109
+ {
110
+ prop: "subject_id",
111
+ label: t("system.base.tenant_project_grant.field.subject"),
112
+ component: formData.subject_type === BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_DEPT ? "tree-select" : "select",
113
+ options: subjectOptions.value,
114
+ props:
115
+ formData.subject_type === BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_DEPT
116
+ ? { filterable: true, checkStrictly: true, renderAfterExpand: false, style: { width: "100%" }, disabled: dialog.editing || !formData.tenant_id || loading.value }
117
+ : { filterable: true, clearable: true, disabled: dialog.editing || !formData.tenant_id || formData.subject_type === 0 || loading.value }
118
+ },
119
+ {
120
+ prop: "all",
121
+ label: t("system.base.tenant_project_grant.field.scope"),
122
+ labelTooltip: t("system.base.tenant_project_grant.tooltip.scope"),
123
+ component: "switch",
124
+ checkboxLabel: t("system.base.tenant_project_grant.all_projects"),
125
+ props: { disabled: !formData.tenant_id || !formData.subject_id || loading.value }
126
+ },
127
+ {
128
+ prop: "project_id",
129
+ label: t("system.base.tenant_project_grant.field.projects"),
130
+ component: "select",
131
+ options: projectOptions.value,
132
+ visible: () => !formData.all,
133
+ props: { multiple: true, filterable: true, clearable: true, disabled: !formData.tenant_id || loading.value }
134
+ }
135
+ ]);
136
+ const columns = computed<ColumnProps[]>(() => [
137
+ ...tenantColumns({ label: t("common.field.tenant"), minWidth: 150, order: 1 }),
138
+ {
139
+ prop: "subject_type",
140
+ label: t("system.base.tenant_project_grant.field.subject_type"),
141
+ minWidth: 110,
142
+ search: { el: "select", enum: subjectTypeOptions.value },
143
+ render: scope => subjectTypeLabel.value.get((scope.row as BaseTenantProjectGrant).subject_type) ?? String((scope.row as BaseTenantProjectGrant).subject_type)
144
+ },
145
+ { prop: "subject_name", label: t("system.base.tenant_project_grant.field.subject"), minWidth: 160, showOverflowTooltip: true },
146
+ {
147
+ prop: "project_names",
148
+ label: t("system.base.tenant_project_grant.field.projects"),
149
+ minWidth: 220,
150
+ showOverflowTooltip: true,
151
+ render: scope => {
152
+ const row = scope.row as BaseTenantProjectGrant;
153
+ return row.project_id.length === 1 && row.project_id[0] === 0
154
+ ? t("system.base.tenant_project_grant.all_projects")
155
+ : row.project_names.join(", ") || row.project_id.join(", ") || t("common.value.none");
156
+ }
157
+ },
158
+ {
159
+ prop: "operation",
160
+ label: t("common.field.operation"),
161
+ fixed: "right",
162
+ cellType: "actions",
163
+ actions: [
164
+ {
165
+ label: t("common.action.edit"),
166
+ type: "primary",
167
+ link: true,
168
+ icon: EditPen,
169
+ hidden: () => !BUTTONS.value["base:tenant:project:grant:update"],
170
+ onClick: scope => handleOpenDialog(scope.row as BaseTenantProjectGrant)
171
+ },
172
+ {
173
+ label: t("common.action.delete"),
174
+ type: "danger",
175
+ link: true,
176
+ icon: Delete,
177
+ hidden: () => !BUTTONS.value["base:tenant:project:grant:delete"],
178
+ onClick: scope => handleDelete(scope.row as BaseTenantProjectGrant)
179
+ }
180
+ ]
181
+ }
182
+ ]);
183
+ const headerActions = computed<HeaderActionProps[]>(() => [
184
+ {
185
+ label: t("common.action.create"),
186
+ type: "success",
187
+ icon: CirclePlus,
188
+ hidden: () => !BUTTONS.value["base:tenant:project:grant:create"],
189
+ onClick: () => handleOpenDialog()
190
+ },
191
+ {
192
+ label: t("common.action.delete"),
193
+ type: "danger",
194
+ icon: Delete,
195
+ hidden: () => !BUTTONS.value["base:tenant:project:grant:delete"],
196
+ disabled: scope => !scope.selectedList.length,
197
+ onClick: scope => handleDelete(scope.selectedList as BaseTenantProjectGrant[])
198
+ }
199
+ ]);
200
+
201
+ /** 请求项目授权分页列表。 */
202
+ async function requestProjectGrantTable(params: Record<string, unknown>) {
203
+ const page = buildPageRequest(params) as unknown as PageBaseTenantProjectGrantRequest;
204
+ const tenantId = toRequestTenantId(params.tenant_id);
205
+ const data = await defBaseTenantProjectGrantService.PageBaseTenantProjectGrant({ ...page, tenant_id: tenantId });
206
+ return { data: { list: data.grants ?? [], total: data.total } };
207
+ }
208
+
209
+ /** 打开项目授权新增或编辑弹窗。 */
210
+ async function handleOpenDialog(row?: BaseTenantProjectGrant) {
211
+ resetForm();
212
+ dialog.editing = Boolean(row);
213
+ dialog.titleKey = row ? "common.action.edit_resource" : "common.action.create_resource";
214
+ loading.value = true;
215
+ try {
216
+ await loadTenantOptions(true);
217
+ if (!isDefaultTenant.value) formData.tenant_id = Number(tenantOptions.value[0]?.value) || undefined;
218
+ if (row) {
219
+ formData.tenant_id = row.tenant_id;
220
+ formData.subject_type = row.subject_type;
221
+ formData.subject_id = row.subject_id;
222
+ formData.all = row.project_id.length === 1 && row.project_id[0] === 0;
223
+ formData.project_id = formData.all ? [] : [...row.project_id];
224
+ }
225
+ await Promise.all([loadProjectOptions(), loadSubjectOptions()]);
226
+ dialog.visible = true;
227
+ } finally {
228
+ loading.value = false;
229
+ }
230
+ }
231
+
232
+ /** 加载目标租户项目选项。 */
233
+ async function loadProjectOptions() {
234
+ projectOptions.value = formData.tenant_id ? (await defBaseTenantProjectService.OptionBaseTenantProject({ tenant_id: formData.tenant_id })).list ?? [] : [];
235
+ }
236
+
237
+ /** 根据授权类型加载岗位、角色、部门或用户主体选项。 */
238
+ async function loadSubjectOptions() {
239
+ subjectOptions.value = [];
240
+ if (!formData.tenant_id || formData.subject_type === BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_UNSPECIFIED) return;
241
+ switch (formData.subject_type) {
242
+ case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_POST:
243
+ subjectOptions.value = (await defBasePostService.OptionBasePost({ tenant_id: formData.tenant_id })).list ?? [];
244
+ return;
245
+ case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_ROLE:
246
+ subjectOptions.value = (await defBaseRoleService.OptionBaseRole({ tenant_id: formData.tenant_id })).list ?? [];
247
+ return;
248
+ case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_DEPT:
249
+ subjectOptions.value = ((await defBaseDeptService.OptionBaseDept({ tenant_id: formData.tenant_id })).list ?? []) as unknown as ProFormOption[];
250
+ return;
251
+ case BaseTenantProjectGrantSubjectType.BASE_TENANT_PROJECT_GRANT_SUBJECT_TYPE_USER:
252
+ subjectOptions.value = (await defBaseUserService.OptionBaseUser({ keyword: "", tenant_id: formData.tenant_id })).list ?? [];
253
+ }
254
+ }
255
+
256
+ /** 切换目标租户时重置主体和项目选择。 */
257
+ async function handleTenantChange(value: unknown) {
258
+ if (loading.value) return;
259
+ formData.tenant_id = Number(value) || undefined;
260
+ formData.subject_type = DEFAULT_SUBJECT_TYPE;
261
+ formData.subject_id = undefined;
262
+ formData.project_id = [];
263
+ formData.all = false;
264
+ await Promise.all([loadProjectOptions(), loadSubjectOptions()]);
265
+ }
266
+
267
+ /** 切换授权类型时重新加载对应主体数据。 */
268
+ async function handleSubjectTypeChange(value: unknown) {
269
+ if (loading.value) return;
270
+ formData.subject_type = Number(value) as BaseTenantProjectGrantSubjectType;
271
+ formData.subject_id = undefined;
272
+ await loadSubjectOptions();
273
+ }
274
+
275
+ /** 提交项目授权新增或编辑。 */
276
+ function handleSubmit() {
277
+ formDialogRef.value?.validate()?.then(valid => {
278
+ if (!valid || !formData.tenant_id || !formData.subject_id || formData.subject_type === 0) return;
279
+ const grant: BaseTenantProjectGrant = {
280
+ tenant_id: formData.tenant_id,
281
+ subject_type: formData.subject_type,
282
+ subject_id: formData.subject_id,
283
+ project_id: formData.all ? [0] : [...formData.project_id],
284
+ tenant_name: "",
285
+ subject_name: "",
286
+ subject_code: "",
287
+ project_names: [],
288
+ grant_key: ""
289
+ };
290
+ loading.value = true;
291
+ const request = dialog.editing
292
+ ? defBaseTenantProjectGrantService.UpdateBaseTenantProjectGrant({ base_tenant_project_grant: grant })
293
+ : defBaseTenantProjectGrantService.CreateBaseTenantProjectGrant({ base_tenant_project_grant: grant });
294
+ request.then(() => {
295
+ ElMessage.success(t(dialog.editing ? "common.message.update_success" : "common.message.create_success", { resource: t("system.base.tenant_project_grant.title") }));
296
+ handleCloseDialog();
297
+ proTable.value?.getTableList();
298
+ }).finally(() => { loading.value = false; });
299
+ });
300
+ }
301
+
302
+ /** 关闭并重置项目授权弹窗。 */
303
+ function handleCloseDialog() {
304
+ dialog.visible = false;
305
+ dialog.editing = false;
306
+ resetForm();
307
+ }
308
+
309
+ /** 重置项目授权表单。 */
310
+ function resetForm() {
311
+ formDialogRef.value?.resetFields();
312
+ formDialogRef.value?.clearValidate();
313
+ formData.tenant_id = undefined;
314
+ formData.subject_type = DEFAULT_SUBJECT_TYPE;
315
+ formData.subject_id = undefined;
316
+ formData.project_id = [];
317
+ formData.all = false;
318
+ projectOptions.value = [];
319
+ subjectOptions.value = [];
320
+ }
321
+
322
+ /** 删除项目授权,支持单条和批量记录。 */
323
+ function handleDelete(selected: BaseTenantProjectGrant | BaseTenantProjectGrant[]) {
324
+ const rows = Array.isArray(selected) ? selected : [selected];
325
+ if (!rows.length) return;
326
+ const label = rows.length === 1 ? rows[0].subject_name || rows[0].subject_code || rows[0].grant_key : t("common.dialog.delete_batch", { count: rows.length, unit: "", resource: t("system.base.tenant_project_grant.title") });
327
+ const message = rows.length === 1
328
+ ? `${t("common.dialog.delete_single", { resource: t("system.base.tenant_project_grant.title") })}\n${t("common.dialog.resource_field", { field: t("system.base.tenant_project_grant.field.subject"), value: label })}`
329
+ : label;
330
+ ElMessageBox.confirm(message, t("common.title.warning"), { confirmButtonText: t("common.action.confirm"), cancelButtonText: t("common.action.cancel"), type: "warning" }).then(async () => {
331
+ await Promise.all(rows.map(row => defBaseTenantProjectGrantService.DeleteBaseTenantProjectGrant({ tenant_id: row.tenant_id, subject_type: row.subject_type, subject_id: row.subject_id })));
332
+ ElMessage.success(t("common.message.delete_success", { resource: t("system.base.tenant_project_grant.title") }));
333
+ proTable.value?.getTableList();
334
+ });
335
+ }
336
+ </script>
@@ -84,7 +84,7 @@
84
84
  </template>
85
85
 
86
86
  <script setup lang="ts">
87
- import { computed, nextTick, reactive, ref, watch } from "vue";
87
+ import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
88
88
  import { ElMessage, ElMessageBox, ElTree } from "element-plus";
89
89
  import type { CheckboxValueType } from "element-plus";
90
90
  import { CirclePlus, Delete, EditPen, Position, QuestionFilled, Search, Switch } from "@element-plus/icons-vue";
@@ -96,12 +96,10 @@ import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
96
96
  import { defBaseRoleService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_role";
97
97
  import type { BaseRole, BaseRoleForm, PageBaseRoleRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_role";
98
98
  import { defBaseMenuService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_menu";
99
- import { defBaseTenantService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant";
100
- import type { SelectOptionResponse_Option, TreeOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
99
+ import type { TreeOptionResponse_Option } from "@liujitcn/kratos-admin-system/rpc/common/v1/common";
101
100
  import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
102
101
  import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
103
- import { useUserStore } from "@liujitcn/kratos-admin-core/stores/runtime";
104
- import { DEFAULT_TENANT_CODE, requestTenantOptions } from "@liujitcn/kratos-admin-core/tenant";
102
+ import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
105
103
  import { t } from "@liujitcn/kratos-admin-core";
106
104
 
107
105
  defineOptions({
@@ -122,7 +120,6 @@ type BaseRoleFormState = Omit<BaseRoleForm, "tenant_id"> & {
122
120
  };
123
121
 
124
122
  const { BUTTONS } = useAuthButtons();
125
- const userStore = useUserStore();
126
123
  const proTable = ref<ProTableInstance>();
127
124
  const formDialogRef = ref<InstanceType<typeof FormDialog>>();
128
125
  const permTreeRef = ref<InstanceType<typeof ElTree>>();
@@ -133,12 +130,12 @@ const dialog = reactive({
133
130
  });
134
131
 
135
132
  const menuPermOptions = ref<TreeOptionResponse_Option[]>([]);
136
- const tenantOptions = ref<SelectOptionResponse_Option[]>([]);
137
133
  const statusOptions = computed<ProFormOption[]>(() => [
138
134
  { label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
139
135
  { label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
140
136
  ]);
141
137
  const protectedRoleCodes = new Set(["admin", "authuser", "user"]);
138
+ const undeletableRoleCodes = new Set(["super", "tenant", "admin", "authuser", "user"]);
142
139
 
143
140
  const formData = reactive<BaseRoleFormState>({
144
141
  /** 角色ID */
@@ -238,24 +235,14 @@ const permKeywords = ref("");
238
235
  const isExpanded = ref(true);
239
236
  const parentChildLinked = ref(true);
240
237
 
241
- /** 当前登录账号是否默认租户。 */
242
- const isDefaultTenant = computed(() => userStore.userInfo.tenant_code === DEFAULT_TENANT_CODE);
238
+ const { isDefaultTenant, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
239
+ onMounted(() => {
240
+ if (isDefaultTenant.value) void loadTenantOptions();
241
+ });
243
242
 
244
243
  /** 角色表单字段配置。 */
245
244
  const formFields = computed<ProFormField[]>(() => [
246
- {
247
- prop: "tenant_id",
248
- label: t("common.field.tenant"),
249
- component: "select",
250
- props: {
251
- placeholder: t("common.placeholder.select"),
252
- filterable: true,
253
- disabled: Boolean(formData.id),
254
- onChange: handleFormTenantChange
255
- },
256
- visible: () => isDefaultTenant.value,
257
- options: tenantOptions.value
258
- },
245
+ tenantFormField({ label: t("common.field.tenant"), disabledOnEdit: true, props: { onChange: handleFormTenantChange } }),
259
246
  {
260
247
  prop: "name",
261
248
  label: t("system.base.role.field.name"),
@@ -302,19 +289,7 @@ const formFields = computed<ProFormField[]>(() => [
302
289
  /** 角色表格列配置。 */
303
290
  const columns = computed<ColumnProps[]>(() => [
304
291
  { type: "selection", width: 55, selectable: row => canDeleteRole(row as BaseRole) },
305
- ...(isDefaultTenant.value
306
- ? ([
307
- {
308
- prop: "tenant_id",
309
- label: t("common.field.tenant"),
310
- minWidth: 140,
311
- align: "left",
312
- showOverflowTooltip: true,
313
- search: { el: "select", key: "tenant_id", props: { filterable: true }, order: 1 },
314
- enum: requestTenantOptions
315
- }
316
- ] satisfies ColumnProps[])
317
- : []),
292
+ ...tenantColumns({ label: t("common.field.tenant"), order: 1 }),
318
293
  { prop: "name", label: t("system.base.role.field.name"), minWidth: 140, search: { el: "input" } },
319
294
  { prop: "code", label: t("system.base.role.field.code"), minWidth: 160, search: { el: "input" } },
320
295
  {
@@ -401,7 +376,7 @@ const headerActions = computed<HeaderActionProps[]>(() => [
401
376
  async function requestBaseRoleTable(params: PageBaseRoleRequest) {
402
377
  const data = await defBaseRoleService.PageBaseRole({
403
378
  ...buildPageRequest(params),
404
- tenant_id: isDefaultTenant.value ? params.tenant_id : undefined
379
+ tenant_id: toRequestTenantId(params.tenant_id)
405
380
  });
406
381
  return { data: { list: data.base_roles ?? [], total: data.total } };
407
382
  }
@@ -413,15 +388,6 @@ function refreshTable() {
413
388
  proTable.value?.getTableList();
414
389
  }
415
390
 
416
- /**
417
- * 加载租户下拉选项。
418
- */
419
- async function loadTenantOptions() {
420
- if (!isDefaultTenant.value || tenantOptions.value.length) return;
421
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
422
- tenantOptions.value = response.list ?? [];
423
- }
424
-
425
391
  /**
426
392
  * 按目标角色加载可分配的菜单权限树数据。
427
393
  */
@@ -516,7 +482,7 @@ function canManageRole(row?: BaseRole) {
516
482
  return Boolean(row?.code && !row.is_protected);
517
483
  }
518
484
 
519
- /** 判断角色是否禁止切换状态和删除。 */
485
+ /** 判断角色是否禁止切换状态。 */
520
486
  function isRoleProtected(code?: string) {
521
487
  return Boolean(code && protectedRoleCodes.has(code));
522
488
  }
@@ -528,7 +494,7 @@ function canChangeRoleStatus(row?: BaseRole) {
528
494
 
529
495
  /** 判断当前账号是否允许删除目标角色。 */
530
496
  function canDeleteRole(row?: BaseRole) {
531
- return canManageRole(row) && !isRoleProtected(row?.code);
497
+ return canManageRole(row) && !undeletableRoleCodes.has(row?.code ?? "");
532
498
  }
533
499
 
534
500
  /**