@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.
- package/README.md +2 -2
- 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/locales/generated.d.ts +24 -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/views/ai/chat/components/speech-recognition.d.ts +33 -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/components/tenant-project/TenantProjectManager.vue +14 -35
- package/dist/package/src/locales/en-US.json +6 -0
- package/dist/package/src/locales/ja-JP.json +6 -0
- package/dist/package/src/locales/zh-CN.json +6 -0
- package/dist/package/src/locales/zh-TW.json +6 -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/views/ai/chat/components/XSender.vue +17 -25
- package/dist/package/src/views/ai/chat/components/speech-recognition.ts +156 -0
- package/dist/package/src/views/base/job/index.vue +82 -158
- package/dist/package/src/views/base/login-policy/index.vue +12 -6
- 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 +1 -1
- package/dist/package/src/views/base/{project-grant → tenant-project-grant}/index.vue +3 -1
- package/package.json +2 -2
- /package/dist/package/declarations/src/components/{tenant-project → tenant-project-grant}/ProjectGrantDialog.vue.d.ts +0 -0
- /package/dist/package/declarations/src/views/base/{project-grant → tenant-project-grant}/index.vue.d.ts +0 -0
- /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
|
|
105
|
-
import { Attachments,
|
|
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
|
-
|
|
161
|
-
|
|
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:
|
|
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
|
-
|
|
233
|
-
|
|
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:
|
|
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
|
|
26
|
-
import {
|
|
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
|
-
|
|
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 =
|
|
374
|
-
formData.user_id =
|
|
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 =
|
|
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:
|
|
403
|
-
user_id:
|
|
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
|