@liujitcn/kratos-admin-core 0.0.46 → 0.0.48
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 +6 -0
- package/dist/package/declarations/src/components/Dialog/FormDialog.vue.d.ts +3 -1
- package/dist/package/declarations/src/components/Dialog/PasswordVerifyDialog.vue.d.ts +4 -0
- package/dist/package/declarations/src/format.d.ts +1 -1
- package/dist/package/declarations/src/locales/generated.d.ts +108 -0
- package/dist/package/declarations/src/utils/proTable.d.ts +2 -12
- package/dist/package/declarations/src/utils/tableColumn.d.ts +11 -0
- package/dist/package/declarations/src/utils/utils.d.ts +2 -0
- package/dist/package/src/components/Dialog/FormDialog.vue +5 -2
- package/dist/package/src/components/Dialog/PasswordVerifyDialog.vue +6 -2
- package/dist/package/src/components/ProForm/index.vue +2 -2
- package/dist/package/src/components/ProTable/components/TableColumn.vue +147 -4
- package/dist/package/src/format.ts +1 -1
- package/dist/package/src/locales/en-US.json +27 -0
- package/dist/package/src/locales/index.ts +23 -9
- package/dist/package/src/locales/ja-JP.json +27 -0
- package/dist/package/src/locales/zh-CN.json +27 -0
- package/dist/package/src/locales/zh-TW.json +27 -0
- package/dist/package/src/utils/proTable.ts +3 -53
- package/dist/package/src/utils/tableColumn.ts +55 -0
- package/dist/package/src/utils/utils.ts +9 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -177,3 +177,9 @@ pnpm --filter @liujitcn/kratos-admin-core type:check
|
|
|
177
177
|
pnpm --filter @liujitcn/kratos-admin-core build:package
|
|
178
178
|
pnpm --filter @liujitcn/kratos-admin-core pack
|
|
179
179
|
```
|
|
180
|
+
|
|
181
|
+
时间列的对齐方式由页面通过 `align` 显式配置,ProTable 不按时间字段名称判断;修改时间列使用 `align: "center"` 居中显示。
|
|
182
|
+
|
|
183
|
+
ProTable 的 `cellType: "actions"` 操作列按当前行的 `hidden` 过滤按钮,优先展示使用 `common.action.edit`、`common.action.delete` 文案的编辑、删除操作;可见按钮超过三项时,其余操作收入“更多”菜单。未配置或无权限的编辑、删除不会补充显示,菜单保留图标、禁用状态和点击参数;自定义 render 或插槽不参与折叠。
|
|
184
|
+
|
|
185
|
+
操作列默认固定在右侧。配置式操作列(`cellType: "actions"`)根据当前页实际展示按钮和表头的最大宽度自适应,计入翻译文案、图标和间距,随语言、字体和行权限变化重新测量,最小宽度为 `80px`;折叠菜单内的按钮不参与测量。自定义 render 或插槽操作列仍默认 `160px`。页面可通过 `width` 或 `minWidth` 覆盖自动宽度,通过 `fixed: "left"` 调整固定位置,或通过 `fixed: false` 取消固定。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FormRules } from "element-plus";
|
|
2
|
-
import type { ProFormField } from "../../components/ProForm/interface";
|
|
2
|
+
import type { ProFormField, ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
3
3
|
/** 表单弹窗组件属性。 */
|
|
4
4
|
interface FormDialogProps {
|
|
5
5
|
modelValue: boolean;
|
|
@@ -10,6 +10,7 @@ interface FormDialogProps {
|
|
|
10
10
|
fields: ProFormField[];
|
|
11
11
|
rules?: FormRules;
|
|
12
12
|
labelWidth?: string;
|
|
13
|
+
labelPosition?: ProFormLabelPosition;
|
|
13
14
|
gutter?: number;
|
|
14
15
|
colSpan?: number;
|
|
15
16
|
confirmText?: string;
|
|
@@ -60,6 +61,7 @@ declare const __VLS_base: import("vue").DefineComponent<FormDialogProps, {
|
|
|
60
61
|
confirmLoading: boolean;
|
|
61
62
|
rules: FormRules;
|
|
62
63
|
labelWidth: string;
|
|
64
|
+
labelPosition: ProFormLabelPosition;
|
|
63
65
|
gutter: number;
|
|
64
66
|
colSpan: number;
|
|
65
67
|
formProps: Record<string, any>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
1
2
|
import type { PasswordCryptoScene } from "../../utils/passwordCrypto";
|
|
2
3
|
import type { PasswordCrypto } from "../../rpc/common/v1/types";
|
|
3
4
|
/** 通用密码验证弹窗属性。 */
|
|
@@ -32,6 +33,8 @@ interface PasswordVerifyDialogProps {
|
|
|
32
33
|
closeOnPressEscape?: boolean;
|
|
33
34
|
/** 表单标签宽度。 */
|
|
34
35
|
labelWidth?: string;
|
|
36
|
+
/** 表单标签布局方向。 */
|
|
37
|
+
labelPosition?: ProFormLabelPosition;
|
|
35
38
|
}
|
|
36
39
|
declare const __VLS_export: import("vue").DefineComponent<PasswordVerifyDialogProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
37
40
|
confirm: (password: PasswordCrypto) => any;
|
|
@@ -57,6 +60,7 @@ declare const __VLS_export: import("vue").DefineComponent<PasswordVerifyDialogPr
|
|
|
57
60
|
cancelText: string;
|
|
58
61
|
confirmLoading: boolean;
|
|
59
62
|
labelWidth: string;
|
|
63
|
+
labelPosition: ProFormLabelPosition;
|
|
60
64
|
passwordLabel: string;
|
|
61
65
|
passwordPlaceholder: string;
|
|
62
66
|
scene: PasswordCryptoScene;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { formatJson, formatSrc, normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "./utils/utils";
|
|
1
|
+
export { formatDateTime, formatJson, formatSrc, normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "./utils/utils";
|
|
@@ -12,6 +12,33 @@ export declare const LOCALE_MESSAGES: {
|
|
|
12
12
|
"common.action.delete": string;
|
|
13
13
|
"common.action.download": string;
|
|
14
14
|
"common.action.edit": string;
|
|
15
|
+
"common.action.next": string;
|
|
16
|
+
"common.action.previous": string;
|
|
17
|
+
"common.action.refresh": string;
|
|
18
|
+
"common.action.enable": string;
|
|
19
|
+
"common.action.disable": string;
|
|
20
|
+
"common.action.export": string;
|
|
21
|
+
"common.action.import": string;
|
|
22
|
+
"common.action.copy": string;
|
|
23
|
+
"common.action.detail": string;
|
|
24
|
+
"common.action.submit": string;
|
|
25
|
+
"common.action.clear": string;
|
|
26
|
+
"common.action.filter": string;
|
|
27
|
+
"common.action.more": string;
|
|
28
|
+
"common.action.publish": string;
|
|
29
|
+
"common.action.revoke": string;
|
|
30
|
+
"common.action.retry": string;
|
|
31
|
+
"common.action.start": string;
|
|
32
|
+
"common.action.stop": string;
|
|
33
|
+
"common.action.pause": string;
|
|
34
|
+
"common.action.resume": string;
|
|
35
|
+
"common.action.upload": string;
|
|
36
|
+
"common.action.preview": string;
|
|
37
|
+
"common.action.configure": string;
|
|
38
|
+
"common.action.continue": string;
|
|
39
|
+
"common.action.skip": string;
|
|
40
|
+
"common.action.select_all": string;
|
|
41
|
+
"common.action.deselect_all": string;
|
|
15
42
|
"common.action.expand": string;
|
|
16
43
|
"common.action.collapse": string;
|
|
17
44
|
"common.action.login": string;
|
|
@@ -350,6 +377,33 @@ export declare const LOCALE_MESSAGES: {
|
|
|
350
377
|
"common.action.delete": string;
|
|
351
378
|
"common.action.download": string;
|
|
352
379
|
"common.action.edit": string;
|
|
380
|
+
"common.action.next": string;
|
|
381
|
+
"common.action.previous": string;
|
|
382
|
+
"common.action.refresh": string;
|
|
383
|
+
"common.action.enable": string;
|
|
384
|
+
"common.action.disable": string;
|
|
385
|
+
"common.action.export": string;
|
|
386
|
+
"common.action.import": string;
|
|
387
|
+
"common.action.copy": string;
|
|
388
|
+
"common.action.detail": string;
|
|
389
|
+
"common.action.submit": string;
|
|
390
|
+
"common.action.clear": string;
|
|
391
|
+
"common.action.filter": string;
|
|
392
|
+
"common.action.more": string;
|
|
393
|
+
"common.action.publish": string;
|
|
394
|
+
"common.action.revoke": string;
|
|
395
|
+
"common.action.retry": string;
|
|
396
|
+
"common.action.start": string;
|
|
397
|
+
"common.action.stop": string;
|
|
398
|
+
"common.action.pause": string;
|
|
399
|
+
"common.action.resume": string;
|
|
400
|
+
"common.action.upload": string;
|
|
401
|
+
"common.action.preview": string;
|
|
402
|
+
"common.action.configure": string;
|
|
403
|
+
"common.action.continue": string;
|
|
404
|
+
"common.action.skip": string;
|
|
405
|
+
"common.action.select_all": string;
|
|
406
|
+
"common.action.deselect_all": string;
|
|
353
407
|
"common.action.expand": string;
|
|
354
408
|
"common.action.collapse": string;
|
|
355
409
|
"common.action.login": string;
|
|
@@ -688,6 +742,33 @@ export declare const LOCALE_MESSAGES: {
|
|
|
688
742
|
"common.action.delete": string;
|
|
689
743
|
"common.action.download": string;
|
|
690
744
|
"common.action.edit": string;
|
|
745
|
+
"common.action.next": string;
|
|
746
|
+
"common.action.previous": string;
|
|
747
|
+
"common.action.refresh": string;
|
|
748
|
+
"common.action.enable": string;
|
|
749
|
+
"common.action.disable": string;
|
|
750
|
+
"common.action.export": string;
|
|
751
|
+
"common.action.import": string;
|
|
752
|
+
"common.action.copy": string;
|
|
753
|
+
"common.action.detail": string;
|
|
754
|
+
"common.action.submit": string;
|
|
755
|
+
"common.action.clear": string;
|
|
756
|
+
"common.action.filter": string;
|
|
757
|
+
"common.action.more": string;
|
|
758
|
+
"common.action.publish": string;
|
|
759
|
+
"common.action.revoke": string;
|
|
760
|
+
"common.action.retry": string;
|
|
761
|
+
"common.action.start": string;
|
|
762
|
+
"common.action.stop": string;
|
|
763
|
+
"common.action.pause": string;
|
|
764
|
+
"common.action.resume": string;
|
|
765
|
+
"common.action.upload": string;
|
|
766
|
+
"common.action.preview": string;
|
|
767
|
+
"common.action.configure": string;
|
|
768
|
+
"common.action.continue": string;
|
|
769
|
+
"common.action.skip": string;
|
|
770
|
+
"common.action.select_all": string;
|
|
771
|
+
"common.action.deselect_all": string;
|
|
691
772
|
"common.action.expand": string;
|
|
692
773
|
"common.action.collapse": string;
|
|
693
774
|
"common.action.login": string;
|
|
@@ -1026,6 +1107,33 @@ export declare const LOCALE_MESSAGES: {
|
|
|
1026
1107
|
"common.action.delete": string;
|
|
1027
1108
|
"common.action.download": string;
|
|
1028
1109
|
"common.action.edit": string;
|
|
1110
|
+
"common.action.next": string;
|
|
1111
|
+
"common.action.previous": string;
|
|
1112
|
+
"common.action.refresh": string;
|
|
1113
|
+
"common.action.enable": string;
|
|
1114
|
+
"common.action.disable": string;
|
|
1115
|
+
"common.action.export": string;
|
|
1116
|
+
"common.action.import": string;
|
|
1117
|
+
"common.action.copy": string;
|
|
1118
|
+
"common.action.detail": string;
|
|
1119
|
+
"common.action.submit": string;
|
|
1120
|
+
"common.action.clear": string;
|
|
1121
|
+
"common.action.filter": string;
|
|
1122
|
+
"common.action.more": string;
|
|
1123
|
+
"common.action.publish": string;
|
|
1124
|
+
"common.action.revoke": string;
|
|
1125
|
+
"common.action.retry": string;
|
|
1126
|
+
"common.action.start": string;
|
|
1127
|
+
"common.action.stop": string;
|
|
1128
|
+
"common.action.pause": string;
|
|
1129
|
+
"common.action.resume": string;
|
|
1130
|
+
"common.action.upload": string;
|
|
1131
|
+
"common.action.preview": string;
|
|
1132
|
+
"common.action.configure": string;
|
|
1133
|
+
"common.action.continue": string;
|
|
1134
|
+
"common.action.skip": string;
|
|
1135
|
+
"common.action.select_all": string;
|
|
1136
|
+
"common.action.deselect_all": string;
|
|
1029
1137
|
"common.action.expand": string;
|
|
1030
1138
|
"common.action.collapse": string;
|
|
1031
1139
|
"common.action.login": string;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EnumProps } from "../components/ProTable/interface";
|
|
2
|
+
export { resolveTableColumnAlign, type TableAlign } from "./tableColumn";
|
|
2
3
|
/** 字典值输出给表格枚举时的目标类型。 */
|
|
3
4
|
type DictValueType = "number" | "string";
|
|
4
5
|
/** 表格批量操作支持的主键类型。 */
|
|
5
6
|
type SelectedId = string | number;
|
|
6
|
-
/** 表格单元格支持的对齐方式。 */
|
|
7
|
-
export type TableAlign = "left" | "center" | "right";
|
|
8
7
|
/** 表格分页入参。 */
|
|
9
8
|
type PageRequestParams = Record<string, any> & {
|
|
10
9
|
/** 当前页码。 */
|
|
@@ -19,14 +18,6 @@ type NormalizedPageRequest<T extends PageRequestParams> = T & {
|
|
|
19
18
|
/** 接口请求每页条数。 */
|
|
20
19
|
page_size: number;
|
|
21
20
|
};
|
|
22
|
-
/**
|
|
23
|
-
* 根据列语义和当前表格数据解析单元格对齐方式。
|
|
24
|
-
*
|
|
25
|
-
* 显式 align 始终优先;数据表映射按文本左对齐,字典、静态枚举及预置状态列居中,
|
|
26
|
-
* 金额列和纯数字列右对齐,其余内容左对齐。空表时会先按列语义返回默认值,
|
|
27
|
-
* 数据加载后由响应式表格重新解析。
|
|
28
|
-
*/
|
|
29
|
-
export declare function resolveTableColumnAlign(column: ColumnProps, rows?: Record<string, any>[]): TableAlign;
|
|
30
21
|
/**
|
|
31
22
|
* 将字典缓存转换为 ProTable 搜索枚举数据。
|
|
32
23
|
*/
|
|
@@ -41,4 +32,3 @@ export declare function buildPageRequest<T extends PageRequestParams>(params: T)
|
|
|
41
32
|
* 统一整理表格多选和单条操作产生的 ID 集合。
|
|
42
33
|
*/
|
|
43
34
|
export declare function normalizeSelectedIds(selected?: SelectedId | SelectedId[]): SelectedId[];
|
|
44
|
-
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ColumnProps } from "../components/ProTable/interface";
|
|
2
|
+
/** 表格单元格支持的对齐方式。 */
|
|
3
|
+
export type TableAlign = "left" | "center" | "right";
|
|
4
|
+
/**
|
|
5
|
+
* 根据列语义和当前表格数据解析单元格对齐方式。
|
|
6
|
+
*
|
|
7
|
+
* 显式 align 始终优先;字典、枚举及预置状态列居中,
|
|
8
|
+
* 金额列和纯数字列右对齐,其余内容左对齐。空表时会先按列语义返回默认值,
|
|
9
|
+
* 数据加载后由响应式表格重新解析。
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveTableColumnAlign(column: ColumnProps, rows?: Record<string, any>[]): TableAlign;
|
|
@@ -6,6 +6,8 @@ export declare function formatJson(str: string): string;
|
|
|
6
6
|
* 将后端分单位金额转换为元字符串。
|
|
7
7
|
*/
|
|
8
8
|
export declare function formatPrice(price?: number): string;
|
|
9
|
+
/** 将时间值格式化为统一的本地日期时间文本。 */
|
|
10
|
+
export declare function formatDateTime(value?: string | number | Date): string;
|
|
9
11
|
/**
|
|
10
12
|
* 按静态资源域名补齐图片地址。
|
|
11
13
|
*/
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
:fields="fields"
|
|
24
24
|
:rules="rules"
|
|
25
25
|
:label-width="labelWidth"
|
|
26
|
+
:label-position="labelPosition"
|
|
26
27
|
:gutter="gutter"
|
|
27
28
|
:col-span="colSpan"
|
|
28
29
|
v-bind="formProps"
|
|
@@ -50,7 +51,7 @@ import { computed, nextTick, ref, useSlots, watch } from "vue";
|
|
|
50
51
|
import type { FormRules } from "element-plus";
|
|
51
52
|
import ProDialog from "../../components/Dialog/ProDialog.vue";
|
|
52
53
|
import ProForm from "../../components/ProForm/index.vue";
|
|
53
|
-
import type { ProFormField, ProFormInstance } from "../../components/ProForm/interface";
|
|
54
|
+
import type { ProFormField, ProFormInstance, ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
54
55
|
import { useLocaleStore } from "../../locales";
|
|
55
56
|
|
|
56
57
|
const { t } = useLocaleStore();
|
|
@@ -65,6 +66,7 @@ interface FormDialogProps {
|
|
|
65
66
|
fields: ProFormField[];
|
|
66
67
|
rules?: FormRules;
|
|
67
68
|
labelWidth?: string;
|
|
69
|
+
labelPosition?: ProFormLabelPosition;
|
|
68
70
|
gutter?: number;
|
|
69
71
|
colSpan?: number;
|
|
70
72
|
confirmText?: string;
|
|
@@ -81,7 +83,8 @@ const props = withDefaults(defineProps<FormDialogProps>(), {
|
|
|
81
83
|
width: "500px",
|
|
82
84
|
top: "8vh",
|
|
83
85
|
rules: () => ({}),
|
|
84
|
-
labelWidth: "
|
|
86
|
+
labelWidth: "6em",
|
|
87
|
+
labelPosition: "right",
|
|
85
88
|
gutter: 20,
|
|
86
89
|
colSpan: 24,
|
|
87
90
|
confirmText: "",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
:fields="fields"
|
|
24
24
|
:rules="rules"
|
|
25
25
|
:label-width="labelWidth"
|
|
26
|
+
:label-position="labelPosition"
|
|
26
27
|
@keyup.enter.prevent="handleConfirm"
|
|
27
28
|
/>
|
|
28
29
|
</ProDialog>
|
|
@@ -33,7 +34,7 @@ import { computed, reactive, ref } from "vue";
|
|
|
33
34
|
import type { FormRules } from "element-plus";
|
|
34
35
|
import ProDialog from "../../components/Dialog/ProDialog.vue";
|
|
35
36
|
import ProForm from "../../components/ProForm/index.vue";
|
|
36
|
-
import type { ProFormField, ProFormInstance } from "../../components/ProForm/interface";
|
|
37
|
+
import type { ProFormField, ProFormInstance, ProFormLabelPosition } from "../../components/ProForm/interface";
|
|
37
38
|
import { useLocaleStore } from "../../locales";
|
|
38
39
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from "../../utils/passwordCrypto";
|
|
39
40
|
import type { PasswordCryptoScene } from "../../utils/passwordCrypto";
|
|
@@ -73,6 +74,8 @@ interface PasswordVerifyDialogProps {
|
|
|
73
74
|
closeOnPressEscape?: boolean;
|
|
74
75
|
/** 表单标签宽度。 */
|
|
75
76
|
labelWidth?: string;
|
|
77
|
+
/** 表单标签布局方向。 */
|
|
78
|
+
labelPosition?: ProFormLabelPosition;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
const props = withDefaults(defineProps<PasswordVerifyDialogProps>(), {
|
|
@@ -89,7 +92,8 @@ const props = withDefaults(defineProps<PasswordVerifyDialogProps>(), {
|
|
|
89
92
|
destroyOnClose: false,
|
|
90
93
|
closeOnClickModal: false,
|
|
91
94
|
closeOnPressEscape: true,
|
|
92
|
-
labelWidth: "
|
|
95
|
+
labelWidth: "6em",
|
|
96
|
+
labelPosition: "right"
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
const emit = defineEmits<{
|
|
@@ -3,8 +3,19 @@
|
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts" name="TableColumn">
|
|
6
|
-
import { h, inject, isProxy, markRaw, ref, toRaw, useSlots } from "vue";
|
|
7
|
-
import {
|
|
6
|
+
import { h, inject, isProxy, markRaw, onUnmounted, ref, toRaw, useSlots, withDirectives } from "vue";
|
|
7
|
+
import type { ObjectDirective } from "vue";
|
|
8
|
+
import {
|
|
9
|
+
ElButton,
|
|
10
|
+
ElDropdown,
|
|
11
|
+
ElDropdownItem,
|
|
12
|
+
ElDropdownMenu,
|
|
13
|
+
ElImage,
|
|
14
|
+
ElSwitch,
|
|
15
|
+
ElTableColumn,
|
|
16
|
+
ElTag,
|
|
17
|
+
ElText
|
|
18
|
+
} from "element-plus";
|
|
8
19
|
import DictLabel from "../../../components/Dict/DictLabel.vue";
|
|
9
20
|
import { ColumnProps, HeaderRenderScope, RenderScope, TableActionProps } from "../../../components/ProTable/interface";
|
|
10
21
|
import type { TableAlign } from "../../../utils/proTable";
|
|
@@ -21,6 +32,57 @@ const slots = useSlots();
|
|
|
21
32
|
|
|
22
33
|
const enumMap = inject("enumMap", ref(new Map()));
|
|
23
34
|
|
|
35
|
+
const actionWidths = ref(new Map<string, number>());
|
|
36
|
+
const measuredCells = new Map<HTMLElement, string>();
|
|
37
|
+
let resizeObserver: ResizeObserver | undefined;
|
|
38
|
+
let measureFrame = 0;
|
|
39
|
+
|
|
40
|
+
/** 合并当前列所有可见行和表头的实际宽度,支持语言、字体和权限变化。 */
|
|
41
|
+
const scheduleActionMeasurement = () => {
|
|
42
|
+
cancelAnimationFrame(measureFrame);
|
|
43
|
+
measureFrame = requestAnimationFrame(() => {
|
|
44
|
+
const widths = new Map<string, number>();
|
|
45
|
+
measuredCells.forEach((key, element) => {
|
|
46
|
+
if (!element.isConnected || !element.getClientRects().length) return;
|
|
47
|
+
const cell = element.closest(".cell");
|
|
48
|
+
if (!cell) return;
|
|
49
|
+
const style = getComputedStyle(cell);
|
|
50
|
+
const width = Math.ceil(
|
|
51
|
+
element.getBoundingClientRect().width + parseFloat(style.paddingLeft) + parseFloat(style.paddingRight) + 2
|
|
52
|
+
);
|
|
53
|
+
widths.set(key, Math.max(widths.get(key) ?? 80, width));
|
|
54
|
+
});
|
|
55
|
+
if (widths.size !== actionWidths.value.size || [...widths].some(([key, width]) => actionWidths.value.get(key) !== width)) {
|
|
56
|
+
actionWidths.value = widths;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** 观察渲染后的操作内容,避免按字符个数估算不同语言的文本宽度。 */
|
|
62
|
+
const measureActionContent: ObjectDirective<HTMLElement, string> = {
|
|
63
|
+
mounted(element, binding) {
|
|
64
|
+
measuredCells.set(element, binding.value);
|
|
65
|
+
resizeObserver ??= new ResizeObserver(scheduleActionMeasurement);
|
|
66
|
+
resizeObserver.observe(element);
|
|
67
|
+
scheduleActionMeasurement();
|
|
68
|
+
},
|
|
69
|
+
updated(element, binding) {
|
|
70
|
+
measuredCells.set(element, binding.value);
|
|
71
|
+
scheduleActionMeasurement();
|
|
72
|
+
},
|
|
73
|
+
unmounted(element) {
|
|
74
|
+
measuredCells.delete(element);
|
|
75
|
+
resizeObserver?.unobserve(element);
|
|
76
|
+
scheduleActionMeasurement();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
onUnmounted(() => {
|
|
81
|
+
resizeObserver?.disconnect();
|
|
82
|
+
cancelAnimationFrame(measureFrame);
|
|
83
|
+
measuredCells.clear();
|
|
84
|
+
});
|
|
85
|
+
|
|
24
86
|
/**
|
|
25
87
|
* 透传给 Element Plus 前移除图标组件上的响应式代理,避免 Vue 对组件对象发出性能告警。
|
|
26
88
|
*/
|
|
@@ -166,18 +228,23 @@ const renderMoneyCell = (item: ColumnProps, scope: RenderScope<any>) => {
|
|
|
166
228
|
};
|
|
167
229
|
|
|
168
230
|
/**
|
|
169
|
-
*
|
|
231
|
+
* 渲染操作按钮列,优先展示编辑、删除,超过三项时将其他操作折叠。
|
|
170
232
|
*/
|
|
171
233
|
const renderActionsCell = (item: ColumnProps, scope: RenderScope<any>) => {
|
|
172
234
|
if (!item.actions?.length) return "--";
|
|
173
235
|
const visibleActions = item.actions.filter(action => !getBooleanValue(action.hidden, scope));
|
|
174
236
|
if (!visibleActions.length) return "--";
|
|
175
|
-
|
|
237
|
+
const primaryLabels = [t("common.action.edit"), t("common.action.delete")];
|
|
238
|
+
const primaryActions = primaryLabels.flatMap(label => visibleActions.filter(action => action.label === label));
|
|
239
|
+
const otherActions = visibleActions.filter(action => !primaryLabels.includes(action.label));
|
|
240
|
+
const inlineActions = visibleActions.length > 3 ? primaryActions : [...primaryActions, ...otherActions];
|
|
241
|
+
const buttons = inlineActions.map((action: TableActionProps) => {
|
|
176
242
|
const params = resolveColumnParams(action.params, scope);
|
|
177
243
|
return h(
|
|
178
244
|
ElButton,
|
|
179
245
|
{
|
|
180
246
|
key: action.label,
|
|
247
|
+
size: "small",
|
|
181
248
|
type: action.type ?? "primary",
|
|
182
249
|
link: action.link ?? true,
|
|
183
250
|
icon: normalizeActionIcon(action.icon),
|
|
@@ -187,6 +254,41 @@ const renderActionsCell = (item: ColumnProps, scope: RenderScope<any>) => {
|
|
|
187
254
|
{ default: () => action.label }
|
|
188
255
|
);
|
|
189
256
|
});
|
|
257
|
+
if (visibleActions.length > 3 && otherActions.length)
|
|
258
|
+
buttons.push(
|
|
259
|
+
h(
|
|
260
|
+
ElDropdown,
|
|
261
|
+
{ trigger: "click", placement: "bottom-end", size: "small" },
|
|
262
|
+
{
|
|
263
|
+
default: () =>
|
|
264
|
+
h(ElButton, { type: "primary", link: true, size: "small", icon: ArrowDown }, () => t("common.action.more")),
|
|
265
|
+
dropdown: () =>
|
|
266
|
+
h(ElDropdownMenu, null, () =>
|
|
267
|
+
otherActions.map(action => {
|
|
268
|
+
const disabled = getBooleanValue(action.disabled, scope);
|
|
269
|
+
const params = resolveColumnParams(action.params, scope);
|
|
270
|
+
return h(
|
|
271
|
+
ElDropdownItem,
|
|
272
|
+
{
|
|
273
|
+
key: action.label,
|
|
274
|
+
icon: normalizeActionIcon(action.icon),
|
|
275
|
+
disabled,
|
|
276
|
+
style: disabled ? undefined : { color: `var(--el-color-${action.type ?? "primary"})` },
|
|
277
|
+
onClick: () => {
|
|
278
|
+
if (!disabled) action.onClick(scope, params);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
() => action.label
|
|
282
|
+
);
|
|
283
|
+
})
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
);
|
|
288
|
+
const content = h("span", { class: "pro-table-actions" }, buttons);
|
|
289
|
+
return item.width == null && item.minWidth == null
|
|
290
|
+
? withDirectives(content, [[measureActionContent, item.prop ?? item.label ?? ""]])
|
|
291
|
+
: content;
|
|
190
292
|
};
|
|
191
293
|
|
|
192
294
|
/** 渲染默认表头标题,避免窄列换行并保留完整标题提示。 */
|
|
@@ -213,12 +315,23 @@ const renderPresetCell = (item: ColumnProps, scope: RenderScope<any>) => {
|
|
|
213
315
|
}
|
|
214
316
|
};
|
|
215
317
|
|
|
318
|
+
/** 渲染表格列,为操作列提供自适应宽度和右侧固定默认值。 */
|
|
216
319
|
const RenderTableColumn = (item: ColumnProps) => {
|
|
217
320
|
if (!item.isShow) return null;
|
|
321
|
+
const isActionColumn = item.cellType === "actions" || item.prop === "operation";
|
|
322
|
+
const autoActionWidth = item.cellType === "actions" && !item.render && !(item.prop && slots[handleProp(item.prop)]);
|
|
218
323
|
return h(
|
|
219
324
|
ElTableColumn,
|
|
220
325
|
{
|
|
221
326
|
...item,
|
|
327
|
+
width:
|
|
328
|
+
item.width ??
|
|
329
|
+
(isActionColumn && item.minWidth == null
|
|
330
|
+
? autoActionWidth
|
|
331
|
+
? (actionWidths.value.get(item.prop ?? item.label ?? "") ?? 80)
|
|
332
|
+
: 160
|
|
333
|
+
: undefined),
|
|
334
|
+
fixed: item.fixed ?? (isActionColumn ? "right" : undefined),
|
|
222
335
|
align: item.align ?? props.resolveAlign?.(item) ?? "left",
|
|
223
336
|
showOverflowTooltip: item.showOverflowTooltip ?? item.prop !== "operation"
|
|
224
337
|
},
|
|
@@ -235,9 +348,39 @@ const RenderTableColumn = (item: ColumnProps) => {
|
|
|
235
348
|
header: (scope: HeaderRenderScope<any>) => {
|
|
236
349
|
if (item.headerRender) return item.headerRender(scope);
|
|
237
350
|
if (item.prop && slots[`${handleProp(item.prop)}Header`]) return slots[`${handleProp(item.prop)}Header`]!(scope);
|
|
351
|
+
if (autoActionWidth && item.width == null && item.minWidth == null) {
|
|
352
|
+
return withDirectives(h("span", { class: "pro-table-action-header" }, item.label), [
|
|
353
|
+
[measureActionContent, item.prop ?? item.label ?? ""]
|
|
354
|
+
]);
|
|
355
|
+
}
|
|
238
356
|
return renderHeaderLabel(item);
|
|
239
357
|
}
|
|
240
358
|
}
|
|
241
359
|
);
|
|
242
360
|
};
|
|
243
361
|
</script>
|
|
362
|
+
|
|
363
|
+
<style lang="scss">
|
|
364
|
+
// 操作容器在函数式列的插槽中生成,不带本组件的 scopeId,使用专属类名限定样式。
|
|
365
|
+
.pro-table-actions {
|
|
366
|
+
display: inline-flex;
|
|
367
|
+
flex-wrap: nowrap;
|
|
368
|
+
gap: 4px;
|
|
369
|
+
align-items: center;
|
|
370
|
+
width: max-content;
|
|
371
|
+
vertical-align: middle;
|
|
372
|
+
white-space: nowrap;
|
|
373
|
+
.el-button {
|
|
374
|
+
margin-left: 0;
|
|
375
|
+
}
|
|
376
|
+
.el-dropdown {
|
|
377
|
+
display: inline-flex;
|
|
378
|
+
align-items: center;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
.pro-table-action-header {
|
|
382
|
+
display: inline-block;
|
|
383
|
+
width: max-content;
|
|
384
|
+
white-space: nowrap;
|
|
385
|
+
}
|
|
386
|
+
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { formatJson, formatSrc, normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "./utils/utils";
|
|
1
|
+
export { formatDateTime, formatJson, formatSrc, normalizeRichTextMediaPaths, normalizeStaticAssetPath } from "./utils/utils";
|
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
"common.action.delete": "Delete",
|
|
8
8
|
"common.action.download": "Download",
|
|
9
9
|
"common.action.edit": "Edit",
|
|
10
|
+
"common.action.next": "Next",
|
|
11
|
+
"common.action.previous": "Previous",
|
|
12
|
+
"common.action.refresh": "Refresh",
|
|
13
|
+
"common.action.enable": "Enable",
|
|
14
|
+
"common.action.disable": "Disable",
|
|
15
|
+
"common.action.export": "Export",
|
|
16
|
+
"common.action.import": "Import",
|
|
17
|
+
"common.action.copy": "Copy",
|
|
18
|
+
"common.action.detail": "Details",
|
|
19
|
+
"common.action.submit": "Submit",
|
|
20
|
+
"common.action.clear": "Clear",
|
|
21
|
+
"common.action.filter": "Filter",
|
|
22
|
+
"common.action.more": "More",
|
|
23
|
+
"common.action.publish": "Publish",
|
|
24
|
+
"common.action.revoke": "Revoke",
|
|
25
|
+
"common.action.retry": "Retry",
|
|
26
|
+
"common.action.start": "Start",
|
|
27
|
+
"common.action.stop": "Stop",
|
|
28
|
+
"common.action.pause": "Pause",
|
|
29
|
+
"common.action.resume": "Resume",
|
|
30
|
+
"common.action.upload": "Upload",
|
|
31
|
+
"common.action.preview": "Preview",
|
|
32
|
+
"common.action.configure": "Configure",
|
|
33
|
+
"common.action.continue": "Continue",
|
|
34
|
+
"common.action.skip": "Skip",
|
|
35
|
+
"common.action.select_all": "Select all",
|
|
36
|
+
"common.action.deselect_all": "Deselect all",
|
|
10
37
|
"common.action.expand": "Expand",
|
|
11
38
|
"common.action.collapse": "Collapse",
|
|
12
39
|
"common.action.login": "Log in",
|
|
@@ -72,23 +72,30 @@ export function registerLocaleMessages(modules: AdminModule[]): void {
|
|
|
72
72
|
|
|
73
73
|
modules.forEach(module => {
|
|
74
74
|
if (!module.messages) return;
|
|
75
|
-
const
|
|
75
|
+
const defaultMessages = module.messages[DEFAULT_LOCALE] ?? {};
|
|
76
|
+
const expectedKeys = requiredLocaleKeys(defaultMessages);
|
|
76
77
|
SUPPORTED_LOCALES.forEach(locale => {
|
|
77
78
|
const messages = module.messages?.[locale];
|
|
78
|
-
if (!messages)
|
|
79
|
-
|
|
79
|
+
if (!messages) {
|
|
80
|
+
warnLocaleIssue(`${module.name} 缺少 ${locale} 语言包,已回退到 ${DEFAULT_LOCALE}`);
|
|
81
|
+
}
|
|
82
|
+
const targetMessages = messages ?? {};
|
|
83
|
+
const keys = requiredLocaleKeys(targetMessages);
|
|
80
84
|
if (keys.join("\u0000") !== expectedKeys.join("\u0000")) {
|
|
81
|
-
|
|
85
|
+
warnLocaleIssue(`${module.name} 的 ${locale} 语言包键集合不一致,缺失文案已回退到 ${DEFAULT_LOCALE}`);
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
const target = merged.get(locale) as LocaleMessages;
|
|
85
|
-
|
|
89
|
+
expectedKeys.forEach(key => {
|
|
90
|
+
let message = targetMessages[key] ?? defaultMessages[key];
|
|
86
91
|
assertLocaleKeyNamespace(module.name, key);
|
|
87
92
|
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
|
88
93
|
throw new Error(`${locale} 语言键重复: ${key}`);
|
|
89
94
|
}
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
if (!hasMatchingLocalePlaceholders(module.name, key, message, defaultMessages[key] ?? "")) {
|
|
96
|
+
message = defaultMessages[key] ?? "";
|
|
97
|
+
}
|
|
98
|
+
target[key] = message;
|
|
92
99
|
});
|
|
93
100
|
});
|
|
94
101
|
});
|
|
@@ -215,9 +222,16 @@ function requiredLocaleKeys(messages: LocaleMessages): string[] {
|
|
|
215
222
|
.sort();
|
|
216
223
|
}
|
|
217
224
|
|
|
218
|
-
function
|
|
225
|
+
function hasMatchingLocalePlaceholders(moduleName: string, key: string, message: string, sourceMessage: string): boolean {
|
|
219
226
|
const placeholders = (value: string) => [...value.matchAll(/\{([A-Za-z0-9_]+)\}/g)].map(match => match[1]).sort();
|
|
220
227
|
if (placeholders(message).join("\u0000") !== placeholders(sourceMessage).join("\u0000")) {
|
|
221
|
-
|
|
228
|
+
warnLocaleIssue(`${moduleName} 的 ${key} 占位符集合不一致,已回退到 ${DEFAULT_LOCALE}`);
|
|
229
|
+
return false;
|
|
222
230
|
}
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 记录语言包问题但不阻断应用启动。 */
|
|
235
|
+
function warnLocaleIssue(message: string): void {
|
|
236
|
+
console.warn(`[i18n] ${message}`);
|
|
223
237
|
}
|
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
"common.action.delete": "削除",
|
|
8
8
|
"common.action.download": "ダウンロード",
|
|
9
9
|
"common.action.edit": "編集者",
|
|
10
|
+
"common.action.next": "次へ",
|
|
11
|
+
"common.action.previous": "前へ",
|
|
12
|
+
"common.action.refresh": "更新",
|
|
13
|
+
"common.action.enable": "有効化",
|
|
14
|
+
"common.action.disable": "無効化",
|
|
15
|
+
"common.action.export": "エクスポート",
|
|
16
|
+
"common.action.import": "インポート",
|
|
17
|
+
"common.action.copy": "コピー",
|
|
18
|
+
"common.action.detail": "詳細",
|
|
19
|
+
"common.action.submit": "送信",
|
|
20
|
+
"common.action.clear": "クリア",
|
|
21
|
+
"common.action.filter": "フィルター",
|
|
22
|
+
"common.action.more": "その他",
|
|
23
|
+
"common.action.publish": "公開",
|
|
24
|
+
"common.action.revoke": "取り消し",
|
|
25
|
+
"common.action.retry": "再試行",
|
|
26
|
+
"common.action.start": "開始",
|
|
27
|
+
"common.action.stop": "停止",
|
|
28
|
+
"common.action.pause": "一時停止",
|
|
29
|
+
"common.action.resume": "再開",
|
|
30
|
+
"common.action.upload": "アップロード",
|
|
31
|
+
"common.action.preview": "プレビュー",
|
|
32
|
+
"common.action.configure": "設定",
|
|
33
|
+
"common.action.continue": "続行",
|
|
34
|
+
"common.action.skip": "スキップ",
|
|
35
|
+
"common.action.select_all": "すべて選択",
|
|
36
|
+
"common.action.deselect_all": "すべて選択解除",
|
|
10
37
|
"common.action.expand": "展開する",
|
|
11
38
|
"common.action.collapse": "閉じる",
|
|
12
39
|
"common.action.login": "ログイン",
|
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
"common.action.delete": "删除",
|
|
8
8
|
"common.action.download": "下载",
|
|
9
9
|
"common.action.edit": "编辑",
|
|
10
|
+
"common.action.next": "下一步",
|
|
11
|
+
"common.action.previous": "上一步",
|
|
12
|
+
"common.action.refresh": "刷新",
|
|
13
|
+
"common.action.enable": "启用",
|
|
14
|
+
"common.action.disable": "禁用",
|
|
15
|
+
"common.action.export": "导出",
|
|
16
|
+
"common.action.import": "导入",
|
|
17
|
+
"common.action.copy": "复制",
|
|
18
|
+
"common.action.detail": "详情",
|
|
19
|
+
"common.action.submit": "提交",
|
|
20
|
+
"common.action.clear": "清空",
|
|
21
|
+
"common.action.filter": "筛选",
|
|
22
|
+
"common.action.more": "更多",
|
|
23
|
+
"common.action.publish": "发布",
|
|
24
|
+
"common.action.revoke": "撤销",
|
|
25
|
+
"common.action.retry": "重试",
|
|
26
|
+
"common.action.start": "开始",
|
|
27
|
+
"common.action.stop": "停止",
|
|
28
|
+
"common.action.pause": "暂停",
|
|
29
|
+
"common.action.resume": "恢复",
|
|
30
|
+
"common.action.upload": "上传",
|
|
31
|
+
"common.action.preview": "预览",
|
|
32
|
+
"common.action.configure": "配置",
|
|
33
|
+
"common.action.continue": "继续",
|
|
34
|
+
"common.action.skip": "跳过",
|
|
35
|
+
"common.action.select_all": "全选",
|
|
36
|
+
"common.action.deselect_all": "取消全选",
|
|
10
37
|
"common.action.expand": "展开",
|
|
11
38
|
"common.action.collapse": "收起",
|
|
12
39
|
"common.action.login": "登录",
|
|
@@ -7,6 +7,33 @@
|
|
|
7
7
|
"common.action.delete": "刪除",
|
|
8
8
|
"common.action.download": "下載",
|
|
9
9
|
"common.action.edit": "編輯",
|
|
10
|
+
"common.action.next": "下一步",
|
|
11
|
+
"common.action.previous": "上一步",
|
|
12
|
+
"common.action.refresh": "重新整理",
|
|
13
|
+
"common.action.enable": "啟用",
|
|
14
|
+
"common.action.disable": "停用",
|
|
15
|
+
"common.action.export": "匯出",
|
|
16
|
+
"common.action.import": "匯入",
|
|
17
|
+
"common.action.copy": "複製",
|
|
18
|
+
"common.action.detail": "詳情",
|
|
19
|
+
"common.action.submit": "提交",
|
|
20
|
+
"common.action.clear": "清空",
|
|
21
|
+
"common.action.filter": "篩選",
|
|
22
|
+
"common.action.more": "更多",
|
|
23
|
+
"common.action.publish": "發布",
|
|
24
|
+
"common.action.revoke": "撤銷",
|
|
25
|
+
"common.action.retry": "重試",
|
|
26
|
+
"common.action.start": "開始",
|
|
27
|
+
"common.action.stop": "停止",
|
|
28
|
+
"common.action.pause": "暫停",
|
|
29
|
+
"common.action.resume": "恢復",
|
|
30
|
+
"common.action.upload": "上傳",
|
|
31
|
+
"common.action.preview": "預覽",
|
|
32
|
+
"common.action.configure": "設定",
|
|
33
|
+
"common.action.continue": "繼續",
|
|
34
|
+
"common.action.skip": "跳過",
|
|
35
|
+
"common.action.select_all": "全選",
|
|
36
|
+
"common.action.deselect_all": "取消全選",
|
|
10
37
|
"common.action.expand": "展開",
|
|
11
38
|
"common.action.collapse": "收起",
|
|
12
39
|
"common.action.login": "登入",
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EnumProps } from "../components/ProTable/interface";
|
|
2
2
|
import type { OptionBaseDictResponse_BaseDictItem } from "../rpc/system/admin/v1/base_dict";
|
|
3
3
|
import { useDictStoreHook } from "../stores/modules/dict";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
export { resolveTableColumnAlign, type TableAlign } from "./tableColumn";
|
|
5
6
|
|
|
6
7
|
/** 字典值输出给表格枚举时的目标类型。 */
|
|
7
8
|
type DictValueType = "number" | "string";
|
|
8
9
|
/** 表格批量操作支持的主键类型。 */
|
|
9
10
|
type SelectedId = string | number;
|
|
10
11
|
|
|
11
|
-
/** 表格单元格支持的对齐方式。 */
|
|
12
|
-
export type TableAlign = "left" | "center" | "right";
|
|
13
|
-
|
|
14
12
|
/** 表格分页入参。 */
|
|
15
13
|
type PageRequestParams = Record<string, any> & {
|
|
16
14
|
/** 当前页码。 */
|
|
@@ -27,54 +25,6 @@ type NormalizedPageRequest<T extends PageRequestParams> = T & {
|
|
|
27
25
|
page_size: number;
|
|
28
26
|
};
|
|
29
27
|
|
|
30
|
-
/**
|
|
31
|
-
* 获取表格行的列值,支持点号分隔的多级字段。
|
|
32
|
-
*/
|
|
33
|
-
function getTableCellValue(row: Record<string, any>, prop: string) {
|
|
34
|
-
return prop.split(".").reduce((value, key) => (value == null ? undefined : value[key]), row as any);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 判断值是否为可用于数值对齐的 Number 类型。
|
|
39
|
-
*/
|
|
40
|
-
function isPureNumber(value: unknown) {
|
|
41
|
-
return typeof value === "number" && Number.isFinite(value);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 判断枚举是否来自数据表映射,远程函数和响应式选项都属于动态数据映射。
|
|
46
|
-
*/
|
|
47
|
-
function isDataTableEnum(enumValue: ColumnProps["enum"]) {
|
|
48
|
-
return typeof enumValue === "function" || isRef(enumValue);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 根据列语义和当前表格数据解析单元格对齐方式。
|
|
53
|
-
*
|
|
54
|
-
* 显式 align 始终优先;数据表映射按文本左对齐,字典、静态枚举及预置状态列居中,
|
|
55
|
-
* 金额列和纯数字列右对齐,其余内容左对齐。空表时会先按列语义返回默认值,
|
|
56
|
-
* 数据加载后由响应式表格重新解析。
|
|
57
|
-
*/
|
|
58
|
-
export function resolveTableColumnAlign(column: ColumnProps, rows: Record<string, any>[] = []): TableAlign {
|
|
59
|
-
if (column.align === "left" || column.align === "center" || column.align === "right") return column.align;
|
|
60
|
-
|
|
61
|
-
const centeredTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"];
|
|
62
|
-
if (centeredTypes.includes(column.type as TypeProps)) return "center";
|
|
63
|
-
if (column.cellType === "actions" || column.cellType === "status" || column.cellType === "image") return "center";
|
|
64
|
-
if (column.cellType === "money") return "right";
|
|
65
|
-
if (column.dictCode || column.tag) return "center";
|
|
66
|
-
if (column.enum) return isDataTableEnum(column.enum) ? "left" : "center";
|
|
67
|
-
|
|
68
|
-
if (column.prop) {
|
|
69
|
-
const values = rows
|
|
70
|
-
.map(row => getTableCellValue(row, column.prop as string))
|
|
71
|
-
.filter(value => value !== undefined && value !== null && value !== "");
|
|
72
|
-
if (values.length && values.every(isPureNumber)) return "right";
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return "left";
|
|
76
|
-
}
|
|
77
|
-
|
|
78
28
|
/**
|
|
79
29
|
* 按配置将字典值转换为表格枚举可识别的类型。
|
|
80
30
|
*/
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ColumnProps, TypeProps } from "../components/ProTable/interface";
|
|
2
|
+
|
|
3
|
+
/** 表格单元格支持的对齐方式。 */
|
|
4
|
+
export type TableAlign = "left" | "center" | "right";
|
|
5
|
+
|
|
6
|
+
/** 获取表格行的列值,支持点号分隔的多级字段。 */
|
|
7
|
+
function getTableCellValue(row: Record<string, any>, prop: string) {
|
|
8
|
+
return prop.split(".").reduce((value, key) => (value == null ? undefined : value[key]), row as any);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** 判断值是否为可用于数值对齐的 Number 类型。 */
|
|
12
|
+
function isPureNumber(value: unknown) {
|
|
13
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 判断列字段名是否表示时间值。 */
|
|
17
|
+
function isTimeColumn(column: ColumnProps) {
|
|
18
|
+
const prop = column.prop?.toLowerCase();
|
|
19
|
+
return !!prop && /(^|[._])(created|updated|deleted|expires|scheduled|execute|start|end|login|logout|last)[._]?at?$/.test(prop);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** 判断列字段名是否表示数值。 */
|
|
23
|
+
function isNumericColumn(column: ColumnProps) {
|
|
24
|
+
const prop = column.prop?.toLowerCase();
|
|
25
|
+
return !!prop && /(^|[._])(id|sort|size|count|total|rows|priority|status_code|latency|duration|process_time|ttl|retention|attempt|qps|rate|number|num|amount|days|minutes|seconds|ms)$/.test(prop);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 根据列语义和当前表格数据解析单元格对齐方式。
|
|
30
|
+
*
|
|
31
|
+
* 显式 align 始终优先;字典、枚举及预置状态列居中,
|
|
32
|
+
* 金额列和纯数字列右对齐,其余内容左对齐。空表时会先按列语义返回默认值,
|
|
33
|
+
* 数据加载后由响应式表格重新解析。
|
|
34
|
+
*/
|
|
35
|
+
export function resolveTableColumnAlign(column: ColumnProps, rows: Record<string, any>[] = []): TableAlign {
|
|
36
|
+
if (column.align === "left" || column.align === "center" || column.align === "right") return column.align;
|
|
37
|
+
|
|
38
|
+
const centeredTypes: TypeProps[] = ["selection", "radio", "index", "expand", "sort"];
|
|
39
|
+
if (centeredTypes.includes(column.type as TypeProps)) return "center";
|
|
40
|
+
if (column.cellType === "actions" || column.cellType === "status" || column.cellType === "image") return "center";
|
|
41
|
+
if (column.cellType === "money") return "right";
|
|
42
|
+
if (column.dictCode || column.tag) return "center";
|
|
43
|
+
if (column.enum) return "center";
|
|
44
|
+
if (isTimeColumn(column)) return "center";
|
|
45
|
+
if (isNumericColumn(column)) return "right";
|
|
46
|
+
|
|
47
|
+
if (column.prop) {
|
|
48
|
+
const values = rows
|
|
49
|
+
.map(row => getTableCellValue(row, column.prop as string))
|
|
50
|
+
.filter(value => value !== undefined && value !== null && value !== "");
|
|
51
|
+
if (values.length && values.every(isPureNumber)) return "right";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return "left";
|
|
55
|
+
}
|
|
@@ -17,6 +17,15 @@ export function formatPrice(price?: number) {
|
|
|
17
17
|
return (price / 100).toFixed(2);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** 将时间值格式化为统一的本地日期时间文本。 */
|
|
21
|
+
export function formatDateTime(value?: string | number | Date) {
|
|
22
|
+
if (value === undefined || value === null || value === "") return "-";
|
|
23
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
24
|
+
if (Number.isNaN(date.getTime())) return String(value);
|
|
25
|
+
const pad = (part: number) => String(part).padStart(2, "0");
|
|
26
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
/**
|
|
21
30
|
* 按静态资源域名补齐图片地址。
|
|
22
31
|
*/
|
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.48",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "kratos-admin frontend runtime and base modules",
|
|
7
7
|
"author": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"dayjs": "^1.11.21",
|
|
33
33
|
"dompurify": "^3.4.5",
|
|
34
34
|
"driver.js": "^1.3.1",
|
|
35
|
-
"echarts": "^
|
|
35
|
+
"echarts": "^6.1.0",
|
|
36
36
|
"echarts-liquidfill": "^3.1.0",
|
|
37
37
|
"element-plus": "^2.7.6",
|
|
38
38
|
"go-captcha-vue": "2.0.6",
|