@liujitcn/kratos-admin-system 0.0.50 → 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.
Files changed (29) hide show
  1. package/README.md +2 -2
  2. package/dist/package/declarations/src/api/system/admin/v1/base_redact_output_policy.d.ts +4 -1
  3. package/dist/package/declarations/src/api/system/admin/v1/base_redact_storage_policy.d.ts +3 -1
  4. package/dist/package/declarations/src/locales/generated.d.ts +24 -0
  5. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_output_policy.d.ts +8 -0
  6. package/dist/package/declarations/src/rpc/system/admin/v1/base_redact_storage_policy.d.ts +25 -0
  7. package/dist/package/declarations/src/views/ai/chat/components/speech-recognition.d.ts +33 -0
  8. package/dist/package/src/api/system/admin/v1/base_redact_output_policy.ts +7 -0
  9. package/dist/package/src/api/system/admin/v1/base_redact_storage_policy.ts +7 -0
  10. package/dist/package/src/components/tenant-project/TenantProjectManager.vue +14 -35
  11. package/dist/package/src/locales/en-US.json +6 -0
  12. package/dist/package/src/locales/ja-JP.json +6 -0
  13. package/dist/package/src/locales/zh-CN.json +6 -0
  14. package/dist/package/src/locales/zh-TW.json +6 -0
  15. package/dist/package/src/rpc/system/admin/v1/base_redact_output_policy.ts +9 -0
  16. package/dist/package/src/rpc/system/admin/v1/base_redact_storage_policy.ts +30 -0
  17. package/dist/package/src/views/ai/chat/components/XSender.vue +17 -25
  18. package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
  19. package/dist/package/src/views/base/job/index.vue +82 -158
  20. package/dist/package/src/views/base/login-policy/index.vue +12 -6
  21. package/dist/package/src/views/base/redact-output-policy/index.vue +19 -7
  22. package/dist/package/src/views/base/redact-rule/index.vue +70 -53
  23. package/dist/package/src/views/base/redact-storage-policy/index.vue +18 -7
  24. package/dist/package/src/views/base/role/index.vue +1 -1
  25. package/dist/package/src/views/base/{project-grant → tenant-project-grant}/index.vue +3 -1
  26. package/package.json +2 -2
  27. /package/dist/package/declarations/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue.d.ts +0 -0
  28. /package/dist/package/declarations/src/views/base/{project-grant → tenant-project-grant}/index.vue.d.ts +0 -0
  29. /package/dist/package/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue +0 -0
@@ -7,6 +7,7 @@
7
7
  /* eslint-disable */
8
8
  import type { Status } from "../../../common/v1/enum";
9
9
  import type { Empty } from "../../../google/protobuf/empty";
10
+ import type { BaseApiDoc } from "./base_api";
10
11
 
11
12
  /** 出库脱敏策略处理模式。 */
