@liujitcn/kratos-admin-core 0.0.51 → 0.0.53
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/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +10 -3
- package/dist/package/declarations/src/components/Dialog/ProDialog.vue.d.ts +9 -1
- package/dist/package/declarations/src/components/Dialog/interface/index.d.ts +16 -0
- package/dist/package/declarations/src/components/SearchForm/index.vue.d.ts +30 -3
- package/dist/package/declarations/src/locales/generated.d.ts +12 -0
- package/dist/package/src/components/Dialog/FormDialog.vue +15 -0
- package/dist/package/src/components/Dialog/ProDialog.vue +30 -2
- package/dist/package/src/components/Dialog/interface/index.ts +39 -0
- package/dist/package/src/components/ImportExcel/index.vue +1 -1
- package/dist/package/src/components/SearchForm/index.vue +63 -8
- package/dist/package/src/directives/modules/auth.ts +20 -11
- package/dist/package/src/hooks/useTable.ts +5 -1
- package/dist/package/src/locales/en-US.json +3 -0
- package/dist/package/src/locales/ja-JP.json +3 -0
- package/dist/package/src/locales/zh-CN.json +3 -0
- package/dist/package/src/locales/zh-TW.json +3 -0
- package/dist/package/src/routers/index.ts +7 -2
- package/dist/package/src/stores/modules/user.ts +3 -1
- package/dist/package/src/utils/utils.ts +3 -1
- package/package.json +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { FormRules } from "element-plus";
|
|
2
2
|
import type { ProFormField, ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
3
|
+
import type { DialogOpenOptions } from "./interface";
|
|
3
4
|
/** 表单弹窗组件属性。 */
|
|
4
5
|
interface FormDialogProps {
|
|
5
6
|
modelValue: boolean;
|
|
@@ -27,13 +28,19 @@ declare function validate(): Promise<boolean | undefined>;
|
|
|
27
28
|
declare function resetFields(): void;
|
|
28
29
|
/** 清理内部 ProForm 校验状态。 */
|
|
29
30
|
declare function clearValidate(props?: string | string[]): void;
|
|
30
|
-
|
|
31
|
+
/** 异步加载弹窗数据,并只提交最新一次打开请求。 */
|
|
32
|
+
declare function open<T>(options: DialogOpenOptions<T>): Promise<boolean>;
|
|
33
|
+
/** 关闭弹窗并使未完成的打开请求失效。 */
|
|
34
|
+
declare function close(): void;
|
|
35
|
+
declare var __VLS_25: string, __VLS_26: any, __VLS_29: {};
|
|
31
36
|
type __VLS_Slots = {} & {
|
|
32
|
-
[K in NonNullable<typeof
|
|
37
|
+
[K in NonNullable<typeof __VLS_25>]?: (props: typeof __VLS_26) => any;
|
|
33
38
|
} & {
|
|
34
|
-
footer?: (props: typeof
|
|
39
|
+
footer?: (props: typeof __VLS_29) => any;
|
|
35
40
|
};
|
|
36
41
|
declare const __VLS_base: import("vue").DefineComponent<FormDialogProps, {
|
|
42
|
+
open: typeof open;
|
|
43
|
+
close: typeof close;
|
|
37
44
|
validate: typeof validate;
|
|
38
45
|
resetFields: typeof resetFields;
|
|
39
46
|
clearValidate: typeof clearValidate;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DialogOpenOptions } from "./interface";
|
|
1
2
|
/** 通用弹窗组件属性。 */
|
|
2
3
|
interface ProDialogProps {
|
|
3
4
|
modelValue: boolean;
|
|
@@ -13,6 +14,10 @@ interface ProDialogProps {
|
|
|
13
14
|
/** 是否展示弹窗底部操作区。 */
|
|
14
15
|
showFooter?: boolean;
|
|
15
16
|
}
|
|
17
|
+
/** 异步加载弹窗数据,并只提交最新一次打开请求。 */
|
|
18
|
+
declare function open<T>(options: DialogOpenOptions<T>): Promise<boolean>;
|
|
19
|
+
/** 关闭弹窗并使未完成的打开请求失效。 */
|
|
20
|
+
declare function close(): void;
|
|
16
21
|
declare var __VLS_13: {
|
|
17
22
|
close: () => void;
|
|
18
23
|
titleId: string;
|
|
@@ -25,7 +30,10 @@ type __VLS_Slots = {} & {
|
|
|
25
30
|
} & {
|
|
26
31
|
footer?: (props: typeof __VLS_18) => any;
|
|
27
32
|
};
|
|
28
|
-
declare const __VLS_base: import("vue").DefineComponent<ProDialogProps, {
|
|
33
|
+
declare const __VLS_base: import("vue").DefineComponent<ProDialogProps, {
|
|
34
|
+
open: typeof open;
|
|
35
|
+
close: typeof close;
|
|
36
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
29
37
|
confirm: () => any;
|
|
30
38
|
close: () => any;
|
|
31
39
|
"update:modelValue": (value: boolean) => any;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** 弹窗异步打开配置。 */
|
|
2
|
+
export interface DialogOpenOptions<T> {
|
|
3
|
+
/** 加载弹窗需要展示的数据,不应在异步过程中修改页面状态。 */
|
|
4
|
+
load: () => Promise<T> | T;
|
|
5
|
+
/** 最新一次加载完成后提交数据并更新页面状态。 */
|
|
6
|
+
commit: (data: T) => void;
|
|
7
|
+
}
|
|
8
|
+
/** 弹窗异步打开控制器。 */
|
|
9
|
+
export interface DialogRequestController {
|
|
10
|
+
/** 执行一次异步打开请求,并丢弃过期请求的结果。 */
|
|
11
|
+
open<T>(options: DialogOpenOptions<T>): Promise<boolean>;
|
|
12
|
+
/** 使当前未完成的打开请求失效。 */
|
|
13
|
+
invalidate(): void;
|
|
14
|
+
}
|
|
15
|
+
/** 创建弹窗异步打开控制器。 */
|
|
16
|
+
export declare function createDialogRequestController(): DialogRequestController;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { ColumnProps } from "../../components/ProTable/interface";
|
|
2
2
|
import { BreakPoint } from "../../components/Grid/interface";
|
|
3
|
+
/** 搜索列配置,补充 ProForm 风格的插槽名称和字段后置位置。 */
|
|
4
|
+
type SearchColumn = ColumnProps & {
|
|
5
|
+
/** 自定义搜索项插槽名称,未配置时使用字段 prop。 */
|
|
6
|
+
slotName?: string;
|
|
7
|
+
/** 将当前搜索项插入到指定字段后面。 */
|
|
8
|
+
after?: string;
|
|
9
|
+
};
|
|
3
10
|
/** ProTable 搜索表单组件属性。 */
|
|
4
11
|
interface ProTableProps {
|
|
5
|
-
columns?:
|
|
12
|
+
columns?: SearchColumn[];
|
|
6
13
|
searchParam?: {
|
|
7
14
|
[key: string]: any;
|
|
8
15
|
};
|
|
@@ -11,12 +18,32 @@ interface ProTableProps {
|
|
|
11
18
|
reset: (params: any) => void;
|
|
12
19
|
showOperation?: boolean;
|
|
13
20
|
}
|
|
14
|
-
declare
|
|
15
|
-
|
|
21
|
+
declare var __VLS_43: string | undefined, __VLS_44: {
|
|
22
|
+
field: SearchColumn;
|
|
23
|
+
model: {
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
};
|
|
26
|
+
column: SearchColumn;
|
|
27
|
+
searchParam: {
|
|
28
|
+
[key: string]: any;
|
|
29
|
+
};
|
|
30
|
+
prop: string | undefined;
|
|
31
|
+
};
|
|
32
|
+
type __VLS_Slots = {} & {
|
|
33
|
+
[K in NonNullable<typeof __VLS_43>]?: (props: typeof __VLS_44) => any;
|
|
34
|
+
};
|
|
35
|
+
declare const __VLS_base: import("vue").DefineComponent<ProTableProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ProTableProps> & Readonly<{}>, {
|
|
36
|
+
columns: SearchColumn[];
|
|
16
37
|
searchParam: {
|
|
17
38
|
[key: string]: any;
|
|
18
39
|
};
|
|
19
40
|
showOperation: boolean;
|
|
20
41
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
42
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
21
43
|
declare const _default: typeof __VLS_export;
|
|
22
44
|
export default _default;
|
|
45
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
46
|
+
new (): {
|
|
47
|
+
$slots: S;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -60,6 +60,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
60
60
|
"common.language.ja-JP": string;
|
|
61
61
|
"common.message.no_data": string;
|
|
62
62
|
"common.message.operation_success": string;
|
|
63
|
+
"common.message.request_error": string;
|
|
64
|
+
"common.message.save_success": string;
|
|
63
65
|
"common.message.system_error": string;
|
|
64
66
|
"common.validation.phone_invalid": string;
|
|
65
67
|
"common.validation.phone_required": string;
|
|
@@ -73,6 +75,7 @@ export declare const LOCALE_MESSAGES: {
|
|
|
73
75
|
"common.status.enabled": string;
|
|
74
76
|
"common.value.female": string;
|
|
75
77
|
"common.value.male": string;
|
|
78
|
+
"common.message.unknown": string;
|
|
76
79
|
"common.value.all": string;
|
|
77
80
|
"core.auth.login_again": string;
|
|
78
81
|
"core.auth.refresh_token_missing": string;
|
|
@@ -426,6 +429,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
426
429
|
"common.language.ja-JP": string;
|
|
427
430
|
"common.message.no_data": string;
|
|
428
431
|
"common.message.operation_success": string;
|
|
432
|
+
"common.message.request_error": string;
|
|
433
|
+
"common.message.save_success": string;
|
|
429
434
|
"common.message.system_error": string;
|
|
430
435
|
"common.validation.phone_invalid": string;
|
|
431
436
|
"common.validation.phone_required": string;
|
|
@@ -439,6 +444,7 @@ export declare const LOCALE_MESSAGES: {
|
|
|
439
444
|
"common.status.enabled": string;
|
|
440
445
|
"common.value.female": string;
|
|
441
446
|
"common.value.male": string;
|
|
447
|
+
"common.message.unknown": string;
|
|
442
448
|
"common.value.all": string;
|
|
443
449
|
"core.auth.login_again": string;
|
|
444
450
|
"core.auth.refresh_token_missing": string;
|
|
@@ -792,6 +798,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
792
798
|
"common.language.ja-JP": string;
|
|
793
799
|
"common.message.no_data": string;
|
|
794
800
|
"common.message.operation_success": string;
|
|
801
|
+
"common.message.request_error": string;
|
|
802
|
+
"common.message.save_success": string;
|
|
795
803
|
"common.message.system_error": string;
|
|
796
804
|
"common.validation.phone_invalid": string;
|
|
797
805
|
"common.validation.phone_required": string;
|
|
@@ -805,6 +813,7 @@ export declare const LOCALE_MESSAGES: {
|
|
|
805
813
|
"common.status.enabled": string;
|
|
806
814
|
"common.value.female": string;
|
|
807
815
|
"common.value.male": string;
|
|
816
|
+
"common.message.unknown": string;
|
|
808
817
|
"common.value.all": string;
|
|
809
818
|
"core.auth.login_again": string;
|
|
810
819
|
"core.auth.refresh_token_missing": string;
|
|
@@ -1158,6 +1167,8 @@ export declare const LOCALE_MESSAGES: {
|
|
|
1158
1167
|
"common.language.ja-JP": string;
|
|
1159
1168
|
"common.message.no_data": string;
|
|
1160
1169
|
"common.message.operation_success": string;
|
|
1170
|
+
"common.message.request_error": string;
|
|
1171
|
+
"common.message.save_success": string;
|
|
1161
1172
|
"common.message.system_error": string;
|
|
1162
1173
|
"common.validation.phone_invalid": string;
|
|
1163
1174
|
"common.validation.phone_required": string;
|
|
@@ -1171,6 +1182,7 @@ export declare const LOCALE_MESSAGES: {
|
|
|
1171
1182
|
"common.status.enabled": string;
|
|
1172
1183
|
"common.value.female": string;
|
|
1173
1184
|
"common.value.male": string;
|
|
1185
|
+
"common.message.unknown": string;
|
|
1174
1186
|
"common.value.all": string;
|
|
1175
1187
|
"core.auth.login_again": string;
|
|
1176
1188
|
"core.auth.refresh_token_missing": string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<ProDialog
|
|
3
|
+
ref="proDialogRef"
|
|
3
4
|
:model-value="modelValue"
|
|
4
5
|
:title="title"
|
|
5
6
|
:width="width"
|
|
@@ -53,6 +54,7 @@ import ProDialog from "../../components/Dialog/ProDialog.vue";
|
|
|
53
54
|
import ProForm from "../../components/ProForm/index.vue";
|
|
54
55
|
import type { ProFormField, ProFormInstance, ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
55
56
|
import { useLocaleStore } from "../../locales";
|
|
57
|
+
import type { DialogOpenOptions } from "./interface";
|
|
56
58
|
|
|
57
59
|
const { t } = useLocaleStore();
|
|
58
60
|
|
|
@@ -105,6 +107,7 @@ const emit = defineEmits<{
|
|
|
105
107
|
}>();
|
|
106
108
|
|
|
107
109
|
const slots = useSlots();
|
|
110
|
+
const proDialogRef = ref<InstanceType<typeof ProDialog>>();
|
|
108
111
|
const proFormRef = ref<ProFormInstance>();
|
|
109
112
|
|
|
110
113
|
/** 弹窗打开后再清理校验状态,兼容异步加载选项后才显示的表单。 */
|
|
@@ -160,7 +163,19 @@ function clearValidate(props?: string | string[]) {
|
|
|
160
163
|
proFormRef.value?.clearValidate(props);
|
|
161
164
|
}
|
|
162
165
|
|
|
166
|
+
/** 异步加载弹窗数据,并只提交最新一次打开请求。 */
|
|
167
|
+
function open<T>(options: DialogOpenOptions<T>) {
|
|
168
|
+
return proDialogRef.value?.open(options) ?? Promise.resolve(false);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** 关闭弹窗并使未完成的打开请求失效。 */
|
|
172
|
+
function close() {
|
|
173
|
+
proDialogRef.value?.close();
|
|
174
|
+
}
|
|
175
|
+
|
|
163
176
|
defineExpose({
|
|
177
|
+
open,
|
|
178
|
+
close,
|
|
164
179
|
validate,
|
|
165
180
|
resetFields,
|
|
166
181
|
clearValidate
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
</template>
|
|
35
35
|
|
|
36
36
|
<script setup lang="ts" name="ProDialog">
|
|
37
|
+
import { watch } from "vue";
|
|
37
38
|
import { useLocaleStore } from "../../locales";
|
|
39
|
+
import { createDialogRequestController, type DialogOpenOptions } from "./interface";
|
|
38
40
|
|
|
39
41
|
const { t } = useLocaleStore();
|
|
40
42
|
|
|
@@ -54,7 +56,7 @@ interface ProDialogProps {
|
|
|
54
56
|
showFooter?: boolean;
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
withDefaults(defineProps<ProDialogProps>(), {
|
|
59
|
+
const props = withDefaults(defineProps<ProDialogProps>(), {
|
|
58
60
|
title: "",
|
|
59
61
|
width: "500px",
|
|
60
62
|
top: "8vh",
|
|
@@ -75,8 +77,31 @@ const emit = defineEmits<{
|
|
|
75
77
|
closed: [];
|
|
76
78
|
}>();
|
|
77
79
|
|
|
80
|
+
const dialogRequestController = createDialogRequestController();
|
|
81
|
+
|
|
82
|
+
watch(
|
|
83
|
+
() => props.modelValue,
|
|
84
|
+
value => {
|
|
85
|
+
if (!value) dialogRequestController.invalidate();
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
/** 异步加载弹窗数据,并只提交最新一次打开请求。 */
|
|
90
|
+
async function open<T>(options: DialogOpenOptions<T>) {
|
|
91
|
+
const opened = await dialogRequestController.open(options);
|
|
92
|
+
if (opened) emit("update:modelValue", true);
|
|
93
|
+
return opened;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** 关闭弹窗并使未完成的打开请求失效。 */
|
|
97
|
+
function close() {
|
|
98
|
+
dialogRequestController.invalidate();
|
|
99
|
+
emit("update:modelValue", false);
|
|
100
|
+
}
|
|
101
|
+
|
|
78
102
|
/** 同步弹窗显示状态到外部。 */
|
|
79
103
|
function handleVisibleChange(value: boolean) {
|
|
104
|
+
if (!value) dialogRequestController.invalidate();
|
|
80
105
|
emit("update:modelValue", value);
|
|
81
106
|
}
|
|
82
107
|
|
|
@@ -87,12 +112,13 @@ function handleConfirm() {
|
|
|
87
112
|
|
|
88
113
|
/** 处理点击取消按钮后的回调,并主动关闭弹窗。 */
|
|
89
114
|
function handleCancel() {
|
|
90
|
-
|
|
115
|
+
close();
|
|
91
116
|
emit("cancel");
|
|
92
117
|
}
|
|
93
118
|
|
|
94
119
|
/** 处理弹窗关闭时的回调。 */
|
|
95
120
|
function handleClose() {
|
|
121
|
+
dialogRequestController.invalidate();
|
|
96
122
|
emit("close");
|
|
97
123
|
}
|
|
98
124
|
|
|
@@ -100,4 +126,6 @@ function handleClose() {
|
|
|
100
126
|
function handleClosed() {
|
|
101
127
|
emit("closed");
|
|
102
128
|
}
|
|
129
|
+
|
|
130
|
+
defineExpose({ open, close });
|
|
103
131
|
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** 弹窗异步打开配置。 */
|
|
2
|
+
export interface DialogOpenOptions<T> {
|
|
3
|
+
/** 加载弹窗需要展示的数据,不应在异步过程中修改页面状态。 */
|
|
4
|
+
load: () => Promise<T> | T;
|
|
5
|
+
/** 最新一次加载完成后提交数据并更新页面状态。 */
|
|
6
|
+
commit: (data: T) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** 弹窗异步打开控制器。 */
|
|
10
|
+
export interface DialogRequestController {
|
|
11
|
+
/** 执行一次异步打开请求,并丢弃过期请求的结果。 */
|
|
12
|
+
open<T>(options: DialogOpenOptions<T>): Promise<boolean>;
|
|
13
|
+
/** 使当前未完成的打开请求失效。 */
|
|
14
|
+
invalidate(): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** 创建弹窗异步打开控制器。 */
|
|
18
|
+
export function createDialogRequestController(): DialogRequestController {
|
|
19
|
+
let requestSerial = 0;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
async open<T>({ load, commit }: DialogOpenOptions<T>) {
|
|
23
|
+
const currentSerial = ++requestSerial;
|
|
24
|
+
let data: T;
|
|
25
|
+
try {
|
|
26
|
+
data = await load();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (currentSerial !== requestSerial) return false;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
if (currentSerial !== requestSerial) return false;
|
|
32
|
+
commit(data);
|
|
33
|
+
return currentSerial === requestSerial;
|
|
34
|
+
},
|
|
35
|
+
invalidate() {
|
|
36
|
+
requestSerial += 1;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -97,7 +97,7 @@ const downloadTemp = () => {
|
|
|
97
97
|
const uploadExcel = async (param: UploadRequestOptions) => {
|
|
98
98
|
let excelFormData = new FormData();
|
|
99
99
|
excelFormData.append("file", param.file);
|
|
100
|
-
excelFormData.append("isCover", isCover.value
|
|
100
|
+
excelFormData.append("isCover", String(isCover.value));
|
|
101
101
|
await parameter.value.importApi!(excelFormData);
|
|
102
102
|
parameter.value.getTableList && parameter.value.getTableList();
|
|
103
103
|
dialogVisible.value = false;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div v-if="
|
|
2
|
+
<div v-if="searchColumns.length" class="card table-search" :class="{ 'table-search--no-operation': !showOperation }">
|
|
3
3
|
<el-form ref="formRef" :model="searchParam">
|
|
4
4
|
<Grid ref="gridRef" :collapsed="collapsed" :gap="[20, 0]" :cols="searchCol">
|
|
5
|
-
<GridItem v-for="(item, index) in
|
|
5
|
+
<GridItem v-for="(item, index) in searchColumns" :key="item.prop" v-bind="getResponsive(item)" :index="index">
|
|
6
6
|
<el-form-item>
|
|
7
7
|
<template #label>
|
|
8
8
|
<el-space :size="4">
|
|
@@ -13,7 +13,17 @@
|
|
|
13
13
|
</el-space>
|
|
14
14
|
<span> :</span>
|
|
15
15
|
</template>
|
|
16
|
-
<
|
|
16
|
+
<template v-if="resolveSearchSlotName(item)">
|
|
17
|
+
<slot
|
|
18
|
+
:name="resolveSearchSlotName(item)"
|
|
19
|
+
:field="item"
|
|
20
|
+
:model="searchParam"
|
|
21
|
+
:column="item"
|
|
22
|
+
:search-param="searchParam"
|
|
23
|
+
:prop="item.search?.key ?? item.prop"
|
|
24
|
+
/>
|
|
25
|
+
</template>
|
|
26
|
+
<SearchFormItem v-else :column="item" :search-param="searchParam" />
|
|
17
27
|
</el-form-item>
|
|
18
28
|
</GridItem>
|
|
19
29
|
<GridItem v-if="showOperation" suffix>
|
|
@@ -33,7 +43,7 @@
|
|
|
33
43
|
</div>
|
|
34
44
|
</template>
|
|
35
45
|
<script setup lang="ts" name="SearchForm">
|
|
36
|
-
import { computed, ref } from "vue";
|
|
46
|
+
import { computed, ref, useSlots } from "vue";
|
|
37
47
|
import { ColumnProps } from "../../components/ProTable/interface";
|
|
38
48
|
import { BreakPoint } from "../../components/Grid/interface";
|
|
39
49
|
import { Delete, Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue";
|
|
@@ -44,9 +54,17 @@ import { useLocaleStore } from "../../locales";
|
|
|
44
54
|
|
|
45
55
|
const { t } = useLocaleStore();
|
|
46
56
|
|
|
57
|
+
/** 搜索列配置,补充 ProForm 风格的插槽名称和字段后置位置。 */
|
|
58
|
+
type SearchColumn = ColumnProps & {
|
|
59
|
+
/** 自定义搜索项插槽名称,未配置时使用字段 prop。 */
|
|
60
|
+
slotName?: string;
|
|
61
|
+
/** 将当前搜索项插入到指定字段后面。 */
|
|
62
|
+
after?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
47
65
|
/** ProTable 搜索表单组件属性。 */
|
|
48
66
|
interface ProTableProps {
|
|
49
|
-
columns?:
|
|
67
|
+
columns?: SearchColumn[]; // 搜索配置列
|
|
50
68
|
searchParam?: { [key: string]: any }; // 搜索参数
|
|
51
69
|
searchCol: number | Record<BreakPoint, number>;
|
|
52
70
|
search: (params: any) => void; // 搜索方法
|
|
@@ -61,6 +79,43 @@ const props = withDefaults(defineProps<ProTableProps>(), {
|
|
|
61
79
|
showOperation: true
|
|
62
80
|
});
|
|
63
81
|
|
|
82
|
+
const slots = useSlots();
|
|
83
|
+
|
|
84
|
+
/** 按 after 配置排列搜索项,未配置或找不到目标字段时保持原有顺序。 */
|
|
85
|
+
const searchColumns = computed<SearchColumn[]>(() => {
|
|
86
|
+
const sourceColumns = [...(props.columns ?? [])];
|
|
87
|
+
const positionedColumns = sourceColumns.filter(column => {
|
|
88
|
+
const after = column.after;
|
|
89
|
+
return Boolean(after && sourceColumns.some(item => item.prop === after));
|
|
90
|
+
});
|
|
91
|
+
const positionedSet = new Set(positionedColumns);
|
|
92
|
+
const columnsByAnchor = new Map<string, SearchColumn[]>();
|
|
93
|
+
for (const column of positionedColumns) {
|
|
94
|
+
const anchor = column.after!;
|
|
95
|
+
const columns = columnsByAnchor.get(anchor) ?? [];
|
|
96
|
+
columns.push(column);
|
|
97
|
+
columnsByAnchor.set(anchor, columns);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result: SearchColumn[] = [];
|
|
101
|
+
for (const column of sourceColumns) {
|
|
102
|
+
if (positionedSet.has(column)) continue;
|
|
103
|
+
result.push(column);
|
|
104
|
+
const columns = column.prop ? columnsByAnchor.get(column.prop) : undefined;
|
|
105
|
+
if (!columns) continue;
|
|
106
|
+
result.push(...columns);
|
|
107
|
+
columnsByAnchor.delete(column.prop!);
|
|
108
|
+
}
|
|
109
|
+
for (const columns of columnsByAnchor.values()) result.push(...columns);
|
|
110
|
+
return result;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/** 按 ProForm 的 slotName 优先、字段 prop 回退规则解析搜索项插槽。 */
|
|
114
|
+
function resolveSearchSlotName(item: SearchColumn): string | undefined {
|
|
115
|
+
const slotName = item.slotName ?? item.prop;
|
|
116
|
+
return slotName && slots[slotName] ? slotName : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
64
119
|
// 获取响应式设置
|
|
65
120
|
const getResponsive = (item: ColumnProps) => {
|
|
66
121
|
return {
|
|
@@ -84,10 +139,10 @@ const breakPoint = computed<BreakPoint>(() => gridRef.value?.breakPoint);
|
|
|
84
139
|
// 判断是否显示 展开/合并 按钮
|
|
85
140
|
const showCollapse = computed(() => {
|
|
86
141
|
let show = false;
|
|
87
|
-
|
|
142
|
+
searchColumns.value.reduce((prev, current) => {
|
|
88
143
|
prev +=
|
|
89
|
-
(current.search
|
|
90
|
-
(current.search
|
|
144
|
+
(current.search?.[breakPoint.value]?.span ?? current.search?.span ?? 1) +
|
|
145
|
+
(current.search?.[breakPoint.value]?.offset ?? current.search?.offset ?? 0);
|
|
91
146
|
if (typeof props.searchCol !== "number") {
|
|
92
147
|
if (prev >= props.searchCol[breakPoint.value]) show = true;
|
|
93
148
|
} else {
|
|
@@ -7,18 +7,27 @@ import type { Directive, DirectiveBinding } from "vue";
|
|
|
7
7
|
|
|
8
8
|
const GLOBAL_AUTH_BUTTON_KEY = "__global__";
|
|
9
9
|
|
|
10
|
+
const originalDisplayMap = new WeakMap<HTMLElement, string>();
|
|
11
|
+
|
|
12
|
+
/** 根据当前按钮权限更新元素可见性。 */
|
|
13
|
+
function updateAuthElement(el: HTMLElement, binding: DirectiveBinding) {
|
|
14
|
+
const { value } = binding;
|
|
15
|
+
const authStore = useAuthStore();
|
|
16
|
+
const currentPageRoles =
|
|
17
|
+
authStore.authButtonListGet[authStore.routeName] ?? authStore.authButtonListGet[GLOBAL_AUTH_BUTTON_KEY] ?? [];
|
|
18
|
+
const hasPermission =
|
|
19
|
+
value instanceof Array && value.length
|
|
20
|
+
? value.every(item => currentPageRoles.includes(item))
|
|
21
|
+
: currentPageRoles.includes(value);
|
|
22
|
+
if (!originalDisplayMap.has(el)) originalDisplayMap.set(el, el.style.display);
|
|
23
|
+
el.style.display = hasPermission ? (originalDisplayMap.get(el) ?? "") : "none";
|
|
24
|
+
}
|
|
25
|
+
|
|
10
26
|
const auth: Directive = {
|
|
11
|
-
mounted
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
authStore.authButtonListGet[authStore.routeName] ?? authStore.authButtonListGet[GLOBAL_AUTH_BUTTON_KEY] ?? [];
|
|
16
|
-
if (value instanceof Array && value.length) {
|
|
17
|
-
const hasPermission = value.every(item => currentPageRoles.includes(item));
|
|
18
|
-
if (!hasPermission) el.remove();
|
|
19
|
-
} else {
|
|
20
|
-
if (!currentPageRoles.includes(value)) el.remove();
|
|
21
|
-
}
|
|
27
|
+
mounted: updateAuthElement,
|
|
28
|
+
updated: updateAuthElement,
|
|
29
|
+
unmounted(el: HTMLElement) {
|
|
30
|
+
originalDisplayMap.delete(el);
|
|
22
31
|
}
|
|
23
32
|
};
|
|
24
33
|
|
|
@@ -15,6 +15,7 @@ export const useTable = (
|
|
|
15
15
|
dataCallBack?: (data: any) => any,
|
|
16
16
|
requestError?: (error: any) => void
|
|
17
17
|
) => {
|
|
18
|
+
let requestSerial = 0;
|
|
18
19
|
const state = reactive<Table.StateProps>({
|
|
19
20
|
// 表格加载状态
|
|
20
21
|
loading: false,
|
|
@@ -58,11 +59,13 @@ export const useTable = (
|
|
|
58
59
|
* */
|
|
59
60
|
const getTableList = async () => {
|
|
60
61
|
if (!api) return;
|
|
62
|
+
const currentRequestSerial = ++requestSerial;
|
|
61
63
|
state.loading = true;
|
|
62
64
|
try {
|
|
63
65
|
// 先把初始化参数和分页参数放到总参数里面
|
|
64
66
|
Object.assign(state.totalParam, initParam, isPageable ? pageParam.value : {});
|
|
65
67
|
let { data } = await api({ ...state.searchInitParam, ...state.totalParam });
|
|
68
|
+
if (currentRequestSerial !== requestSerial) return;
|
|
66
69
|
dataCallBack && (data = dataCallBack(data));
|
|
67
70
|
state.tableData = isPageable ? data.list : data;
|
|
68
71
|
// 解构后台返回的分页数据 (如果有分页更新分页信息)
|
|
@@ -72,9 +75,10 @@ export const useTable = (
|
|
|
72
75
|
state.pageable.total = Number.isFinite(total) && total >= 0 ? total : 0;
|
|
73
76
|
}
|
|
74
77
|
} catch (error) {
|
|
78
|
+
if (currentRequestSerial !== requestSerial) return;
|
|
75
79
|
requestError && requestError(error);
|
|
76
80
|
} finally {
|
|
77
|
-
state.loading = false;
|
|
81
|
+
if (currentRequestSerial === requestSerial) state.loading = false;
|
|
78
82
|
}
|
|
79
83
|
};
|
|
80
84
|
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"common.language.ja-JP": "Japanese",
|
|
56
56
|
"common.message.no_data": "No data",
|
|
57
57
|
"common.message.operation_success": "Operation completed",
|
|
58
|
+
"common.message.request_error": "Request failed",
|
|
59
|
+
"common.message.save_success": "Saved successfully",
|
|
58
60
|
"common.message.system_error": "System error",
|
|
59
61
|
"common.validation.phone_invalid": "Enter a valid phone number",
|
|
60
62
|
"common.validation.phone_required": "Enter a phone number",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"common.status.enabled": "Enabled",
|
|
69
71
|
"common.value.female": "Female",
|
|
70
72
|
"common.value.male": "Male",
|
|
73
|
+
"common.message.unknown": "Unknown",
|
|
71
74
|
"common.value.all": "All",
|
|
72
75
|
"core.auth.login_again": "Log in again",
|
|
73
76
|
"core.auth.refresh_token_missing": "Refresh token is missing",
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"common.language.ja-JP": "日本語",
|
|
56
56
|
"common.message.no_data": "データはまだありません",
|
|
57
57
|
"common.message.operation_success": "操作は成功しました",
|
|
58
|
+
"common.message.request_error": "リクエストに失敗しました",
|
|
59
|
+
"common.message.save_success": "保存しました",
|
|
58
60
|
"common.message.system_error": "システムエラー",
|
|
59
61
|
"common.validation.phone_invalid": "正しい携帯電話番号を入力してください",
|
|
60
62
|
"common.validation.phone_required": "携帯電話番号を入力してください",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"common.status.enabled": "有効にする",
|
|
69
71
|
"common.value.female": "女性",
|
|
70
72
|
"common.value.male": "男性",
|
|
73
|
+
"common.message.unknown": "不明",
|
|
71
74
|
"common.value.all": "すべて",
|
|
72
75
|
"core.auth.login_again": "再度ログインしてください",
|
|
73
76
|
"core.auth.refresh_token_missing": "リフレッシュトークンが存在しません",
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"common.language.ja-JP": "日语",
|
|
56
56
|
"common.message.no_data": "暂无数据",
|
|
57
57
|
"common.message.operation_success": "操作成功",
|
|
58
|
+
"common.message.request_error": "请求失败",
|
|
59
|
+
"common.message.save_success": "保存成功",
|
|
58
60
|
"common.message.system_error": "系统出错",
|
|
59
61
|
"common.validation.phone_invalid": "请输入正确的手机号码",
|
|
60
62
|
"common.validation.phone_required": "请输入手机号码",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"common.status.enabled": "启用",
|
|
69
71
|
"common.value.female": "女",
|
|
70
72
|
"common.value.male": "男",
|
|
73
|
+
"common.message.unknown": "未知",
|
|
71
74
|
"common.value.all": "全部",
|
|
72
75
|
"core.auth.login_again": "重新登录",
|
|
73
76
|
"core.auth.refresh_token_missing": "刷新令牌不存在",
|
|
@@ -55,6 +55,8 @@
|
|
|
55
55
|
"common.language.ja-JP": "日語",
|
|
56
56
|
"common.message.no_data": "暫無資料",
|
|
57
57
|
"common.message.operation_success": "操作成功",
|
|
58
|
+
"common.message.request_error": "請求失敗",
|
|
59
|
+
"common.message.save_success": "儲存成功",
|
|
58
60
|
"common.message.system_error": "系統出錯",
|
|
59
61
|
"common.validation.phone_invalid": "請輸入正確的手機號碼",
|
|
60
62
|
"common.validation.phone_required": "請輸入手機號碼",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"common.status.enabled": "啟用",
|
|
69
71
|
"common.value.female": "女",
|
|
70
72
|
"common.value.male": "男",
|
|
73
|
+
"common.message.unknown": "未知",
|
|
71
74
|
"common.value.all": "全部",
|
|
72
75
|
"core.auth.login_again": "重新登入",
|
|
73
76
|
"core.auth.refresh_token_missing": "重新整理令牌不存在",
|
|
@@ -18,6 +18,12 @@ const routerMode = {
|
|
|
18
18
|
history: () => createWebHistory()
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/** 校验登录回跳地址只能是站内路径。 */
|
|
22
|
+
function resolveSafeRedirect(path: string) {
|
|
23
|
+
if (!path || path === LOGIN_URL || path.startsWith("//") || /^[a-z][a-z\d+.-]*:/i.test(path)) return HOME_URL;
|
|
24
|
+
return path.startsWith("/") ? path : HOME_URL;
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
/**
|
|
22
28
|
* @description 📚 路由参数配置简介
|
|
23
29
|
* @param path ==> 路由菜单访问路径
|
|
@@ -63,8 +69,7 @@ router.beforeEach(async to => {
|
|
|
63
69
|
}
|
|
64
70
|
if (hasAccessToken) {
|
|
65
71
|
// 登录态访问登录页时优先回到显式 redirect,避免 from 为根路径时触发重复重定向。
|
|
66
|
-
|
|
67
|
-
return targetPath;
|
|
72
|
+
return resolveSafeRedirect(redirectQuery);
|
|
68
73
|
}
|
|
69
74
|
resetRouter();
|
|
70
75
|
return true;
|
|
@@ -9,6 +9,7 @@ import { UserState } from "../../stores/interface";
|
|
|
9
9
|
import piniaPersistConfig from "../../stores/helper/persist";
|
|
10
10
|
import { useDictStoreHook } from "../../stores/modules/dict";
|
|
11
11
|
import { useLockScreenStore } from "../../stores/modules/lockScreen";
|
|
12
|
+
import { useAuthStore } from "../../stores/modules/auth";
|
|
12
13
|
|
|
13
14
|
const defaultUserInfo: UserInfoForm = {
|
|
14
15
|
user_name: "",
|
|
@@ -56,7 +57,7 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
56
57
|
/** 根据接口返回统一更新令牌信息 */
|
|
57
58
|
updateTokenAuth(accessToken: string, tokenType: string, expiresIn?: number) {
|
|
58
59
|
const tokenPrefix = tokenType ? `${tokenType} ` : "";
|
|
59
|
-
const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 :
|
|
60
|
+
const expiresAt = expiresIn && expiresIn > 0 ? Date.now() + expiresIn * 1000 : Number.MAX_SAFE_INTEGER;
|
|
60
61
|
|
|
61
62
|
this.authInvalidated = false;
|
|
62
63
|
this.setToken(`${tokenPrefix}${accessToken}`.trim());
|
|
@@ -98,6 +99,7 @@ export const useUserStore = defineStore("admin-user", {
|
|
|
98
99
|
// 清理登录态时同步清空字典缓存,避免切换账号后读到旧字典。
|
|
99
100
|
useDictStoreHook().clearDictionaryCache();
|
|
100
101
|
useLockScreenStore().clearLock();
|
|
102
|
+
useAuthStore().$reset();
|
|
101
103
|
this.authVersion += 1;
|
|
102
104
|
this.authInvalidated = true;
|
|
103
105
|
this.setToken("");
|
|
@@ -32,9 +32,11 @@ export function formatDateTime(value?: string | number | Date) {
|
|
|
32
32
|
export function formatSrc(src: string) {
|
|
33
33
|
const value = String(src ?? "").trim();
|
|
34
34
|
if (!value) return value;
|
|
35
|
-
if (/^(https?:)?\/\//.test(value) || value.startsWith("
|
|
35
|
+
if (/^(https?:)?\/\//.test(value) || value.startsWith("blob:")) {
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
38
|
+
if (value.startsWith("data:")) return /^data:image\/(?:gif|jpe?g|png|webp|bmp);base64,/i.test(value) ? value : "";
|
|
39
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(value)) return "";
|
|
38
40
|
|
|
39
41
|
const configuredBase = String(import.meta.env.VITE_APP_STATIC_URL ?? "").trim();
|
|
40
42
|
const staticBase = configuredBase
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liujitcn/kratos-admin-core",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.53",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin frontend runtime and base modules",
|
|
7
7
|
"author": {
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
"scripts": {
|
|
237
237
|
"build": "pnpm build:package",
|
|
238
238
|
"build:package": "node ../../scripts/build-package.mjs .",
|
|
239
|
-
"test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js",
|
|
239
|
+
"test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js dist/test/tests/codegen-hmr.test.js dist/test/tests/dialog-request.test.js",
|
|
240
240
|
"type:check": "vue-tsc --noEmit --skipLibCheck"
|
|
241
241
|
}
|
|
242
242
|
}
|