@liujitcn/kratos-admin-system 0.0.48 → 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 (95) 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/{LogTable.vue.d.ts → log/LogTable.vue.d.ts} +2 -0
  7. package/dist/package/declarations/src/components/tenant-project/ProjectGrantDialog.vue.d.ts +8 -0
  8. package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +29 -0
  9. package/dist/package/declarations/src/components/tenant-project/TenantProjectSelect.vue.d.ts +31 -0
  10. package/dist/package/declarations/src/components/tenant-project/TenantProjectText.vue.d.ts +19 -0
  11. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +14 -0
  12. package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +40 -0
  13. package/dist/package/declarations/src/index.d.ts +2 -0
  14. package/dist/package/declarations/src/locales/generated.d.ts +172 -0
  15. package/dist/package/declarations/src/rpc/base/v1/config.d.ts +11 -0
  16. package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n_custom.d.ts +105 -0
  17. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +10 -1
  18. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project.d.ts +141 -0
  19. package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project_grant.d.ts +105 -0
  20. package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +2 -2
  21. package/dist/package/declarations/src/views/base/i18n-custom/index.vue.d.ts +3 -0
  22. package/dist/package/declarations/src/views/base/project-grant/index.vue.d.ts +3 -0
  23. package/dist/package/declarations/src/views/base/tenant-project/index.vue.d.ts +3 -0
  24. package/dist/package/src/api/system/admin/v1/base_i18n_custom.ts +72 -0
  25. package/dist/package/src/api/system/admin/v1/base_tenant.ts +3 -2
  26. package/dist/package/src/api/system/admin/v1/base_tenant_project.ts +94 -0
  27. package/dist/package/src/api/system/admin/v1/base_tenant_project_grant.ts +63 -0
  28. package/dist/package/src/components/{LogTable.vue → log/LogTable.vue} +9 -1
  29. package/dist/package/src/components/tenant-project/ProjectGrantDialog.vue +108 -0
  30. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +452 -0
  31. package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +83 -0
  32. package/dist/package/src/components/tenant-project/TenantProjectText.vue +37 -0
  33. package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +26 -0
  34. package/dist/package/src/components/tenant-project/tenant-project-manager.ts +48 -0
  35. package/dist/package/src/index.ts +12 -0
  36. package/dist/package/src/locales/README.md +1 -1
  37. package/dist/package/src/locales/en-US.json +45 -2
  38. package/dist/package/src/locales/ja-JP.json +45 -2
  39. package/dist/package/src/locales/zh-CN.json +45 -2
  40. package/dist/package/src/locales/zh-TW.json +45 -2
  41. package/dist/package/src/module.ts +3 -3
  42. package/dist/package/src/rpc/base/v1/config.ts +12 -0
  43. package/dist/package/src/rpc/system/admin/v1/base_i18n_custom.ts +126 -0
  44. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +11 -1
  45. package/dist/package/src/rpc/system/admin/v1/base_tenant_project.ts +166 -0
  46. package/dist/package/src/rpc/system/admin/v1/base_tenant_project_grant.ts +128 -0
  47. package/dist/package/src/views/ai/chat/components/XSender.vue +4 -3
  48. package/dist/package/src/views/base/api-log/index.vue +3 -3
  49. package/dist/package/src/views/base/config/index.vue +3 -3
  50. package/dist/package/src/views/base/data-access-log/index.vue +4 -4
  51. package/dist/package/src/views/base/dept/index.vue +10 -44
  52. package/dist/package/src/views/base/dict/index.vue +3 -3
  53. package/dist/package/src/views/base/dict/item.vue +3 -3
  54. package/dist/package/src/views/base/file/index.vue +12 -3
  55. package/dist/package/src/views/base/i18n-custom/index.vue +321 -0
  56. package/dist/package/src/views/base/job/index.vue +3 -3
  57. package/dist/package/src/views/base/job/log.vue +1 -0
  58. package/dist/package/src/views/base/login-log/index.vue +13 -4
  59. package/dist/package/src/views/base/login-policy/index.vue +97 -34
  60. package/dist/package/src/views/base/menu/index.vue +3 -3
  61. package/dist/package/src/views/base/message/index.vue +9 -31
  62. package/dist/package/src/views/base/message-category/index.vue +2 -4
  63. package/dist/package/src/views/base/oauth-client/index.vue +8 -41
  64. package/dist/package/src/views/base/online-session/index.vue +8 -26
  65. package/dist/package/src/views/base/operation-log/index.vue +5 -5
  66. package/dist/package/src/views/base/permission-log/index.vue +3 -3
  67. package/dist/package/src/views/base/policy-evaluation-log/index.vue +3 -3
  68. package/dist/package/src/views/base/post/index.vue +9 -41
  69. package/dist/package/src/views/base/project-grant/index.vue +336 -0
  70. package/dist/package/src/views/base/role/index.vue +13 -46
  71. package/dist/package/src/views/base/tenant/index.vue +163 -19
  72. package/dist/package/src/views/base/tenant-project/index.vue +12 -0
  73. package/dist/package/src/views/base/user/index.vue +30 -50
  74. package/dist/package/src/views/profile/components/login-log.vue +15 -2
  75. package/dist/package/src/views/profile/components/password.vue +1 -1
  76. package/dist/package/src/views/profile/components/security.vue +2 -2
  77. package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +2 -2
  78. package/dist/package/src/views/tool/code-gen/config.ts +2 -1
  79. package/package.json +2 -2
  80. /package/dist/package/declarations/src/components/{ChangePasswordForm.vue.d.ts → account/ChangePasswordForm.vue.d.ts} +0 -0
  81. /package/dist/package/declarations/src/components/{ForcedPasswordDialog.vue.d.ts → account/ForcedPasswordDialog.vue.d.ts} +0 -0
  82. /package/dist/package/declarations/src/components/{Ai.vue.d.ts → ai/Ai.vue.d.ts} +0 -0
  83. /package/dist/package/declarations/src/components/{DynamicI18nCell.vue.d.ts → i18n/DynamicI18nCell.vue.d.ts} +0 -0
  84. /package/dist/package/declarations/src/components/{DynamicI18nEditor.vue.d.ts → i18n/DynamicI18nEditor.vue.d.ts} +0 -0
  85. /package/dist/package/declarations/src/components/{dynamicI18n.d.ts → i18n/dynamicI18n.d.ts} +0 -0
  86. /package/dist/package/declarations/src/components/{log.d.ts → log/log.d.ts} +0 -0
  87. /package/dist/package/declarations/src/components/{Notification.vue.d.ts → notification/Notification.vue.d.ts} +0 -0
  88. /package/dist/package/src/components/{ChangePasswordForm.vue → account/ChangePasswordForm.vue} +0 -0
  89. /package/dist/package/src/components/{ForcedPasswordDialog.vue → account/ForcedPasswordDialog.vue} +0 -0
  90. /package/dist/package/src/components/{Ai.vue → ai/Ai.vue} +0 -0
  91. /package/dist/package/src/components/{DynamicI18nCell.vue → i18n/DynamicI18nCell.vue} +0 -0
  92. /package/dist/package/src/components/{DynamicI18nEditor.vue → i18n/DynamicI18nEditor.vue} +0 -0
  93. /package/dist/package/src/components/{dynamicI18n.ts → i18n/dynamicI18n.ts} +0 -0
  94. /package/dist/package/src/components/{log.ts → log/log.ts} +0 -0
  95. /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,18 +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
