@liujitcn/kratos-admin-system 0.0.49 → 0.0.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -8
- package/dist/package/declarations/src/api/system/admin/v1/base_i18n_custom.d.ts +18 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
- package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant.d.ts +2 -2
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project.d.ts +23 -0
- package/dist/package/declarations/src/api/system/admin/v1/base_tenant_project_grant.d.ts +16 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +29 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectSelect.vue.d.ts +31 -0
- package/dist/package/declarations/src/components/tenant-project/TenantProjectText.vue.d.ts +19 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +14 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +40 -0
- package/dist/package/declarations/src/components/tenant-project-grant/ProjectGrantDialog.vue.d.ts +8 -0
- package/dist/package/declarations/src/index.d.ts +2 -0
- package/dist/package/declarations/src/locales/generated.d.ts +196 -0
- package/dist/package/declarations/src/rpc/base/v1/config.d.ts +11 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_i18n_custom.d.ts +105 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant.d.ts +10 -1
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project.d.ts +141 -0
- package/dist/package/declarations/src/rpc/system/admin/v1/base_tenant_project_grant.d.ts +105 -0
- package/dist/package/declarations/src/views/ai/chat/components/SessionPanel.vue.d.ts +2 -2
- package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +33 -0
- package/dist/package/declarations/src/views/base/i18n-custom/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/tenant-project/index.vue.d.ts +3 -0
- package/dist/package/declarations/src/views/base/tenant-project-grant/index.vue.d.ts +3 -0
- package/dist/package/src/api/system/admin/v1/base_i18n_custom.ts +72 -0
- package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
- package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
- package/dist/package/src/api/system/admin/v1/base_tenant.ts +3 -2
- package/dist/package/src/api/system/admin/v1/base_tenant_project.ts +94 -0
- package/dist/package/src/api/system/admin/v1/base_tenant_project_grant.ts +63 -0
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +431 -0
- package/dist/package/src/components/tenant-project/TenantProjectSelect.vue +83 -0
- package/dist/package/src/components/tenant-project/TenantProjectText.vue +37 -0
- package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +26 -0
- package/dist/package/src/components/tenant-project/tenant-project-manager.ts +48 -0
- package/dist/package/src/components/tenant-project-grant/ProjectGrantDialog.vue +108 -0
- package/dist/package/src/index.ts +12 -0
- package/dist/package/src/locales/README.md +1 -1
- package/dist/package/src/locales/en-US.json +51 -2
- package/dist/package/src/locales/ja-JP.json +51 -2
- package/dist/package/src/locales/zh-CN.json +51 -2
- package/dist/package/src/locales/zh-TW.json +51 -2
- package/dist/package/src/module.ts +3 -3
- package/dist/package/src/rpc/base/v1/config.ts +12 -0
- package/dist/package/src/rpc/system/admin/v1/base_i18n_custom.ts +126 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
- package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
- package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +11 -1
- package/dist/package/src/rpc/system/admin/v1/base_tenant_project.ts +166 -0
- package/dist/package/src/rpc/system/admin/v1/base_tenant_project_grant.ts +128 -0
- package/dist/package/src/views/ai/chat/components/XSender.vue +21 -28
- package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
- package/dist/package/src/views/base/api-log/index.vue +3 -3
- package/dist/package/src/views/base/config/index.vue +3 -3
- package/dist/package/src/views/base/data-access-log/index.vue +3 -3
- package/dist/package/src/views/base/dept/index.vue +10 -45
- package/dist/package/src/views/base/dict/index.vue +3 -3
- package/dist/package/src/views/base/dict/item.vue +3 -3
- package/dist/package/src/views/base/file/index.vue +12 -3
- package/dist/package/src/views/base/i18n-custom/index.vue +321 -0
- package/dist/package/src/views/base/job/index.vue +85 -161
- package/dist/package/src/views/base/login-log/index.vue +13 -4
- package/dist/package/src/views/base/login-policy/index.vue +109 -40
- package/dist/package/src/views/base/menu/index.vue +3 -3
- package/dist/package/src/views/base/message/index.vue +9 -32
- package/dist/package/src/views/base/message-category/index.vue +2 -4
- package/dist/package/src/views/base/oauth-client/index.vue +8 -42
- package/dist/package/src/views/base/online-session/index.vue +8 -27
- package/dist/package/src/views/base/operation-log/index.vue +3 -3
- package/dist/package/src/views/base/permission-log/index.vue +3 -3
- package/dist/package/src/views/base/policy-evaluation-log/index.vue +3 -3
- package/dist/package/src/views/base/post/index.vue +9 -42
- package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
- package/dist/package/src/views/base/redact-rule/index.vue +70 -53
- package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
- package/dist/package/src/views/base/role/index.vue +14 -48
- package/dist/package/src/views/base/tenant/index.vue +163 -19
- package/dist/package/src/views/base/tenant-project/index.vue +12 -0
- package/dist/package/src/views/base/tenant-project-grant/index.vue +338 -0
- package/dist/package/src/views/base/user/index.vue +30 -51
- package/dist/package/src/views/profile/components/login-log.vue +15 -2
- package/dist/package/src/views/profile/components/password.vue +1 -1
- package/dist/package/src/views/profile/components/security.vue +2 -2
- package/dist/package/src/views/tool/code-gen/components/CodeGenLocaleEditor.vue +2 -2
- package/dist/package/src/views/tool/code-gen/config.ts +2 -1
- package/package.json +2 -2
- /package/dist/package/declarations/src/components/{ChangePasswordForm.vue.d.ts → account/ChangePasswordForm.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{ForcedPasswordDialog.vue.d.ts → account/ForcedPasswordDialog.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{Ai.vue.d.ts → ai/Ai.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{DynamicI18nCell.vue.d.ts → i18n/DynamicI18nCell.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{DynamicI18nEditor.vue.d.ts → i18n/DynamicI18nEditor.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{dynamicI18n.d.ts → i18n/dynamicI18n.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{LogTable.vue.d.ts → log/LogTable.vue.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{log.d.ts → log/log.d.ts} +0 -0
- /package/dist/package/declarations/src/components/{Notification.vue.d.ts → notification/Notification.vue.d.ts} +0 -0
- /package/dist/package/src/components/{ChangePasswordForm.vue → account/ChangePasswordForm.vue} +0 -0
- /package/dist/package/src/components/{ForcedPasswordDialog.vue → account/ForcedPasswordDialog.vue} +0 -0
- /package/dist/package/src/components/{Ai.vue → ai/Ai.vue} +0 -0
- /package/dist/package/src/components/{DynamicI18nCell.vue → i18n/DynamicI18nCell.vue} +0 -0
- /package/dist/package/src/components/{DynamicI18nEditor.vue → i18n/DynamicI18nEditor.vue} +0 -0
- /package/dist/package/src/components/{dynamicI18n.ts → i18n/dynamicI18n.ts} +0 -0
- /package/dist/package/src/components/{LogTable.vue → log/LogTable.vue} +0 -0
- /package/dist/package/src/components/{log.ts → log/log.ts} +0 -0
- /package/dist/package/src/components/{Notification.vue → notification/Notification.vue} +0 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="table-box">
|
|
3
|
+
<ProTable
|
|
4
|
+
ref="proTable"
|
|
5
|
+
:key="isDefaultTenant ? 'default-tenant' : 'normal-tenant'"
|
|
6
|
+
row-key="id"
|
|
7
|
+
:columns="columns"
|
|
8
|
+
:header-actions="headerActions"
|
|
9
|
+
:request-api="requestBaseTenantProjectTable"
|
|
10
|
+
>
|
|
11
|
+
<template v-for="slotName in forwardedSlotNames" #[slotName]="scope">
|
|
12
|
+
<slot :name="slotName" v-bind="scope" :tenant-project="resolveTenantProjectSlotContext(scope)" />
|
|
13
|
+
</template>
|
|
14
|
+
</ProTable>
|
|
15
|
+
|
|
16
|
+
<FormDialog
|
|
17
|
+
v-model="dialog.visible"
|
|
18
|
+
ref="formDialogRef"
|
|
19
|
+
:title="t(dialog.titleKey, { resource: t('common.field.project') })"
|
|
20
|
+
width="560px"
|
|
21
|
+
:model="formData"
|
|
22
|
+
:fields="formFields"
|
|
23
|
+
:rules="rules"
|
|
24
|
+
@confirm="handleSubmit"
|
|
25
|
+
@close="handleCloseDialog"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { computed, reactive, ref, useSlots } from "vue";
|
|
32
|
+
import { ElMessage, ElMessageBox } from "element-plus";
|
|
33
|
+
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
34
|
+
import type { ColumnProps, HeaderActionProps, ProTableInstance, RenderScope, TableActionProps } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
35
|
+
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
36
|
+
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
37
|
+
import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
38
|
+
import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
|
|
39
|
+
import { defBaseTenantProjectService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant_project";
|
|
40
|
+
import type { BaseTenantProject, BaseTenantProjectForm, PageBaseTenantProjectRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
41
|
+
import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
42
|
+
import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
|
|
43
|
+
import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
|
|
44
|
+
import { t } from "@liujitcn/kratos-admin-core";
|
|
45
|
+
import {
|
|
46
|
+
mergeTenantProjectExtraData,
|
|
47
|
+
tenantProjectKey,
|
|
48
|
+
type TenantProjectAction,
|
|
49
|
+
type TenantProjectContext,
|
|
50
|
+
type TenantProjectExtraData,
|
|
51
|
+
type TenantProjectManagerProps
|
|
52
|
+
} from "./tenant-project-manager";
|
|
53
|
+
|
|
54
|
+
defineOptions({
|
|
55
|
+
name: "TenantProjectManager",
|
|
56
|
+
inheritAttrs: false
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const props = withDefaults(defineProps<TenantProjectManagerProps>(), {
|
|
60
|
+
extraColumns: () => [],
|
|
61
|
+
extraActions: () => []
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const emit = defineEmits<{
|
|
65
|
+
/** 外部业务字段加载失败。 */
|
|
66
|
+
"extra-data-error": [error: unknown];
|
|
67
|
+
}>();
|
|
68
|
+
|
|
69
|
+
/** 项目表单状态,租户由服务端租户上下文自动补全。 */
|
|
70
|
+
type BaseTenantProjectFormState = Omit<BaseTenantProjectForm, "tenant_id">;
|
|
71
|
+
|
|
72
|
+
const { BUTTONS } = useAuthButtons();
|
|
73
|
+
const slots = useSlots();
|
|
74
|
+
const forwardedSlotNames = computed(() => Object.keys(slots).filter(name => name !== "default"));
|
|
75
|
+
const proTable = ref<ProTableInstance>();
|
|
76
|
+
const formDialogRef = ref<InstanceType<typeof FormDialog>>();
|
|
77
|
+
const extraDataByProject = ref<TenantProjectExtraData>({});
|
|
78
|
+
let extraDataRequestRevision = 0;
|
|
79
|
+
|
|
80
|
+
const dialog = reactive({
|
|
81
|
+
titleKey: "common.action.create_resource",
|
|
82
|
+
visible: false
|
|
83
|
+
});
|
|
84
|
+
const statusOptions = computed<ProFormOption[]>(() => [
|
|
85
|
+
{ label: t("common.status.enabled"), value: Status.STATUS_ENABLE },
|
|
86
|
+
{ label: t("common.status.disabled"), value: Status.STATUS_DISABLE }
|
|
87
|
+
]);
|
|
88
|
+
const formData = reactive<BaseTenantProjectFormState>({
|
|
89
|
+
/** 项目ID。 */
|
|
90
|
+
id: 0,
|
|
91
|
+
/** 项目名称。 */
|
|
92
|
+
name: "",
|
|
93
|
+
/** 项目编号。 */
|
|
94
|
+
code: "",
|
|
95
|
+
/** 显示顺序。 */
|
|
96
|
+
sort: 1,
|
|
97
|
+
/** 状态。 */
|
|
98
|
+
status: Status.STATUS_ENABLE,
|
|
99
|
+
/** 备注。 */
|
|
100
|
+
remark: ""
|
|
101
|
+
});
|
|
102
|
+
const rules = computed(() => ({
|
|
103
|
+
name: [
|
|
104
|
+
{
|
|
105
|
+
required: true,
|
|
106
|
+
message: t("common.validation.required_input", { field: t("system.base.tenant_project.field.name") }),
|
|
107
|
+
trigger: "blur"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
max: 100,
|
|
111
|
+
message: t("common.validation.max_length", { field: t("system.base.tenant_project.field.name"), max: 100 }),
|
|
112
|
+
trigger: "blur"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
code: [
|
|
116
|
+
{
|
|
117
|
+
required: true,
|
|
118
|
+
message: t("common.validation.required_input", { field: t("system.base.tenant_project.field.code") }),
|
|
119
|
+
trigger: "blur"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
max: 50,
|
|
123
|
+
message: t("common.validation.max_length", { field: t("system.base.tenant_project.field.code"), max: 50 }),
|
|
124
|
+
trigger: "blur"
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
sort: [{ required: true, type: "number", min: 1, message: t("common.validation.sort_positive"), trigger: "blur" }],
|
|
128
|
+
status: [
|
|
129
|
+
{
|
|
130
|
+
required: true,
|
|
131
|
+
message: t("common.validation.required_select", { field: t("common.field.status") }),
|
|
132
|
+
trigger: "change"
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
remark: [
|
|
136
|
+
{
|
|
137
|
+
max: 500,
|
|
138
|
+
message: t("common.validation.max_length", { field: t("common.field.remark"), max: 500 }),
|
|
139
|
+
trigger: "blur"
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}));
|
|
143
|
+
|
|
144
|
+
const { isDefaultTenant } = useTenantScope();
|
|
145
|
+
|
|
146
|
+
/** 项目表单字段配置。 */
|
|
147
|
+
const formFields = computed<ProFormField[]>(() => [
|
|
148
|
+
{
|
|
149
|
+
prop: "name",
|
|
150
|
+
label: t("system.base.tenant_project.field.name"),
|
|
151
|
+
component: "input",
|
|
152
|
+
props: { placeholder: t("common.validation.required_input", { field: t("system.base.tenant_project.field.name") }) }
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
prop: "code",
|
|
156
|
+
label: t("system.base.tenant_project.field.code"),
|
|
157
|
+
component: "input",
|
|
158
|
+
props: { placeholder: t("common.validation.required_input", { field: t("system.base.tenant_project.field.code") }) }
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
prop: "sort",
|
|
162
|
+
label: t("common.field.sort"),
|
|
163
|
+
component: "input-number",
|
|
164
|
+
props: { min: 1, precision: 0, step: 1, controlsPosition: "right", style: { width: "100%" } }
|
|
165
|
+
},
|
|
166
|
+
{ prop: "status", label: t("common.field.status"), component: "radio-group", options: statusOptions.value },
|
|
167
|
+
{
|
|
168
|
+
prop: "remark",
|
|
169
|
+
label: t("common.field.remark"),
|
|
170
|
+
component: "textarea",
|
|
171
|
+
props: { placeholder: t("common.placeholder.remark") }
|
|
172
|
+
}
|
|
173
|
+
]);
|
|
174
|
+
|
|
175
|
+
/** 将项目行转换成外部业务模块可用的单条上下文。 */
|
|
176
|
+
function toTenantProjectContext(row: BaseTenantProject): TenantProjectContext {
|
|
177
|
+
const key = tenantProjectKey(row.tenant_id, row.id);
|
|
178
|
+
return {
|
|
179
|
+
tenant_id: row.tenant_id,
|
|
180
|
+
project_id: row.id,
|
|
181
|
+
project: row,
|
|
182
|
+
data: extraDataByProject.value[key] ?? {}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** 将租户项目扩展操作适配成 ProTable 操作。 */
|
|
187
|
+
function toTableAction(action: TenantProjectAction): TableActionProps {
|
|
188
|
+
const { disabled, hidden, params, onClick, ...rest } = action;
|
|
189
|
+
const result: TableActionProps = {
|
|
190
|
+
...rest,
|
|
191
|
+
onClick: (scope, resolvedParams) => onClick(toTenantProjectContext(scope.row as BaseTenantProject), resolvedParams)
|
|
192
|
+
};
|
|
193
|
+
if (disabled !== undefined) result.disabled = scope => resolveProjectBoolean(disabled, scope);
|
|
194
|
+
if (hidden !== undefined) result.hidden = scope => resolveProjectBoolean(hidden, scope);
|
|
195
|
+
if (params) result.params = scope => params(toTenantProjectContext(scope.row as BaseTenantProject));
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** 解析项目扩展操作的布尔配置。 */
|
|
200
|
+
function resolveProjectBoolean(value: boolean | ((context: TenantProjectContext) => boolean), scope: RenderScope): boolean {
|
|
201
|
+
return typeof value === "function" ? value(toTenantProjectContext(scope.row as BaseTenantProject)) : value;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** 项目表格列配置。 */
|
|
205
|
+
const columns = computed<ColumnProps[]>(() => [
|
|
206
|
+
{ type: "selection", width: 55 },
|
|
207
|
+
{ prop: "name", label: t("system.base.tenant_project.field.name"), minWidth: 180, search: { el: "input" } },
|
|
208
|
+
{ prop: "code", label: t("system.base.tenant_project.field.code"), minWidth: 140, search: { el: "input" } },
|
|
209
|
+
{ prop: "sort", label: t("common.field.sort"), minWidth: 90, align: "right" },
|
|
210
|
+
{
|
|
211
|
+
prop: "status",
|
|
212
|
+
label: t("common.field.status"),
|
|
213
|
+
minWidth: 100,
|
|
214
|
+
search: { el: "select" },
|
|
215
|
+
cellType: "status",
|
|
216
|
+
statusProps: {
|
|
217
|
+
activeValue: Status.STATUS_ENABLE,
|
|
218
|
+
inactiveValue: Status.STATUS_DISABLE,
|
|
219
|
+
activeText: t("common.status.enabled"),
|
|
220
|
+
inactiveText: t("common.status.disabled"),
|
|
221
|
+
disabled: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:status"],
|
|
222
|
+
beforeChange: scope => handleBeforeSetStatus(scope.row as BaseTenantProject)
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
{ prop: "remark", label: t("common.field.remark"), minWidth: 160 },
|
|
226
|
+
...props.extraColumns,
|
|
227
|
+
{ prop: "created_at", align: "center", label: t("common.field.created_at"), minWidth: 180 },
|
|
228
|
+
{ prop: "updated_at", label: t("common.field.updated_at"), minWidth: 180, align: "center" },
|
|
229
|
+
{
|
|
230
|
+
prop: "operation",
|
|
231
|
+
label: t("common.field.operation"),
|
|
232
|
+
cellType: "actions",
|
|
233
|
+
actions: [
|
|
234
|
+
...props.extraActions.map(toTableAction),
|
|
235
|
+
{
|
|
236
|
+
label: t("common.action.edit"),
|
|
237
|
+
type: "primary",
|
|
238
|
+
link: true,
|
|
239
|
+
icon: EditPen,
|
|
240
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:update"],
|
|
241
|
+
params: scope => ({ projectId: scope.row.id }),
|
|
242
|
+
onClick: (scope, params) => handleOpenDialog((params?.projectId as number | undefined) ?? (scope.row as BaseTenantProject).id)
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
label: t("common.action.delete"),
|
|
246
|
+
type: "danger",
|
|
247
|
+
link: true,
|
|
248
|
+
icon: Delete,
|
|
249
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:delete"],
|
|
250
|
+
onClick: scope => handleDelete(scope.row as BaseTenantProject)
|
|
251
|
+
}
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
/** 项目顶部按钮配置。 */
|
|
257
|
+
const headerActions = computed<HeaderActionProps[]>(() => [
|
|
258
|
+
{
|
|
259
|
+
label: t("common.action.create"),
|
|
260
|
+
type: "success",
|
|
261
|
+
icon: CirclePlus,
|
|
262
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:create"],
|
|
263
|
+
onClick: () => handleOpenDialog()
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
label: t("common.action.delete"),
|
|
267
|
+
type: "danger",
|
|
268
|
+
icon: Delete,
|
|
269
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:delete"],
|
|
270
|
+
disabled: scope => !scope.selectedList.length,
|
|
271
|
+
onClick: scope => handleDelete(scope.selectedList as BaseTenantProject[])
|
|
272
|
+
}
|
|
273
|
+
]);
|
|
274
|
+
|
|
275
|
+
/** 请求项目分页列表并加载当前页外部业务字段。 */
|
|
276
|
+
async function requestBaseTenantProjectTable(params: PageBaseTenantProjectRequest) {
|
|
277
|
+
const revision = ++extraDataRequestRevision;
|
|
278
|
+
const request = buildPageRequest(params) as PageBaseTenantProjectRequest;
|
|
279
|
+
const data = await defBaseTenantProjectService.PageBaseTenantProject(request);
|
|
280
|
+
const projects = data.base_tenant_projects ?? [];
|
|
281
|
+
if (!props.loadExtraData || !projects.length) {
|
|
282
|
+
if (revision === extraDataRequestRevision) extraDataByProject.value = {};
|
|
283
|
+
return { data: { list: projects, total: data.total } };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const extraData = (await props.loadExtraData({
|
|
288
|
+
projects: projects.map(row => ({ tenant_id: row.tenant_id, project_id: row.id })),
|
|
289
|
+
query: request
|
|
290
|
+
})) ?? {};
|
|
291
|
+
if (revision === extraDataRequestRevision) extraDataByProject.value = extraData;
|
|
292
|
+
return {
|
|
293
|
+
data: {
|
|
294
|
+
list: projects.map(row => mergeTenantProjectExtraData(row, extraData[tenantProjectKey(row.tenant_id, row.id)])),
|
|
295
|
+
total: data.total
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (revision === extraDataRequestRevision) extraDataByProject.value = {};
|
|
300
|
+
emit("extra-data-error", error);
|
|
301
|
+
return { data: { list: projects, total: data.total } };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** 刷新项目表格。 */
|
|
306
|
+
async function refreshTable() {
|
|
307
|
+
await proTable.value?.getTableList();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** 将表格插槽作用域补充为项目上下文。 */
|
|
311
|
+
function resolveTenantProjectSlotContext(scope: Record<string, any>) {
|
|
312
|
+
const row = scope?.row as BaseTenantProject | undefined;
|
|
313
|
+
return row?.id && row.tenant_id ? toTenantProjectContext(row) : undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** 打开项目编辑弹窗。 */
|
|
317
|
+
async function handleOpenDialog(id?: number) {
|
|
318
|
+
resetForm();
|
|
319
|
+
dialog.titleKey = id ? "common.action.edit_resource" : "common.action.create_resource";
|
|
320
|
+
dialog.visible = true;
|
|
321
|
+
if (id) Object.assign(formData, await defBaseTenantProjectService.GetBaseTenantProject({ id }));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 关闭项目弹窗并清理表单。 */
|
|
325
|
+
function handleCloseDialog() {
|
|
326
|
+
dialog.visible = false;
|
|
327
|
+
resetForm();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** 重置项目表单。 */
|
|
331
|
+
function resetForm() {
|
|
332
|
+
formDialogRef.value?.resetFields();
|
|
333
|
+
formDialogRef.value?.clearValidate();
|
|
334
|
+
formData.id = 0;
|
|
335
|
+
formData.name = "";
|
|
336
|
+
formData.code = "";
|
|
337
|
+
formData.sort = 1;
|
|
338
|
+
formData.status = Status.STATUS_ENABLE;
|
|
339
|
+
formData.remark = "";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/** 提交项目表单。 */
|
|
343
|
+
function handleSubmit() {
|
|
344
|
+
formDialogRef.value?.validate()?.then(valid => {
|
|
345
|
+
if (!valid) return;
|
|
346
|
+
const submitData = JSON.parse(JSON.stringify(formData)) as Partial<BaseTenantProjectForm>;
|
|
347
|
+
delete submitData.tenant_id;
|
|
348
|
+
const request = submitData.id
|
|
349
|
+
? defBaseTenantProjectService.UpdateBaseTenantProject({ base_tenant_project: submitData as BaseTenantProjectForm })
|
|
350
|
+
: defBaseTenantProjectService.CreateBaseTenantProject({ base_tenant_project: submitData as BaseTenantProjectForm });
|
|
351
|
+
request.then(() => {
|
|
352
|
+
ElMessage.success(
|
|
353
|
+
t(submitData.id ? "common.message.update_success" : "common.message.create_success", {
|
|
354
|
+
resource: t("common.field.project")
|
|
355
|
+
})
|
|
356
|
+
);
|
|
357
|
+
handleCloseDialog();
|
|
358
|
+
void refreshTable();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** 在项目状态切换前确认并提交。 */
|
|
364
|
+
async function handleBeforeSetStatus(row: BaseTenantProject) {
|
|
365
|
+
const nextStatus = row.status === Status.STATUS_ENABLE ? Status.STATUS_DISABLE : Status.STATUS_ENABLE;
|
|
366
|
+
const text = t(nextStatus === Status.STATUS_ENABLE ? "common.status.enabled" : "common.status.disabled");
|
|
367
|
+
try {
|
|
368
|
+
await ElMessageBox.confirm(
|
|
369
|
+
t("common.dialog.status_change", {
|
|
370
|
+
action: text,
|
|
371
|
+
resource: t("common.field.project"),
|
|
372
|
+
field: t("system.base.tenant_project.field.name"),
|
|
373
|
+
value: row.name || row.code || `ID:${row.id}`
|
|
374
|
+
}),
|
|
375
|
+
t("common.title.notice"),
|
|
376
|
+
{
|
|
377
|
+
confirmButtonText: t("common.action.confirm"),
|
|
378
|
+
cancelButtonText: t("common.action.cancel"),
|
|
379
|
+
type: "warning"
|
|
380
|
+
}
|
|
381
|
+
);
|
|
382
|
+
await defBaseTenantProjectService.SetBaseTenantProjectStatus({ id: row.id, status: nextStatus });
|
|
383
|
+
ElMessage.success(t("common.message.status_success", { action: text }));
|
|
384
|
+
await refreshTable();
|
|
385
|
+
return true;
|
|
386
|
+
} catch {
|
|
387
|
+
return false;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** 删除项目,兼容单条删除与批量删除。 */
|
|
392
|
+
function handleDelete(selected?: number | string | Array<number | string> | BaseTenantProject | BaseTenantProject[]) {
|
|
393
|
+
const projectList = Array.isArray(selected)
|
|
394
|
+
? (selected.filter(item => typeof item === "object") as BaseTenantProject[])
|
|
395
|
+
: selected && typeof selected === "object"
|
|
396
|
+
? [selected as BaseTenantProject]
|
|
397
|
+
: [];
|
|
398
|
+
const projectIds = (
|
|
399
|
+
projectList.length ? projectList.map(item => item.id) : normalizeSelectedIds(selected as number | string | Array<number | string>)
|
|
400
|
+
).join(",");
|
|
401
|
+
if (!projectIds) {
|
|
402
|
+
ElMessage.warning(t("common.message.select_delete_item"));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const confirmMessage =
|
|
406
|
+
projectList.length === 1
|
|
407
|
+
? `${t("common.dialog.delete_single", { resource: t("common.field.project") })}\n${t("common.dialog.resource_field", { field: t("system.base.tenant_project.field.name"), value: projectList[0].name || projectList[0].code || `ID:${projectList[0].id}` })}`
|
|
408
|
+
: projectList.length > 1
|
|
409
|
+
? t("common.dialog.delete_batch", { count: projectList.length, unit: "", resource: t("common.field.project") })
|
|
410
|
+
: t("common.dialog.delete_selected", { resource: t("common.field.project") });
|
|
411
|
+
ElMessageBox.confirm(confirmMessage, t("common.title.warning"), {
|
|
412
|
+
confirmButtonText: t("common.action.confirm"),
|
|
413
|
+
cancelButtonText: t("common.action.cancel"),
|
|
414
|
+
type: "warning"
|
|
415
|
+
}).then(
|
|
416
|
+
() =>
|
|
417
|
+
defBaseTenantProjectService.DeleteBaseTenantProject({ id: projectIds }).then(() => {
|
|
418
|
+
ElMessage.success(t("common.message.delete_success", { resource: t("common.field.project") }));
|
|
419
|
+
void refreshTable();
|
|
420
|
+
}),
|
|
421
|
+
() => ElMessage.info(t("common.dialog.cancel_delete", { resource: t("common.field.project") }))
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
defineExpose({
|
|
426
|
+
/** 刷新项目列表。 */
|
|
427
|
+
refresh: refreshTable,
|
|
428
|
+
/** 兼容表格实例的刷新方法名称。 */
|
|
429
|
+
getTableList: refreshTable
|
|
430
|
+
});
|
|
431
|
+
</script>
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<el-tree-select
|
|
3
|
+
v-model="selectedValue"
|
|
4
|
+
:data="treeOptions"
|
|
5
|
+
:props="treeProps"
|
|
6
|
+
node-key="value"
|
|
7
|
+
check-strictly
|
|
8
|
+
filterable
|
|
9
|
+
clearable
|
|
10
|
+
render-after-expand
|
|
11
|
+
v-bind="$attrs"
|
|
12
|
+
/>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { computed, onMounted, ref } from "vue";
|
|
17
|
+
import { defBaseTenantProjectService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant_project";
|
|
18
|
+
import type { TreeBaseTenantProjectResponse_Option } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
19
|
+
|
|
20
|
+
defineOptions({ name: "TenantProjectSelect", inheritAttrs: false });
|
|
21
|
+
|
|
22
|
+
/** 租户项目选择值。 */
|
|
23
|
+
export interface TenantProjectSelection {
|
|
24
|
+
/** 节点唯一值。 */
|
|
25
|
+
value?: string;
|
|
26
|
+
/** 目标租户ID。 */
|
|
27
|
+
tenant_id?: number;
|
|
28
|
+
/** 目标项目ID。 */
|
|
29
|
+
project_id?: number;
|
|
30
|
+
/** 节点类型。 */
|
|
31
|
+
type?: "tenant" | "project";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 租户项目选择组件属性。 */
|
|
35
|
+
interface TenantProjectSelectProps {
|
|
36
|
+
/** 当前选中的租户项目。 */
|
|
37
|
+
modelValue?: TenantProjectSelection;
|
|
38
|
+
/** 外部传入的租户项目树。 */
|
|
39
|
+
options?: TreeBaseTenantProjectResponse_Option[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const props = withDefaults(defineProps<TenantProjectSelectProps>(), {
|
|
43
|
+
modelValue: undefined,
|
|
44
|
+
options: undefined
|
|
45
|
+
});
|
|
46
|
+
const emit = defineEmits<{
|
|
47
|
+
/** 更新租户项目选择。 */
|
|
48
|
+
(event: "update:modelValue", value: TenantProjectSelection | undefined): void;
|
|
49
|
+
/** 租户项目选择变化。 */
|
|
50
|
+
(event: "change", value: TenantProjectSelection | undefined): void;
|
|
51
|
+
}>();
|
|
52
|
+
|
|
53
|
+
const loadedOptions = ref<TreeBaseTenantProjectResponse_Option[]>([]);
|
|
54
|
+
const treeOptions = computed(() => props.options ?? loadedOptions.value);
|
|
55
|
+
const treeProps = { label: "label", value: "value", children: "children" };
|
|
56
|
+
|
|
57
|
+
const selectedValue = computed<string | undefined>({
|
|
58
|
+
get: () => props.modelValue?.value,
|
|
59
|
+
set: value => {
|
|
60
|
+
const selection = parseSelection(value);
|
|
61
|
+
emit("update:modelValue", selection);
|
|
62
|
+
emit("change", selection);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
onMounted(async () => {
|
|
67
|
+
if (props.options) return;
|
|
68
|
+
const response = await defBaseTenantProjectService.TreeBaseTenantProject({ keyword: "" });
|
|
69
|
+
loadedOptions.value = response.list ?? [];
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function parseSelection(value?: string): TenantProjectSelection | undefined {
|
|
73
|
+
if (!value) return undefined;
|
|
74
|
+
const [type, tenantId, projectId] = value.split(":");
|
|
75
|
+
if (type === "tenant") {
|
|
76
|
+
return { value, type, tenant_id: Number(tenantId) };
|
|
77
|
+
}
|
|
78
|
+
if (type === "project") {
|
|
79
|
+
return { value, type, tenant_id: Number(tenantId), project_id: Number(projectId) };
|
|
80
|
+
}
|
|
81
|
+
return { value };
|
|
82
|
+
}
|
|
83
|
+
</script>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span>{{ label }}</span>
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { computed } from "vue";
|
|
7
|
+
import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
|
|
8
|
+
import type { SelectOptionResponse_Option } from "@liujitcn/kratos-admin-core/rpc/common/v1/common";
|
|
9
|
+
|
|
10
|
+
defineOptions({ name: "TenantProjectText" });
|
|
11
|
+
|
|
12
|
+
/** 租户项目展示组件属性。 */
|
|
13
|
+
interface TenantProjectTextProps {
|
|
14
|
+
/** 包含租户和项目字段的列表行。 */
|
|
15
|
+
row: Record<string, any>;
|
|
16
|
+
/** 项目名称字段。 */
|
|
17
|
+
projectField?: string;
|
|
18
|
+
/** 租户名称字段。 */
|
|
19
|
+
tenantField?: string;
|
|
20
|
+
/** 租户选项,用于只有租户ID时解析名称。 */
|
|
21
|
+
tenantOptions?: SelectOptionResponse_Option[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(defineProps<TenantProjectTextProps>(), {
|
|
25
|
+
projectField: "project_name",
|
|
26
|
+
tenantField: "tenant_name",
|
|
27
|
+
tenantOptions: () => []
|
|
28
|
+
});
|
|
29
|
+
const { isDefaultTenant, resolveTenantLabel } = useTenantScope();
|
|
30
|
+
|
|
31
|
+
const label = computed(() => {
|
|
32
|
+
const projectName = props.row[props.projectField] ?? props.row.project_name ?? props.row.name ?? props.row.project_id ?? "";
|
|
33
|
+
if (!isDefaultTenant.value) return String(projectName);
|
|
34
|
+
const tenantName = props.row[props.tenantField] ?? resolveTenantLabel(props.row, "tenant_id", props.tenantOptions);
|
|
35
|
+
return tenantName ? `${tenantName} / ${projectName}` : String(projectName);
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { BaseTenantProject } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
|
+
|
|
3
|
+
/** 租户项目业务数据查询键。 */
|
|
4
|
+
export interface TenantProjectKey {
|
|
5
|
+
/** 租户 ID。 */
|
|
6
|
+
tenant_id: number;
|
|
7
|
+
/** 公共项目 ID。 */
|
|
8
|
+
project_id: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 租户项目扩展数据,外层键由租户 ID 和项目 ID 组成。 */
|
|
12
|
+
export type TenantProjectExtraData = Record<string, Record<string, unknown>>;
|
|
13
|
+
|
|
14
|
+
/** 创建租户项目扩展数据的稳定键。 */
|
|
15
|
+
export function tenantProjectKey(tenantId: number, projectId: number): string {
|
|
16
|
+
return `${tenantId}:${projectId}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 将外部业务字段合并到项目行,同时保护公共项目字段。 */
|
|
20
|
+
export function mergeTenantProjectExtraData(project: BaseTenantProject, extra?: Record<string, unknown>): BaseTenantProject & Record<string, unknown> {
|
|
21
|
+
if (!extra) return { ...project } as BaseTenantProject & Record<string, unknown>;
|
|
22
|
+
|
|
23
|
+
const protectedFields = new Set(["id", "tenant_id", "code", "name", "status", "sort", "remark", "created_at", "updated_at"]);
|
|
24
|
+
const safeExtra = Object.fromEntries(Object.entries(extra).filter(([key]) => !protectedFields.has(key)));
|
|
25
|
+
return { ...project, ...safeExtra };
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { BaseTenantProject, PageBaseTenantProjectRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
|
+
import type { ColumnProps, TableActionProps } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
3
|
+
import type { TenantProjectExtraData, TenantProjectKey } from "./tenant-project-manager-data";
|
|
4
|
+
|
|
5
|
+
export { mergeTenantProjectExtraData, tenantProjectKey, type TenantProjectExtraData, type TenantProjectKey } from "./tenant-project-manager-data";
|
|
6
|
+
|
|
7
|
+
/** 租户项目单条业务上下文。 */
|
|
8
|
+
export interface TenantProjectContext<TData extends Record<string, unknown> = Record<string, unknown>> extends TenantProjectKey {
|
|
9
|
+
/** 公共项目基础数据。 */
|
|
10
|
+
project: BaseTenantProject;
|
|
11
|
+
/** 外部业务模块返回的当前项目数据。 */
|
|
12
|
+
data: TData;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 租户项目扩展字段批量加载上下文。 */
|
|
16
|
+
export interface TenantProjectExtraDataLoadContext {
|
|
17
|
+
/** 当前页全部项目键。 */
|
|
18
|
+
projects: TenantProjectKey[];
|
|
19
|
+
/** 当前项目列表查询参数。 */
|
|
20
|
+
query: PageBaseTenantProjectRequest;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 租户项目扩展数据加载器。 */
|
|
24
|
+
export type TenantProjectExtraDataLoader = (
|
|
25
|
+
context: TenantProjectExtraDataLoadContext
|
|
26
|
+
) => Promise<TenantProjectExtraData | undefined>;
|
|
27
|
+
|
|
28
|
+
/** 租户项目扩展操作按钮。 */
|
|
29
|
+
export type TenantProjectAction = Omit<TableActionProps, "disabled" | "hidden" | "params" | "onClick"> & {
|
|
30
|
+
/** 按项目上下文控制按钮显隐。 */
|
|
31
|
+
hidden?: boolean | ((context: TenantProjectContext) => boolean);
|
|
32
|
+
/** 按项目上下文控制按钮禁用。 */
|
|
33
|
+
disabled?: boolean | ((context: TenantProjectContext) => boolean);
|
|
34
|
+
/** 根据项目上下文生成按钮附加参数。 */
|
|
35
|
+
params?: (context: TenantProjectContext) => Record<string, any>;
|
|
36
|
+
/** 执行项目扩展操作。 */
|
|
37
|
+
onClick: (context: TenantProjectContext, params?: Record<string, any>) => void | Promise<void>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** 租户项目管理组件属性。 */
|
|
41
|
+
export interface TenantProjectManagerProps {
|
|
42
|
+
/** 追加到基础列表的表格列。 */
|
|
43
|
+
extraColumns?: ColumnProps[];
|
|
44
|
+
/** 追加到基础操作列的业务操作。 */
|
|
45
|
+
extraActions?: TenantProjectAction[];
|
|
46
|
+
/** 加载当前页外部业务字段。 */
|
|
47
|
+
loadExtraData?: TenantProjectExtraDataLoader;
|
|
48
|
+
}
|