@liujitcn/kratos-admin-system 0.0.51 → 0.0.52
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 +1 -1
- package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts +1 -2
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts +10 -0
- package/dist/package/declarations/src/components/tenant-project/tenant-project-manager.d.ts +8 -3
- package/dist/package/declarations/src/index.d.ts +1 -1
- package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +6 -2
- package/dist/package/src/components/tenant-project/TenantProjectManager.vue +95 -57
- package/dist/package/src/components/tenant-project/tenant-project-manager-data.ts +39 -1
- package/dist/package/src/components/tenant-project/tenant-project-manager.ts +19 -4
- package/dist/package/src/index.ts +2 -0
- package/dist/package/src/views/ai/chat/components/XSender.vue +1 -36
- package/dist/package/src/views/ai/chat/components/speech-recognition.ts +28 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -153,7 +153,7 @@ export const adminModules = [systemAdminModule];
|
|
|
153
153
|
|
|
154
154
|
跨模块跳转使用 Vue Router;复用 System 代码时只引用 `package.json#exports` 公开的 npm 子路径。
|
|
155
155
|
|
|
156
|
-
租户项目管理组件通过 `TenantProjectManager`
|
|
156
|
+
租户项目管理组件通过 `TenantProjectManager` 公开。默认租户创建项目时在表单中选择目标租户,普通租户由服务端自动使用当前租户;默认租户查询全部项目,普通租户按项目授权范围查询。外部业务模块可以传入 `extraColumns`、`extraActions` 和 `loadExtraData`:扩展列可通过 `after` 指定插入到哪个基础字段后面,列的同名表格插槽会沿用该位置,未配置时默认插入备注后面,例如 `{ prop: "address", after: "name" }` 配合 `#address` 插槽即可把内容放到项目名称后面;数据加载器接收当前页的 `tenant_id + project_id` 集合,单条操作回调接收同一组双键以及项目基础数据。组件也会透传表格具名插槽,并向行插槽增加 `tenantProject` 上下文;保存业务配置后可通过组件实例的 `refresh()` 刷新列表。按外部业务字段搜索或排序时,应由外部接口参与分页查询,不能只在当前页加载后过滤。
|
|
157
157
|
|
|
158
158
|
外部项目可以在自己的页面中包装该组件,并通过菜单将项目管理页面的 `component` 指向包装页面:
|
|
159
159
|
|
package/dist/package/declarations/src/components/tenant-project/TenantProjectManager.vue.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ColumnProps } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
2
1
|
import { type TenantProjectAction, type TenantProjectManagerProps } from "./tenant-project-manager";
|
|
3
2
|
/** 刷新项目表格。 */
|
|
4
3
|
declare function refreshTable(): Promise<void>;
|
|
@@ -16,7 +15,7 @@ declare const __VLS_base: import("vue").DefineComponent<TenantProjectManagerProp
|
|
|
16
15
|
}, string, import("vue").PublicProps, Readonly<TenantProjectManagerProps> & Readonly<{
|
|
17
16
|
"onExtra-data-error"?: ((error: unknown) => any) | undefined;
|
|
18
17
|
}>, {
|
|
19
|
-
extraColumns:
|
|
18
|
+
extraColumns: import("./tenant-project-manager").TenantProjectExtraColumn[];
|
|
20
19
|
extraActions: TenantProjectAction[];
|
|
21
20
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
21
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
package/dist/package/declarations/src/components/tenant-project/tenant-project-manager-data.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { BaseTenantProject } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
|
+
/** 租户项目表格列的最小结构,用于在不依赖表格组件的情况下排列扩展列。 */
|
|
3
|
+
type TenantProjectColumnLike = {
|
|
4
|
+
prop?: string;
|
|
5
|
+
type?: string;
|
|
6
|
+
};
|
|
2
7
|
/** 租户项目业务数据查询键。 */
|
|
3
8
|
export interface TenantProjectKey {
|
|
4
9
|
/** 租户 ID。 */
|
|
@@ -8,7 +13,12 @@ export interface TenantProjectKey {
|
|
|
8
13
|
}
|
|
9
14
|
/** 租户项目扩展数据,外层键由租户 ID 和项目 ID 组成。 */
|
|
10
15
|
export type TenantProjectExtraData = Record<string, Record<string, unknown>>;
|
|
16
|
+
/** 按基础字段位置排列租户项目管理的扩展列。 */
|
|
17
|
+
export declare function arrangeTenantProjectColumns<T extends TenantProjectColumnLike>(baseColumns: T[], extraColumns: Array<T & {
|
|
18
|
+
after?: string;
|
|
19
|
+
}>): T[];
|
|
11
20
|
/** 创建租户项目扩展数据的稳定键。 */
|
|
12
21
|
export declare function tenantProjectKey(tenantId: number, projectId: number): string;
|
|
13
22
|
/** 将外部业务字段合并到项目行,同时保护公共项目字段。 */
|
|
14
23
|
export declare function mergeTenantProjectExtraData(project: BaseTenantProject, extra?: Record<string, unknown>): BaseTenantProject & Record<string, unknown>;
|
|
24
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { BaseTenantProject, PageBaseTenantProjectRequest } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
2
|
import type { ColumnProps, TableActionProps } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
3
3
|
import type { TenantProjectExtraData, TenantProjectKey } from "./tenant-project-manager-data";
|
|
4
|
-
export { mergeTenantProjectExtraData, tenantProjectKey, type TenantProjectExtraData, type TenantProjectKey } from "./tenant-project-manager-data";
|
|
4
|
+
export { arrangeTenantProjectColumns, mergeTenantProjectExtraData, tenantProjectKey, type TenantProjectExtraData, type TenantProjectKey } from "./tenant-project-manager-data";
|
|
5
5
|
/** 租户项目单条业务上下文。 */
|
|
6
6
|
export interface TenantProjectContext<TData extends Record<string, unknown> = Record<string, unknown>> extends TenantProjectKey {
|
|
7
7
|
/** 公共项目基础数据。 */
|
|
@@ -29,10 +29,15 @@ export type TenantProjectAction = Omit<TableActionProps, "disabled" | "hidden" |
|
|
|
29
29
|
/** 执行项目扩展操作。 */
|
|
30
30
|
onClick: (context: TenantProjectContext, params?: Record<string, any>) => void | Promise<void>;
|
|
31
31
|
};
|
|
32
|
+
/** 租户项目管理的扩展列配置。 */
|
|
33
|
+
export type TenantProjectExtraColumn = ColumnProps & {
|
|
34
|
+
/** 插入到指定基础字段后面,未配置时默认插入备注字段后面。 */
|
|
35
|
+
after?: string;
|
|
36
|
+
};
|
|
32
37
|
/** 租户项目管理组件属性。 */
|
|
33
38
|
export interface TenantProjectManagerProps {
|
|
34
|
-
/**
|
|
35
|
-
extraColumns?:
|
|
39
|
+
/** 追加到基础列表的表格列,可通过 after 指定插入位置。 */
|
|
40
|
+
extraColumns?: TenantProjectExtraColumn[];
|
|
36
41
|
/** 追加到基础操作列的业务操作。 */
|
|
37
42
|
extraActions?: TenantProjectAction[];
|
|
38
43
|
/** 加载当前页外部业务字段。 */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ADMIN_AI_EXTENSION, getAdminAiExtension } from "./ai";
|
|
2
2
|
export type { AdminAiExtension } from "./ai";
|
|
3
3
|
export { default as TenantProjectManager } from "./components/tenant-project/TenantProjectManager.vue";
|
|
4
|
-
export { mergeTenantProjectExtraData, tenantProjectKey, type TenantProjectAction, type TenantProjectContext, type TenantProjectExtraData, type TenantProjectExtraDataLoadContext, type TenantProjectExtraDataLoader, type TenantProjectKey, type TenantProjectManagerProps } from "./components/tenant-project/tenant-project-manager";
|
|
4
|
+
export { mergeTenantProjectExtraData, arrangeTenantProjectColumns, tenantProjectKey, type TenantProjectAction, type TenantProjectContext, type TenantProjectExtraColumn, type TenantProjectExtraData, type TenantProjectExtraDataLoadContext, type TenantProjectExtraDataLoader, type TenantProjectKey, type TenantProjectManagerProps } from "./components/tenant-project/tenant-project-manager";
|
|
5
5
|
export { systemAdminModule } from "./module";
|
|
@@ -3,6 +3,7 @@ export type SpeechResultList = ArrayLike<{
|
|
|
3
3
|
0?: {
|
|
4
4
|
transcript: string;
|
|
5
5
|
};
|
|
6
|
+
isFinal?: boolean;
|
|
6
7
|
length: number;
|
|
7
8
|
}>;
|
|
8
9
|
/** 语音识别错误的最小结构,兼容浏览器事件和不支持错误。 */
|
|
@@ -21,8 +22,11 @@ type SpeechRecognitionOptions = {
|
|
|
21
22
|
/** 识别结果回调。 */
|
|
22
23
|
onResult?: (text: string) => void;
|
|
23
24
|
};
|
|
24
|
-
/**
|
|
25
|
-
export declare function
|
|
25
|
+
/** 增量累积语音识别结果,避免连续模式下 interim 结果重复计入已确认文本。 */
|
|
26
|
+
export declare function accumulateSpeechResultText(results: SpeechResultList, resultIndex: number, previousFinalText: string): {
|
|
27
|
+
finalText: string;
|
|
28
|
+
text: string;
|
|
29
|
+
};
|
|
26
30
|
/** 创建浏览器语音识别实例,并统一结果、状态和卸载处理。 */
|
|
27
31
|
export declare function useSpeechRecognition({ onError, onStart, onEnd, onResult }?: SpeechRecognitionOptions): {
|
|
28
32
|
loading: globalThis.Ref<boolean, boolean>;
|
|
@@ -28,20 +28,31 @@
|
|
|
28
28
|
</template>
|
|
29
29
|
|
|
30
30
|
<script setup lang="ts">
|
|
31
|
-
import { computed, reactive, ref, useSlots } from "vue";
|
|
31
|
+
import { computed, onMounted, reactive, ref, useSlots } from "vue";
|
|
32
32
|
import { ElMessage, ElMessageBox } from "element-plus";
|
|
33
33
|
import { CirclePlus, Delete, EditPen } from "@element-plus/icons-vue";
|
|
34
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
ColumnProps,
|
|
36
|
+
HeaderActionProps,
|
|
37
|
+
ProTableInstance,
|
|
38
|
+
RenderScope,
|
|
39
|
+
TableActionProps
|
|
40
|
+
} from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
35
41
|
import ProTable from "@liujitcn/kratos-admin-core/components/ProTable";
|
|
36
42
|
import FormDialog from "@liujitcn/kratos-admin-core/components/Dialog/FormDialog.vue";
|
|
37
43
|
import type { ProFormField, ProFormOption } from "@liujitcn/kratos-admin-core/components/ProForm/interface";
|
|
38
44
|
import { useAuthButtons } from "@liujitcn/kratos-admin-core/auth";
|
|
39
45
|
import { defBaseTenantProjectService } from "@liujitcn/kratos-admin-system/api/system/admin/v1/base_tenant_project";
|
|
40
|
-
import type {
|
|
46
|
+
import type {
|
|
47
|
+
BaseTenantProject,
|
|
48
|
+
BaseTenantProjectForm,
|
|
49
|
+
PageBaseTenantProjectRequest
|
|
50
|
+
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
41
51
|
import { Status } from "@liujitcn/kratos-admin-system/rpc/common/v1/enum";
|
|
42
52
|
import { buildPageRequest, normalizeSelectedIds } from "@liujitcn/kratos-admin-core/table";
|
|
43
53
|
import { useTenantScope } from "@liujitcn/kratos-admin-core/tenant";
|
|
44
54
|
import { t } from "@liujitcn/kratos-admin-core";
|
|
55
|
+
import { arrangeTenantProjectColumns } from "./tenant-project-manager-data";
|
|
45
56
|
import {
|
|
46
57
|
mergeTenantProjectExtraData,
|
|
47
58
|
tenantProjectKey,
|
|
@@ -66,8 +77,11 @@ const emit = defineEmits<{
|
|
|
66
77
|
"extra-data-error": [error: unknown];
|
|
67
78
|
}>();
|
|
68
79
|
|
|
69
|
-
/**
|
|
70
|
-
type BaseTenantProjectFormState = Omit<BaseTenantProjectForm, "tenant_id"
|
|
80
|
+
/** 项目表单状态,默认租户创建时必须选择目标租户。 */
|
|
81
|
+
type BaseTenantProjectFormState = Omit<BaseTenantProjectForm, "tenant_id"> & {
|
|
82
|
+
/** 项目所属租户。 */
|
|
83
|
+
tenant_id?: number;
|
|
84
|
+
};
|
|
71
85
|
|
|
72
86
|
const { BUTTONS } = useAuthButtons();
|
|
73
87
|
const slots = useSlots();
|
|
@@ -88,6 +102,8 @@ const statusOptions = computed<ProFormOption[]>(() => [
|
|
|
88
102
|
const formData = reactive<BaseTenantProjectFormState>({
|
|
89
103
|
/** 项目ID。 */
|
|
90
104
|
id: 0,
|
|
105
|
+
/** 项目所属租户。 */
|
|
106
|
+
tenant_id: undefined,
|
|
91
107
|
/** 项目名称。 */
|
|
92
108
|
name: "",
|
|
93
109
|
/** 项目编号。 */
|
|
@@ -100,6 +116,13 @@ const formData = reactive<BaseTenantProjectFormState>({
|
|
|
100
116
|
remark: ""
|
|
101
117
|
});
|
|
102
118
|
const rules = computed(() => ({
|
|
119
|
+
tenant_id: [
|
|
120
|
+
{
|
|
121
|
+
required: true,
|
|
122
|
+
message: t("common.validation.required_select", { field: t("common.field.tenant") }),
|
|
123
|
+
trigger: "change"
|
|
124
|
+
}
|
|
125
|
+
],
|
|
103
126
|
name: [
|
|
104
127
|
{
|
|
105
128
|
required: true,
|
|
@@ -141,10 +164,14 @@ const rules = computed(() => ({
|
|
|
141
164
|
]
|
|
142
165
|
}));
|
|
143
166
|
|
|
144
|
-
const { isDefaultTenant } = useTenantScope();
|
|
167
|
+
const { isDefaultTenant, tenantColumns, tenantFormField, toRequestTenantId, loadTenantOptions } = useTenantScope();
|
|
168
|
+
onMounted(() => {
|
|
169
|
+
if (isDefaultTenant.value) void loadTenantOptions();
|
|
170
|
+
});
|
|
145
171
|
|
|
146
172
|
/** 项目表单字段配置。 */
|
|
147
173
|
const formFields = computed<ProFormField[]>(() => [
|
|
174
|
+
tenantFormField({ label: t("common.field.tenant"), disabledOnEdit: true }),
|
|
148
175
|
{
|
|
149
176
|
prop: "name",
|
|
150
177
|
label: t("system.base.tenant_project.field.name"),
|
|
@@ -202,56 +229,60 @@ function resolveProjectBoolean(value: boolean | ((context: TenantProjectContext)
|
|
|
202
229
|
}
|
|
203
230
|
|
|
204
231
|
/** 项目表格列配置。 */
|
|
205
|
-
const columns = computed<ColumnProps[]>(() =>
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
prop: "
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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)
|
|
232
|
+
const columns = computed<ColumnProps[]>(() => {
|
|
233
|
+
const baseColumns: ColumnProps[] = [
|
|
234
|
+
{ type: "selection", width: 55 },
|
|
235
|
+
...tenantColumns({ label: t("common.field.tenant"), order: 1 }),
|
|
236
|
+
{ prop: "name", label: t("system.base.tenant_project.field.name"), minWidth: 180, search: { el: "input" } },
|
|
237
|
+
{ prop: "code", label: t("system.base.tenant_project.field.code"), minWidth: 140, search: { el: "input" } },
|
|
238
|
+
{ prop: "sort", label: t("common.field.sort"), minWidth: 90, align: "right" },
|
|
239
|
+
{
|
|
240
|
+
prop: "status",
|
|
241
|
+
label: t("common.field.status"),
|
|
242
|
+
minWidth: 100,
|
|
243
|
+
search: { el: "select" },
|
|
244
|
+
cellType: "status",
|
|
245
|
+
statusProps: {
|
|
246
|
+
activeValue: Status.STATUS_ENABLE,
|
|
247
|
+
inactiveValue: Status.STATUS_DISABLE,
|
|
248
|
+
activeText: t("common.status.enabled"),
|
|
249
|
+
inactiveText: t("common.status.disabled"),
|
|
250
|
+
disabled: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:status"],
|
|
251
|
+
beforeChange: scope => handleBeforeSetStatus(scope.row as BaseTenantProject)
|
|
251
252
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
253
|
+
},
|
|
254
|
+
{ prop: "remark", label: t("common.field.remark"), minWidth: 160 },
|
|
255
|
+
{ prop: "created_at", align: "center", label: t("common.field.created_at"), minWidth: 180 },
|
|
256
|
+
{ prop: "updated_at", label: t("common.field.updated_at"), minWidth: 180, align: "center" },
|
|
257
|
+
{
|
|
258
|
+
prop: "operation",
|
|
259
|
+
label: t("common.field.operation"),
|
|
260
|
+
cellType: "actions",
|
|
261
|
+
actions: [
|
|
262
|
+
...props.extraActions.map(toTableAction),
|
|
263
|
+
{
|
|
264
|
+
label: t("common.action.edit"),
|
|
265
|
+
type: "primary",
|
|
266
|
+
link: true,
|
|
267
|
+
icon: EditPen,
|
|
268
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:update"],
|
|
269
|
+
params: scope => ({ projectId: scope.row.id }),
|
|
270
|
+
onClick: (scope, params) =>
|
|
271
|
+
handleOpenDialog((params?.projectId as number | undefined) ?? (scope.row as BaseTenantProject).id)
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
label: t("common.action.delete"),
|
|
275
|
+
type: "danger",
|
|
276
|
+
link: true,
|
|
277
|
+
icon: Delete,
|
|
278
|
+
hidden: () => !isDefaultTenant.value || !BUTTONS.value["base:tenant:project:delete"],
|
|
279
|
+
onClick: scope => handleDelete(scope.row as BaseTenantProject)
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
];
|
|
284
|
+
return arrangeTenantProjectColumns(baseColumns, props.extraColumns);
|
|
285
|
+
});
|
|
255
286
|
|
|
256
287
|
/** 项目顶部按钮配置。 */
|
|
257
288
|
const headerActions = computed<HeaderActionProps[]>(() => [
|
|
@@ -275,7 +306,10 @@ const headerActions = computed<HeaderActionProps[]>(() => [
|
|
|
275
306
|
/** 请求项目分页列表并加载当前页外部业务字段。 */
|
|
276
307
|
async function requestBaseTenantProjectTable(params: PageBaseTenantProjectRequest) {
|
|
277
308
|
const revision = ++extraDataRequestRevision;
|
|
278
|
-
const request =
|
|
309
|
+
const request = {
|
|
310
|
+
...buildPageRequest(params),
|
|
311
|
+
tenant_id: toRequestTenantId(params.tenant_id)
|
|
312
|
+
} as PageBaseTenantProjectRequest;
|
|
279
313
|
const data = await defBaseTenantProjectService.PageBaseTenantProject(request);
|
|
280
314
|
const projects = data.base_tenant_projects ?? [];
|
|
281
315
|
if (!props.loadExtraData || !projects.length) {
|
|
@@ -316,6 +350,7 @@ function resolveTenantProjectSlotContext(scope: Record<string, any>) {
|
|
|
316
350
|
/** 打开项目编辑弹窗。 */
|
|
317
351
|
async function handleOpenDialog(id?: number) {
|
|
318
352
|
resetForm();
|
|
353
|
+
await loadTenantOptions();
|
|
319
354
|
dialog.titleKey = id ? "common.action.edit_resource" : "common.action.create_resource";
|
|
320
355
|
dialog.visible = true;
|
|
321
356
|
if (id) Object.assign(formData, await defBaseTenantProjectService.GetBaseTenantProject({ id }));
|
|
@@ -332,6 +367,7 @@ function resetForm() {
|
|
|
332
367
|
formDialogRef.value?.resetFields();
|
|
333
368
|
formDialogRef.value?.clearValidate();
|
|
334
369
|
formData.id = 0;
|
|
370
|
+
formData.tenant_id = undefined;
|
|
335
371
|
formData.name = "";
|
|
336
372
|
formData.code = "";
|
|
337
373
|
formData.sort = 1;
|
|
@@ -344,7 +380,9 @@ function handleSubmit() {
|
|
|
344
380
|
formDialogRef.value?.validate()?.then(valid => {
|
|
345
381
|
if (!valid) return;
|
|
346
382
|
const submitData = JSON.parse(JSON.stringify(formData)) as Partial<BaseTenantProjectForm>;
|
|
347
|
-
|
|
383
|
+
const tenantId = toRequestTenantId(formData.tenant_id);
|
|
384
|
+
if (tenantId === undefined) delete submitData.tenant_id;
|
|
385
|
+
else submitData.tenant_id = tenantId;
|
|
348
386
|
const request = submitData.id
|
|
349
387
|
? defBaseTenantProjectService.UpdateBaseTenantProject({ base_tenant_project: submitData as BaseTenantProjectForm })
|
|
350
388
|
: defBaseTenantProjectService.CreateBaseTenantProject({ base_tenant_project: submitData as BaseTenantProjectForm });
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { BaseTenantProject } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
2
|
|
|
3
|
+
/** 租户项目表格列的最小结构,用于在不依赖表格组件的情况下排列扩展列。 */
|
|
4
|
+
type TenantProjectColumnLike = {
|
|
5
|
+
prop?: string;
|
|
6
|
+
type?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
3
9
|
/** 租户项目业务数据查询键。 */
|
|
4
10
|
export interface TenantProjectKey {
|
|
5
11
|
/** 租户 ID。 */
|
|
@@ -11,13 +17,45 @@ export interface TenantProjectKey {
|
|
|
11
17
|
/** 租户项目扩展数据,外层键由租户 ID 和项目 ID 组成。 */
|
|
12
18
|
export type TenantProjectExtraData = Record<string, Record<string, unknown>>;
|
|
13
19
|
|
|
20
|
+
/** 按基础字段位置排列租户项目管理的扩展列。 */
|
|
21
|
+
export function arrangeTenantProjectColumns<T extends TenantProjectColumnLike>(
|
|
22
|
+
baseColumns: T[],
|
|
23
|
+
extraColumns: Array<T & { after?: string }>
|
|
24
|
+
): T[] {
|
|
25
|
+
const baseKeys = new Set(baseColumns.map(column => column.prop ?? column.type));
|
|
26
|
+
const extrasByAnchor = new Map<string | undefined, T[]>();
|
|
27
|
+
for (const extraColumn of extraColumns) {
|
|
28
|
+
const { after, ...column } = extraColumn;
|
|
29
|
+
const anchor = after && baseKeys.has(after) ? after : "remark";
|
|
30
|
+
const columns = extrasByAnchor.get(anchor) ?? [];
|
|
31
|
+
columns.push(column as T);
|
|
32
|
+
extrasByAnchor.set(anchor, columns);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result: T[] = [];
|
|
36
|
+
for (const baseColumn of baseColumns) {
|
|
37
|
+
result.push(baseColumn);
|
|
38
|
+
const anchor = baseColumn.prop ?? baseColumn.type;
|
|
39
|
+
const columns = extrasByAnchor.get(anchor);
|
|
40
|
+
if (!columns) continue;
|
|
41
|
+
result.push(...columns);
|
|
42
|
+
extrasByAnchor.delete(anchor);
|
|
43
|
+
}
|
|
44
|
+
const remaining: T[] = [];
|
|
45
|
+
for (const columns of extrasByAnchor.values()) remaining.push(...columns);
|
|
46
|
+
return [...result, ...remaining];
|
|
47
|
+
}
|
|
48
|
+
|
|
14
49
|
/** 创建租户项目扩展数据的稳定键。 */
|
|
15
50
|
export function tenantProjectKey(tenantId: number, projectId: number): string {
|
|
16
51
|
return `${tenantId}:${projectId}`;
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
/** 将外部业务字段合并到项目行,同时保护公共项目字段。 */
|
|
20
|
-
export function mergeTenantProjectExtraData(
|
|
55
|
+
export function mergeTenantProjectExtraData(
|
|
56
|
+
project: BaseTenantProject,
|
|
57
|
+
extra?: Record<string, unknown>
|
|
58
|
+
): BaseTenantProject & Record<string, unknown> {
|
|
21
59
|
if (!extra) return { ...project } as BaseTenantProject & Record<string, unknown>;
|
|
22
60
|
|
|
23
61
|
const protectedFields = new Set(["id", "tenant_id", "code", "name", "status", "sort", "remark", "created_at", "updated_at"]);
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BaseTenantProject,
|
|
3
|
+
PageBaseTenantProjectRequest
|
|
4
|
+
} from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_tenant_project";
|
|
2
5
|
import type { ColumnProps, TableActionProps } from "@liujitcn/kratos-admin-core/components/ProTable/interface";
|
|
3
6
|
import type { TenantProjectExtraData, TenantProjectKey } from "./tenant-project-manager-data";
|
|
4
7
|
|
|
5
|
-
export {
|
|
8
|
+
export {
|
|
9
|
+
arrangeTenantProjectColumns,
|
|
10
|
+
mergeTenantProjectExtraData,
|
|
11
|
+
tenantProjectKey,
|
|
12
|
+
type TenantProjectExtraData,
|
|
13
|
+
type TenantProjectKey
|
|
14
|
+
} from "./tenant-project-manager-data";
|
|
6
15
|
|
|
7
16
|
/** 租户项目单条业务上下文。 */
|
|
8
17
|
export interface TenantProjectContext<TData extends Record<string, unknown> = Record<string, unknown>> extends TenantProjectKey {
|
|
@@ -37,10 +46,16 @@ export type TenantProjectAction = Omit<TableActionProps, "disabled" | "hidden" |
|
|
|
37
46
|
onClick: (context: TenantProjectContext, params?: Record<string, any>) => void | Promise<void>;
|
|
38
47
|
};
|
|
39
48
|
|
|
49
|
+
/** 租户项目管理的扩展列配置。 */
|
|
50
|
+
export type TenantProjectExtraColumn = ColumnProps & {
|
|
51
|
+
/** 插入到指定基础字段后面,未配置时默认插入备注字段后面。 */
|
|
52
|
+
after?: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
40
55
|
/** 租户项目管理组件属性。 */
|
|
41
56
|
export interface TenantProjectManagerProps {
|
|
42
|
-
/**
|
|
43
|
-
extraColumns?:
|
|
57
|
+
/** 追加到基础列表的表格列,可通过 after 指定插入位置。 */
|
|
58
|
+
extraColumns?: TenantProjectExtraColumn[];
|
|
44
59
|
/** 追加到基础操作列的业务操作。 */
|
|
45
60
|
extraActions?: TenantProjectAction[];
|
|
46
61
|
/** 加载当前页外部业务字段。 */
|
|
@@ -3,9 +3,11 @@ export type { AdminAiExtension } from "./ai";
|
|
|
3
3
|
export { default as TenantProjectManager } from "./components/tenant-project/TenantProjectManager.vue";
|
|
4
4
|
export {
|
|
5
5
|
mergeTenantProjectExtraData,
|
|
6
|
+
arrangeTenantProjectColumns,
|
|
6
7
|
tenantProjectKey,
|
|
7
8
|
type TenantProjectAction,
|
|
8
9
|
type TenantProjectContext,
|
|
10
|
+
type TenantProjectExtraColumn,
|
|
9
11
|
type TenantProjectExtraData,
|
|
10
12
|
type TenantProjectExtraDataLoadContext,
|
|
11
13
|
type TenantProjectExtraDataLoader,
|
|
@@ -236,42 +236,7 @@ function setRecordText(recordText: string) {
|
|
|
236
236
|
|
|
237
237
|
/** 标准化语音识别文本,压掉多余空白。 */
|
|
238
238
|
function normalizeRecordText(recordText: string) {
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/** 压缩 useRecord 在中文连续识别时返回的前缀累计文本。 */
|
|
243
|
-
function collapseCumulativeRecordText(text: string) {
|
|
244
|
-
if (text.length < 4) return text;
|
|
245
|
-
for (let candidateLength = 2; candidateLength <= Math.floor(text.length / 2); candidateLength++) {
|
|
246
|
-
const candidate = text.slice(-candidateLength);
|
|
247
|
-
const matchResult = matchCumulativeRecordCandidate(text, candidate);
|
|
248
|
-
if (matchResult.matched && (matchResult.hasPartial || matchResult.segmentCount >= 3)) {
|
|
249
|
-
return candidate;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return text;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** 判断文本是否由同一识别结果的前缀片段累计组成。 */
|
|
256
|
-
function matchCumulativeRecordCandidate(text: string, candidate: string) {
|
|
257
|
-
let index = 0;
|
|
258
|
-
let segmentCount = 0;
|
|
259
|
-
let hasPartial = false;
|
|
260
|
-
while (index < text.length) {
|
|
261
|
-
let matchedLength = 0;
|
|
262
|
-
const maxLength = Math.min(candidate.length, text.length - index);
|
|
263
|
-
for (let length = maxLength; length >= 1; length--) {
|
|
264
|
-
if (candidate.startsWith(text.slice(index, index + length))) {
|
|
265
|
-
matchedLength = length;
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (!matchedLength) return { matched: false, segmentCount, hasPartial };
|
|
270
|
-
if (matchedLength < candidate.length) hasPartial = true;
|
|
271
|
-
segmentCount++;
|
|
272
|
-
index += matchedLength;
|
|
273
|
-
}
|
|
274
|
-
return { matched: true, segmentCount, hasPartial };
|
|
239
|
+
return recordText.replace(/\s+/g, " ").trim();
|
|
275
240
|
}
|
|
276
241
|
|
|
277
242
|
/** 根据浏览器语音识别错误类型生成用户可理解的提示。 */
|
|
@@ -5,6 +5,7 @@ export type SpeechResultList = ArrayLike<{
|
|
|
5
5
|
0?: {
|
|
6
6
|
transcript: string;
|
|
7
7
|
};
|
|
8
|
+
isFinal?: boolean;
|
|
8
9
|
length: number;
|
|
9
10
|
}>;
|
|
10
11
|
|
|
@@ -17,6 +18,7 @@ export type SpeechRecognitionError = {
|
|
|
17
18
|
|
|
18
19
|
type SpeechRecognitionResultEvent = {
|
|
19
20
|
results: SpeechResultList;
|
|
21
|
+
resultIndex: number;
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
type SpeechRecognitionInstance = {
|
|
@@ -49,13 +51,28 @@ type SpeechRecognitionOptions = {
|
|
|
49
51
|
onResult?: (text: string) => void;
|
|
50
52
|
};
|
|
51
53
|
|
|
52
|
-
/**
|
|
53
|
-
export function
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
/** 增量累积语音识别结果,避免连续模式下 interim 结果重复计入已确认文本。 */
|
|
55
|
+
export function accumulateSpeechResultText(
|
|
56
|
+
results: SpeechResultList,
|
|
57
|
+
resultIndex: number,
|
|
58
|
+
previousFinalText: string
|
|
59
|
+
): { finalText: string; text: string } {
|
|
60
|
+
let finalText = previousFinalText;
|
|
61
|
+
let interimText = "";
|
|
62
|
+
for (let index = resultIndex; index < results.length; index++) {
|
|
63
|
+
const result = results[index];
|
|
64
|
+
const transcript = result?.[0]?.transcript ?? "";
|
|
65
|
+
if (result?.isFinal) {
|
|
66
|
+
finalText += transcript;
|
|
67
|
+
} else {
|
|
68
|
+
interimText += transcript;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Chrome 连续模式下 interim 结果会累计包含已确认的 final 文本,去除重复前缀。
|
|
72
|
+
if (interimText && finalText && interimText.startsWith(finalText)) {
|
|
73
|
+
interimText = interimText.slice(finalText.length);
|
|
57
74
|
}
|
|
58
|
-
return text;
|
|
75
|
+
return { finalText, text: finalText + interimText };
|
|
59
76
|
}
|
|
60
77
|
|
|
61
78
|
/** 创建浏览器语音识别实例,并统一结果、状态和卸载处理。 */
|
|
@@ -64,6 +81,7 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
|
|
|
64
81
|
const value = ref("");
|
|
65
82
|
let recognition: SpeechRecognitionInstance | null = null;
|
|
66
83
|
let recognitionSession = 0;
|
|
84
|
+
let finalText = "";
|
|
67
85
|
|
|
68
86
|
/** 获取当前浏览器支持的语音识别构造函数。 */
|
|
69
87
|
function resolveRecognitionConstructor() {
|
|
@@ -84,6 +102,7 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
|
|
|
84
102
|
const nextRecognition = new RecognitionConstructor();
|
|
85
103
|
const currentSession = ++recognitionSession;
|
|
86
104
|
recognition = nextRecognition;
|
|
105
|
+
finalText = "";
|
|
87
106
|
const isCurrentRecognition = () => recognition === nextRecognition && recognitionSession === currentSession;
|
|
88
107
|
nextRecognition.continuous = true;
|
|
89
108
|
nextRecognition.interimResults = true;
|
|
@@ -96,7 +115,9 @@ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: Spee
|
|
|
96
115
|
};
|
|
97
116
|
nextRecognition.onresult = event => {
|
|
98
117
|
if (!isCurrentRecognition()) return;
|
|
99
|
-
|
|
118
|
+
const accumulated = accumulateSpeechResultText(event.results, event.resultIndex, finalText);
|
|
119
|
+
finalText = accumulated.finalText;
|
|
120
|
+
value.value = accumulated.text;
|
|
100
121
|
onResult?.(value.value);
|
|
101
122
|
};
|
|
102
123
|
nextRecognition.onerror = error => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liujitcn/kratos-admin-system",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.52",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin system management module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"sortablejs": "^1.15.2",
|
|
17
17
|
"swagger-ui-dist": "^5.32.15",
|
|
18
18
|
"vue-element-plus-x": "^2.0.3",
|
|
19
|
-
"@liujitcn/kratos-admin-core": "0.0.
|
|
19
|
+
"@liujitcn/kratos-admin-core": "0.0.52"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@element-plus/icons-vue": "^2.3.1",
|