- showOverflowTooltip: true,
312
- search: { el: "select", key: "tenant_id", props: { filterable: true }, order: 1 },
313
- enum: requestTenantOptions
314
- }
315
- ] satisfies ColumnProps[])
316
- : []),
292
+ ...tenantColumns({ label: t("common.field.tenant"), order: 1 }),
317
293
  { prop: "name", label: t("system.base.role.field.name"), minWidth: 140, search: { el: "input" } },
318
294
  { prop: "code", label: t("system.base.role.field.code"), minWidth: 160, search: { el: "input" } },
319
295
  {
@@ -400,7 +376,7 @@ const headerActions = computed<HeaderActionProps[]>(() => [
400
376
  async function requestBaseRoleTable(params: PageBaseRoleRequest) {
401
377
  const data = await defBaseRoleService.PageBaseRole({
402
378
  ...buildPageRequest(params),
403
- tenant_id: isDefaultTenant.value ? params.tenant_id : undefined
379
+ tenant_id: toRequestTenantId(params.tenant_id)
404
380
  });
405
381
  return { data: { list: data.base_roles ?? [], total: data.total } };
406
382
  }
@@ -412,15 +388,6 @@ function refreshTable() {
412
388
  proTable.value?.getTableList();
413
389
  }
414
390
 
415
- /**
416
- * 加载租户下拉选项。
417
- */
418
- async function loadTenantOptions() {
419
- if (!isDefaultTenant.value || tenantOptions.value.length) return;
420
- const response = await defBaseTenantService.OptionBaseTenant({ keyword: "" });
421
- tenantOptions.value = response.list ?? [];
422
- }
423
-
424
391
  /**
425
392
  * 按目标角色加载可分配的菜单权限树数据。
426
393
  */
@@ -515,7 +482,7 @@ function canManageRole(row?: BaseRole) {
515
482
  return Boolean(row?.code && !row.is_protected);
516
483
  }
517
484
 
518
- /** 判断角色是否禁止切换状态和删除。 */
485
+ /** 判断角色是否禁止切换状态。 */
519
486
  function isRoleProtected(code?: string) {
520
487
  return Boolean(code && protectedRoleCodes.has(code));
521
488
  }
@@ -527,7 +494,7 @@ function canChangeRoleStatus(row?: BaseRole) {
527
494
 
528
495
  /** 判断当前账号是否允许删除目标角色。 */
529
496
  function canDeleteRole(row?: BaseRole) {
530
- return canManageRole(row) && !isRoleProtected(row?.code);
497
+ return canManageRole(row) && !undeletableRoleCodes.has(row?.code ?? "");
531
498
  }
532
499
 
533
500
  /**