12
13
  export enum BaseRedactOutputPolicyMode {
@@ -140,6 +141,12 @@ export interface BaseRedactOutputPolicy {
140
141
  updated_at: string;
141
142
  }
142
143
 
144
+ /** 查询可出库脱敏字段请求。 */
145
+ export interface GetBaseRedactOutputFieldDocRequest {
146
+ /** 接口ID */
147
+ api_id: number;
148
+ }
149
+
143
150
  /** Admin出库脱敏策略服务。 */
144
151
  export interface BaseRedactOutputPolicyService {
145
152
  /** 查询出库脱敏策略分页列表。 */
@@ -154,4 +161,6 @@ export interface BaseRedactOutputPolicyService {
154
161
  DeleteBaseRedactOutputPolicy(request: DeleteBaseRedactOutputPolicyRequest): Promise<Empty>;
155
162
  /** 设置出库脱敏策略状态。 */
156
163
  SetBaseRedactOutputPolicyStatus(request: SetBaseRedactOutputPolicyStatusRequest): Promise<Empty>;
164
+ /** 查询可出库脱敏的响应字段文档。 */
165
+ GetBaseRedactOutputFieldDoc(request: GetBaseRedactOutputFieldDocRequest): Promise<BaseApiDoc>;
157
166
  }
@@ -118,6 +118,32 @@ export interface BaseRedactStoragePolicy {
118
118
  updated_at: string;
119
119
  }
120
120
 
121
+ /** 查询可入库脱敏字段请求。 */
122
+ export interface ListBaseRedactStorageColumnRequest {
123
+ /** 数据源名称 */
124
+ source_name: string;
125
+ /** 数据库表名 */
126
+ table_name: string;
127
+ }
128
+
129
+ /** 可入库脱敏字段。 */
130
+ export interface BaseRedactStorageColumn {
131
+ /** 字段名 */
132
+ name: string;
133
+ /** 字段注释 */
134
+ comment: string;
135
+ /** 数据库类型 */
136
+ db_type: string;
137
+ /** 数据库完整类型 */
138
+ column_type: string;
139
+ }
140
+
141
+ /** 可入库脱敏字段列表响应。 */
142
+ export interface ListBaseRedactStorageColumnResponse {
143
+ /** 可脱敏字段列表 */
144
+ columns: BaseRedactStorageColumn[];
145
+ }
146
+
121
147
  /** Admin入库脱敏策略服务。 */
122
148
  export interface BaseRedactStoragePolicyService {
123
149
  /** 查询入库脱敏策略分页列表。 */
@@ -134,4 +160,8 @@ export interface BaseRedactStoragePolicyService {
134
160
  DeleteBaseRedactStoragePolicy(request: DeleteBaseRedactStoragePolicyRequest): Promise<Empty>;
135
161
  /** 设置入库脱敏策略状态。 */
136
162
  SetBaseRedactStoragePolicyStatus(request: SetBaseRedactStoragePolicyStatusRequest): Promise<Empty>;
163
+ /** 查询可入库脱敏的字符串字段列表。 */
164
+ ListBaseRedactStorageColumn(
165
+ request: ListBaseRedactStorageColumnRequest,
166
+ ): Promise<ListBaseRedactStorageColumnResponse>;
137
167
  }
@@ -101,8 +101,8 @@
101
101
  </template>
102
102
 
103
103
  <script setup lang="ts" name="XSender">
104
- import { computed, ref, watch } from "vue";
105
- import { Attachments, useRecord, XSender as BaseXSender } from "vue-element-plus-x";
104
+ import { computed, ref } from "vue";
105
+ import { Attachments, XSender as BaseXSender } from "vue-element-plus-x";
106
106
  import type { FilesCardProps } from "vue-element-plus-x/types/FilesCard";
107
107
  import { Loading, Microphone, Paperclip, Promotion } from "@element-plus/icons-vue";
108
108
  import { ElMessage } from "element-plus";
@@ -111,13 +111,8 @@ import { defFileService } from "@liujitcn/kratos-admin-core/api/base/v1/file";
111
111
  import type { AiAttachment } from "@liujitcn/kratos-admin-system/rpc/base/v1/ai_session";
112
112
  import type { SubmitPayload } from "../types";
113
113
  import { buildAIAttachmentFileCard } from "../attachment";
114
-
115
- /** 语音识别错误的最小字段,兼容浏览器事件和不支持错误。 */
116
- type RecordError = {
117
- code?: number;
118
- error?: string;
119
- message?: string;
120
- };
114
+ import { useSpeechRecognition } from "./speech-recognition";
115
+ import type { SpeechRecognitionError } from "./speech-recognition";
121
116
 
122
117
  const props = defineProps<{
123
118
  /** 消息发送加载状态。 */
@@ -153,13 +148,17 @@ const acceptedAttachmentExtensions = [
153
148
  ];
154
149
  const acceptedAttachmentTypes = acceptedAttachmentExtensions.join(",");
155
150
 
151
+ const voiceBaseText = ref("");
156
152
  const {
157
153
  loading: recording,
158
154
  start: startRecord,
159
- stop: stopRecord,
160
- value: recordText
161
- } = useRecord({
155
+ stop: stopRecord
156
+ } = useSpeechRecognition({
157
+ onStart: () => {
158
+ voiceBaseText.value = inputText.value.trim();
159
+ },
162
160
  onEnd: handleRecordEnd,
161
+ onResult: setRecordText,
163
162
  onError: handleRecordError
164
163
  });
165
164
 
@@ -217,10 +216,11 @@ function handleToggleRecord() {
217
216
  /** 语音识别结束后保留当前识别文本。 */
218
217
  function handleRecordEnd(result: string) {
219
218
  setRecordText(result);
219
+ voiceBaseText.value = "";
220
220
  }
221
221
 
222
222
  /** 处理语音识别失败,提示浏览器不支持或麦克风授权异常。 */
223
- function handleRecordError(error: RecordError) {
223
+ function handleRecordError(error: SpeechRecognitionError) {
224
224
  const message = resolveRecordErrorMessage(error);
225
225
  ElMessage.warning(message);
226
226
  }
@@ -229,8 +229,9 @@ function handleRecordError(error: RecordError) {
229
229
  function setRecordText(recordText: string) {
230
230
  const normalizedRecordText = normalizeRecordText(recordText);
231
231
  if (!normalizedRecordText) return;
232
- senderRef.value?.setText(normalizedRecordText);
233
- inputText.value = normalizedRecordText;
232
+ const nextText = [voiceBaseText.value, normalizedRecordText].filter(Boolean).join(" ");
233
+ senderRef.value?.setText(nextText);
234
+ inputText.value = nextText;
234
235
  }
235
236
 
236
237
  /** 标准化语音识别文本,压掉多余空白。 */
@@ -274,7 +275,7 @@ function matchCumulativeRecordCandidate(text: string, candidate: string) {
274
275
  }
275
276
 
276
277
  /** 根据浏览器语音识别错误类型生成用户可理解的提示。 */
277
- function resolveRecordErrorMessage(error: RecordError) {
278
+ function resolveRecordErrorMessage(error: SpeechRecognitionError) {
278
279
  if (error.code === -1) return t("system.ai.chat.message.voice_unsupported");
279
280
  const errorName = error.error ?? "";
280
281
  if (["not-allowed", "service-not-allowed", "permission-denied"].includes(errorName)) {
@@ -389,15 +390,6 @@ function handleDeleteCard(item: { uid?: string | number }) {
389
390
  function resetFileInput() {
390
391
  if (fileInputRef.value) fileInputRef.value.value = "";
391
392
  }
392
-
393
- /** 按 useRecord 文档监听识别文本,并同步到 XSender。 */
394
- watch(
395
- recordText,
396
- value => {
397
- setRecordText(value);
398
- },
399
- { deep: true }
400
- );
401
393
  </script>
402
394
 
403
395
  <style scoped lang="scss">
@@ -0,0 +1,156 @@
1
+ import { getCurrentInstance, onBeforeUnmount, ref } from "vue";
2
+
3
+ /** 语音识别结果的最小结构,便于在不同浏览器实现间复用。 */
4
+ export type SpeechResultList = ArrayLike<{
5
+ 0?: {
6
+ transcript: string;
7
+ };
8
+ length: number;
9
+ }>;
10
+
11
+ /** 语音识别错误的最小结构,兼容浏览器事件和不支持错误。 */
12
+ export type SpeechRecognitionError = {
13
+ code?: number;
14
+ error?: string;
15
+ message?: string;
16
+ };
17
+
18
+ type SpeechRecognitionResultEvent = {
19
+ results: SpeechResultList;
20
+ };
21
+
22
+ type SpeechRecognitionInstance = {
23
+ continuous: boolean;
24
+ interimResults: boolean;
25
+ lang: string;
26
+ onstart: (() => void) | null;
27
+ onend: (() => void) | null;
28
+ onerror: ((error: SpeechRecognitionError) => void) | null;
29
+ onresult: ((event: SpeechRecognitionResultEvent) => void) | null;
30
+ start: () => void;
31
+ stop: () => void;
32
+ };
33
+
34
+ type SpeechRecognitionConstructor = new () => SpeechRecognitionInstance;
35
+
36
+ type SpeechRecognitionWindow = Window & {
37
+ SpeechRecognition?: SpeechRecognitionConstructor;
38
+ webkitSpeechRecognition?: SpeechRecognitionConstructor;
39
+ };
40
+
41
+ type SpeechRecognitionOptions = {
42
+ /** 识别错误回调。 */
43
+ onError?: (error: SpeechRecognitionError) => void;
44
+ /** 识别开始回调。 */
45
+ onStart?: () => void;
46
+ /** 识别结束回调。 */
47
+ onEnd?: (text: string) => void;
48
+ /** 识别结果回调。 */
49
+ onResult?: (text: string) => void;
50
+ };
51
+
52
+ /** 收集一次识别事件中的完整文本。 */
53
+ export function collectSpeechResultText(results: SpeechResultList) {
54
+ let text = "";
55
+ for (let index = 0; index < results.length; index++) {
56
+ text += results[index]?.[0]?.transcript ?? "";
57
+ }
58
+ return text;
59
+ }
60
+
61
+ /** 创建浏览器语音识别实例,并统一结果、状态和卸载处理。 */
62
+ export function useSpeechRecognition({ onError, onStart, onEnd, onResult }: SpeechRecognitionOptions = {}) {
63
+ const loading = ref(false);
64
+ const value = ref("");
65
+ let recognition: SpeechRecognitionInstance | null = null;
66
+ let recognitionSession = 0;
67
+
68
+ /** 获取当前浏览器支持的语音识别构造函数。 */
69
+ function resolveRecognitionConstructor() {
70
+ if (typeof window === "undefined") return undefined;
71
+ const speechWindow = window as SpeechRecognitionWindow;
72
+ return speechWindow.SpeechRecognition ?? speechWindow.webkitSpeechRecognition;
73
+ }
74
+
75
+ /** 开始一次语音识别。 */
76
+ function start() {
77
+ if (loading.value) return;
78
+ const RecognitionConstructor = resolveRecognitionConstructor();
79
+ if (!RecognitionConstructor) {
80
+ onError?.({ code: -1, message: "Speech recognition is not supported by this browser" });
81
+ return;
82
+ }
83
+
84
+ const nextRecognition = new RecognitionConstructor();
85
+ const currentSession = ++recognitionSession;
86
+ recognition = nextRecognition;
87
+ const isCurrentRecognition = () => recognition === nextRecognition && recognitionSession === currentSession;
88
+ nextRecognition.continuous = true;
89
+ nextRecognition.interimResults = true;
90
+ nextRecognition.lang = "zh-CN";
91
+ nextRecognition.onstart = () => {
92
+ if (!isCurrentRecognition()) return;
93
+ loading.value = true;
94
+ value.value = "";
95
+ onStart?.();
96
+ };
97
+ nextRecognition.onresult = event => {
98
+ if (!isCurrentRecognition()) return;
99
+ value.value = collectSpeechResultText(event.results);
100
+ onResult?.(value.value);
101
+ };
102
+ nextRecognition.onerror = error => {
103
+ if (!isCurrentRecognition()) return;
104
+ loading.value = false;
105
+ recognition = null;
106
+ onError?.(error);
107
+ };
108
+ nextRecognition.onend = () => {
109
+ if (!isCurrentRecognition()) return;
110
+ loading.value = false;
111
+ recognition = null;
112
+ onEnd?.(value.value);
113
+ };
114
+
115
+ loading.value = true;
116
+ try {
117
+ nextRecognition.start();
118
+ } catch (error) {
119
+ if (!isCurrentRecognition()) return;
120
+ loading.value = false;
121
+ recognition = null;
122
+ onError?.({
123
+ code: -2,
124
+ message: error instanceof Error ? error.message : String(error)
125
+ });
126
+ }
127
+ }
128
+
129
+ /** 停止当前语音识别。 */
130
+ function stop() {
131
+ const currentRecognition = recognition;
132
+ if (!currentRecognition) return;
133
+ recognition = null;
134
+ recognitionSession++;
135
+ loading.value = false;
136
+ onEnd?.(value.value);
137
+ currentRecognition.stop();
138
+ }
139
+
140
+ if (getCurrentInstance()) {
141
+ onBeforeUnmount(() => {
142
+ const currentRecognition = recognition;
143
+ recognition = null;
144
+ recognitionSession++;
145
+ loading.value = false;
146
+ currentRecognition?.stop();
147
+ });
148
+ }
149
+
150
+ return {
151
+ loading,
152
+ start,
153
+ stop,
154
+ value
155
+ };
156
+ }
@@ -22,8 +22,8 @@
22
22
  </template>
23
23
 
24
24
  <script setup lang="ts">
25
- import { computed, h, reactive, ref, type VNode } from "vue";
26
- import { ElButton, ElMessage, ElMessageBox, ElTag } from "element-plus";
25
+ import { computed, h, reactive, ref } from "vue";
26
+ import { ElMessage, ElMessageBox, ElTag } from "element-plus";
27
27
  import { CirclePlus, Delete, EditPen, Promotion, Tickets, VideoPause, VideoPlay } from "@element-plus/icons-vue";
28
28
  import type {
29
29
  ColumnProps,
@@ -172,132 +172,6 @@ function renderJobNameCell(scope: RenderScope<BaseJob>) {
172
172
  });
173
173
  }
174
174
 
175
- /**
176
- * 渲染定时任务操作列。
177
- */
178
- function renderOperationCell(scope: RenderScope<BaseJob>) {
179
- const row = scope.row;
180
- const actionNodes: VNode[] = [];
181
-
182
- if (BUTTONS.value["base:job:update"]) {
183
- actionNodes.push(
184
- h(
185
- ElButton,
186
- {
187
- key: `edit-${row.id}`,
188
- type: "primary",
189
- link: true,
190
- icon: EditPen,
191
- onClick: () => handleOpenDialog(row.id)
192
- },
193
- () => t("common.action.edit")
194
- )
195
- );
196
- }
197
-
198
- if (BUTTONS.value["base:job:delete"]) {
199
- actionNodes.push(
200
- h(
201
- ElButton,
202
- {
203
- key: `delete-${row.id}`,
204
- type: "danger",
205
- link: true,
206
- icon: Delete,
207
- onClick: () => handleDelete(row)
208
- },
209
- () => t("common.action.delete")
210
- )
211
- );
212
- }
213
-
214
- if (
215
- row.status === Status.STATUS_ENABLE &&
216
- (row.entry_id === undefined || row.entry_id === 0) &&
217
- BUTTONS.value["base:job:start"]
218
- ) {
219
- actionNodes.push(
220
- h(
221
- ElButton,
222
- {
223
- key: `start-${row.id}`,
224
- type: "primary",
225
- link: true,
226
- icon: VideoPlay,
227
- class: "job-action job-action--start",
228
- onClick: () => handleStart(row.id, row.name)
229
- },
230
- () => t("system.base.job.action.start")
231
- )
232
- );
233
- }
234
-
235
- if (row.status === Status.STATUS_ENABLE && row.entry_id > 0 && BUTTONS.value["base:job:stop"]) {
236
- actionNodes.push(
237
- h(
238
- ElButton,
239
- {
240
- key: `stop-${row.id}`,
241
- type: "warning",
242
- link: true,
243
- icon: VideoPause,
244
- class: "job-action job-action--stop",
245
- onClick: () => handleStop(row.id, row.name)
246
- },
247
- () => t("system.base.job.action.stop")
248
- )
249
- );
250
- }
251
-
252
- if (
253
- row.status === Status.STATUS_ENABLE &&
254
- (row.entry_id === undefined || row.entry_id === 0) &&
255
- BUTTONS.value["base:job:exec"]
256
- ) {
257
- actionNodes.push(
258
- h(
259
- ElButton,
260
- {
261
- key: `exec-${row.id}`,
262
- type: "success",
263
- link: true,
264
- icon: Promotion,
265
- class: "job-action job-action--exec",
266
- onClick: () => handleExec(row.id, row.name)
267
- },
268
- () => t("system.base.job.action.execute")
269
- )
270
- );
271
- }
272
-
273
- if (BUTTONS.value["base:job:log"]) {
274
- actionNodes.push(
275
- h(
276
- ElButton,
277
- {
278
- key: `log-${row.id}`,
279
- type: "primary",
280
- link: true,
281
- icon: Tickets,
282
- class: "job-action job-action--log",
283
- onClick: () => handleOpenBaseJob(row.id, row.name)
284
- },
285
- () => t("system.base.job.action.log")
286
- )
287
- );
288
- }
289
-
290
- if (!actionNodes.length) return "--";
291
- return h(
292
- "div",
293
- {
294
- class: "job-operation",
295
- key: `job-operation-${row.id}`
296
- },
297
- actionNodes
298
- );
299
- }
300
-
301
175
  /** 定时任务表单字段配置。 */
302
176
  const formFields = computed<ProFormField[]>(() => [
303
177
  {
@@ -372,7 +246,86 @@ const columns = computed<ColumnProps[]>(() => [
372
246
  {
373
247
  prop: "operation",
374
248
  label: t("common.field.action"),
375
- render: scope => renderOperationCell(scope as unknown as RenderScope<BaseJob>)
249
+ cellType: "actions",
250
+ actions: [
251
+ {
252
+ label: t("common.action.edit"),
253
+ type: "primary",
254
+ link: true,
255
+ icon: EditPen,
256
+ hidden: () => !BUTTONS.value["base:job:update"],
257
+ onClick: scope => handleOpenDialog((scope.row as BaseJob).id)
258
+ },
259
+ {
260
+ label: t("common.action.delete"),
261
+ type: "danger",
262
+ link: true,
263
+ icon: Delete,
264
+ hidden: () => !BUTTONS.value["base:job:delete"],
265
+ onClick: scope => handleDelete(scope.row as BaseJob)
266
+ },
267
+ {
268
+ label: t("system.base.job.action.start"),
269
+ type: "primary",
270
+ link: true,
271
+ icon: VideoPlay,
272
+ hidden: scope => {
273
+ const row = scope.row as BaseJob;
274
+ return (
275
+ row.status !== Status.STATUS_ENABLE ||
276
+ (row.entry_id !== undefined && row.entry_id !== 0) ||
277
+ !BUTTONS.value["base:job:start"]
278
+ );
279
+ },
280
+ onClick: scope => {
281
+ const row = scope.row as BaseJob;
282
+ return handleStart(row.id, row.name);
283
+ }
284
+ },
285
+ {
286
+ label: t("system.base.job.action.stop"),
287
+ type: "warning",
288
+ link: true,
289
+ icon: VideoPause,
290
+ hidden: scope => {
291
+ const row = scope.row as BaseJob;
292
+ return row.status !== Status.STATUS_ENABLE || !(row.entry_id > 0) || !BUTTONS.value["base:job:stop"];
293
+ },
294
+ onClick: scope => {
295
+ const row = scope.row as BaseJob;
296
+ return handleStop(row.id, row.name);
297
+ }
298
+ },
299
+ {
300
+ label: t("system.base.job.action.execute"),
301
+ type: "success",
302
+ link: true,
303
+ icon: Promotion,
304
+ hidden: scope => {
305
+ const row = scope.row as BaseJob;
306
+ return (
307
+ row.status !== Status.STATUS_ENABLE ||
308
+ (row.entry_id !== undefined && row.entry_id !== 0) ||
309
+ !BUTTONS.value["base:job:exec"]
310
+ );
311
+ },
312
+ onClick: scope => {
313
+ const row = scope.row as BaseJob;
314
+ return handleExec(row.id, row.name);
315
+ }
316
+ },
317
+ {
318
+ label: t("system.base.job.action.log"),
319
+ type: "info",
320
+ link: true,
321
+ icon: Tickets,
322
+ hidden: () => !BUTTONS.value["base:job:log"],
323
+ onClick: scope => {
324
+ const row = scope.row as BaseJob;
325
+ return handleOpenBaseJob(row.id, row.name);
326
+ }
327
+ }
328
+ ]
376
329
  }
377
330
  ]);
378
331
 
@@ -608,32 +561,3 @@ function handleOpenBaseJob(id: number, name: string) {
608
561
  navigateTo(router, "/base/job/log", { jobId: id, title: t("system.base.job.title.log", { name }) });
609
562
  }
610
563
  </script>
611
-
612
- <style scoped>
613
- .job-operation {
614
- display: flex;
615
- flex-wrap: wrap;
616
- gap: 4px 10px;
617
- align-items: center;
618
- white-space: nowrap;
619
- }
620
- .job-action {
621
- margin-left: 0;
622
- font-weight: 500;
623
- }
624
- .job-action:deep(.el-icon) {
625
- margin-right: 4px;
626
- }
627
- .job-action--start {
628
- --el-button-text-color: var(--el-color-primary);
629
- }
630
- .job-action--stop {
631
- --el-button-text-color: var(--el-color-warning);
632
- }
633
- .job-action--exec {
634
- --el-button-text-color: var(--el-color-success);
635
- }
636
- .job-action--log {
637
- --el-button-text-color: var(--el-color-info);
638
- }
639
- </style>
@@ -94,7 +94,11 @@ import {
94
94
  } from "@liujitcn/kratos-admin-system/rpc/system/admin/v1/base_login_policy";
95
95
 
96
96
  /** 登录策略表单状态,初始化密码仅在提交时转换为一次性密码密文。 */
97
- interface BaseLoginPolicyFormState extends Omit<BaseLoginPolicyForm, "initial_password"> {
97
+ interface BaseLoginPolicyFormState extends Omit<BaseLoginPolicyForm, "initial_password" | "tenant_id" | "user_id"> {
98
+ /** 租户作用域目标,未选择时保持未定义。 */
99
+ tenant_id?: number;
100
+ /** 用户作用域目标,未选择时保持未定义。 */
101
+ user_id?: number;
98
102
  /** 初始化密码明文只保留在当前表单,留空表示不修改已有值。 */
99
103
  initial_password: string;
100
104
  }
@@ -370,13 +374,13 @@ watch(
370
374
  () => formData.scope_type,
371
375
  value => {
372
376
  if (value === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_GLOBAL) {
373
- formData.tenant_id = 0;
374
- formData.user_id = 0;
377
+ formData.tenant_id = undefined;
378
+ formData.user_id = undefined;
375
379
  userOptions.value = [];
376
380
  return;
377
381
  }
378
382
  if (value === BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_TENANT) {
379
- formData.user_id = 0;
383
+ formData.user_id = undefined;
380
384
  userOptions.value = [];
381
385
  }
382
386
  }
@@ -399,8 +403,8 @@ function defaultForm(): BaseLoginPolicyFormState {
399
403
  return {
400
404
  id: 0,
401
405
  scope_type: BaseLoginPolicyScopeType.BASE_LOGIN_POLICY_SCOPE_TYPE_GLOBAL,
402
- tenant_id: 0,
403
- user_id: 0,
406
+ tenant_id: undefined,
407
+ user_id: undefined,
404
408
  max_failed_attempts: 5,
405
409
  lock_duration_minutes: 15,
406
410
  allow_concurrent_login: false,
@@ -464,6 +468,8 @@ async function handleSubmit() {
464
468
 
465
469
  const baseLoginPolicy: BaseLoginPolicyForm = {
466
470
  ...formData,
471
+ tenant_id: formData.tenant_id ?? 0,
472
+ user_id: formData.user_id ?? 0,
467
473
  initial_password: formData.initial_password
468
474
  ? await encryptPassword(formData.initial_password, PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_CONFIGURE_PASSWORD_POLICY)
469
475
  : undefined