p-pc-ui 1.3.10 → 1.3.12
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/components/p-button-rounded/p-button-rounded.vue +1 -1
- package/dist/components/p-form/index.d.ts +34 -5
- package/dist/components/p-form/p-form.vue +268 -132
- package/dist/components/p-modal-confirm/p-modal-confirm.vue +2 -0
- package/dist/components/p-table/p-table.vue +340 -29
- package/dist/utils/dataUtils.ts +52 -26
- package/package.json +3 -2
|
@@ -60,7 +60,7 @@ const btnClick = useDebounceFn(async () => {
|
|
|
60
60
|
|
|
61
61
|
<a-tooltip color="var(--secondary-color-light)" :overlayInnerStyle="{ color: '#fff' }" :title="tooltip"
|
|
62
62
|
v-bind="$attrs">
|
|
63
|
-
<a-button @click="btnClick" :loading="loading || builtInLoading" class="font-semibold"
|
|
63
|
+
<a-button @click="btnClick" @keydown.enter.prevent="btnClick" :loading="loading || builtInLoading" class="font-semibold"
|
|
64
64
|
:class="disabled ? 'cursor-not-allowed' : 'hover:opacity-80 active:opacity-100'" :style="{
|
|
65
65
|
background: disabled ? '#b5b5b5' : bgColor,
|
|
66
66
|
color: disabled ? '#fff' : color,
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
|
|
2
|
-
type FieldRule = {
|
|
2
|
+
type FieldRule = {
|
|
3
|
+
msg?: string,
|
|
4
|
+
required?: boolean,
|
|
5
|
+
min?: number,
|
|
6
|
+
max?: number,
|
|
7
|
+
decimal?: number,
|
|
8
|
+
regex?: RegExp,
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
interface Base<K extends string = string> {
|
|
5
12
|
key: K,
|
|
@@ -49,7 +56,24 @@ export interface InputCode<K extends string = string> extends Base<K> {
|
|
|
49
56
|
|
|
50
57
|
|
|
51
58
|
|
|
52
|
-
type
|
|
59
|
+
type LoadOptionsParams = {
|
|
60
|
+
p?: number,
|
|
61
|
+
pc?: number,
|
|
62
|
+
[key: string]: any,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type LoadOptionsResult =
|
|
66
|
+
| any[]
|
|
67
|
+
| {
|
|
68
|
+
data?: any[],
|
|
69
|
+
count?: number,
|
|
70
|
+
pages?: {
|
|
71
|
+
total?: number,
|
|
72
|
+
now?: number,
|
|
73
|
+
},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type LoadOptionsData = (params?: LoadOptionsParams) => Promise<LoadOptionsResult>
|
|
53
77
|
|
|
54
78
|
|
|
55
79
|
interface SelectBase<K extends string = string> extends Base<K> {
|
|
@@ -58,7 +82,12 @@ interface SelectBase<K extends string = string> extends Base<K> {
|
|
|
58
82
|
label?: string;
|
|
59
83
|
value?: string;
|
|
60
84
|
};
|
|
61
|
-
isMultiple?: boolean
|
|
85
|
+
isMultiple?: boolean,
|
|
86
|
+
enableSearch?: boolean,
|
|
87
|
+
pageSize?: number,
|
|
88
|
+
pageNumKey?: string,
|
|
89
|
+
pageSizeKey?: string,
|
|
90
|
+
keywordKey?: string,
|
|
62
91
|
}
|
|
63
92
|
|
|
64
93
|
type GetOptionList =
|
|
@@ -76,13 +105,13 @@ interface UploadBase<K extends string = string> extends Base<K> {
|
|
|
76
105
|
|
|
77
106
|
export interface UploadOss<K extends string = string> extends UploadBase<K> {
|
|
78
107
|
type: 'uploadOss',
|
|
79
|
-
getOssToken: ({ file_name }) => Promise<any>,
|
|
108
|
+
getOssToken: ({ file_name }: { file_name: string }) => Promise<any>,
|
|
80
109
|
baseOssUrl: string,
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
export interface UploadTos<K extends string = string> extends UploadBase<K> {
|
|
84
113
|
type: 'uploadTos',
|
|
85
|
-
getTosToken: ({ file_name }) => Promise<any>,
|
|
114
|
+
getTosToken: ({ file_name }: { file_name: string }) => Promise<any>,
|
|
86
115
|
baseTosUrl: string,
|
|
87
116
|
}
|
|
88
117
|
|
|
@@ -58,6 +58,170 @@ const formRef = ref<FormInstance>();
|
|
|
58
58
|
|
|
59
59
|
const emits = defineEmits(["confirm"]);
|
|
60
60
|
|
|
61
|
+
const DEFAULT_SELECT_PAGE_SIZE = 20;
|
|
62
|
+
|
|
63
|
+
type SelectLoadResult =
|
|
64
|
+
| any[]
|
|
65
|
+
| {
|
|
66
|
+
data?: any[];
|
|
67
|
+
count?: number;
|
|
68
|
+
pages?: {
|
|
69
|
+
total?: number;
|
|
70
|
+
now?: number;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
type SelectLoadState = {
|
|
75
|
+
pageNum: number;
|
|
76
|
+
pageSize: number;
|
|
77
|
+
keyword: string;
|
|
78
|
+
hasMore: boolean;
|
|
79
|
+
loading: boolean;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const getSelectValueField = (renderItem: any) => {
|
|
83
|
+
return renderItem.fieldNames?.value || "value";
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ensureSelectLoadState = (renderItem: FormDataItem & { [key: string]: any }) => {
|
|
87
|
+
if (!renderItem.__selectState) {
|
|
88
|
+
renderItem.__selectState = {
|
|
89
|
+
pageNum: 1,
|
|
90
|
+
pageSize: renderItem.pageSize || DEFAULT_SELECT_PAGE_SIZE,
|
|
91
|
+
keyword: "",
|
|
92
|
+
hasMore: true,
|
|
93
|
+
loading: false,
|
|
94
|
+
} as SelectLoadState;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return renderItem.__selectState as SelectLoadState;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const mergeSelectOptionList = (renderItem: FormDataItem & { [key: string]: any }, list: any[]) => {
|
|
101
|
+
const valueKey = getSelectValueField(renderItem);
|
|
102
|
+
const oldList = renderItem.optionList || [];
|
|
103
|
+
const map = new Map<any, any>();
|
|
104
|
+
|
|
105
|
+
for (const item of oldList) {
|
|
106
|
+
map.set(item?.[valueKey], item);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const item of list) {
|
|
110
|
+
map.set(item?.[valueKey], item);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
renderItem.optionList = Array.from(map.values());
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const buildSelectLoadParams = (renderItem: FormDataItem & { [key: string]: any }, state: SelectLoadState) => {
|
|
117
|
+
const pageNumKey = renderItem.pageNumKey || "p";
|
|
118
|
+
const pageSizeKey = renderItem.pageSizeKey || "pc";
|
|
119
|
+
const keywordKey = renderItem.keywordKey || "keyword";
|
|
120
|
+
const params: Record<string, any> = {
|
|
121
|
+
p: state.pageNum,
|
|
122
|
+
pc: state.pageSize,
|
|
123
|
+
[pageNumKey]: state.pageNum,
|
|
124
|
+
[pageSizeKey]: state.pageSize,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const keyword = (state.keyword || "").replaceAll("=", "").trim();
|
|
128
|
+
if (!keyword) {
|
|
129
|
+
return params;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
params[keywordKey] = keyword;
|
|
133
|
+
|
|
134
|
+
return params;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const normalizeSelectLoadResult = (res: SelectLoadResult, state: SelectLoadState) => {
|
|
138
|
+
const list = Array.isArray(res) ? res : res?.data || [];
|
|
139
|
+
|
|
140
|
+
const hasMore =
|
|
141
|
+
Array.isArray(res)
|
|
142
|
+
? list.length >= state.pageSize
|
|
143
|
+
: typeof res?.pages?.total === "number" && typeof res?.pages?.now === "number"
|
|
144
|
+
? res.pages.now < res.pages.total
|
|
145
|
+
: typeof res?.count === "number"
|
|
146
|
+
? state.pageNum * state.pageSize < res.count
|
|
147
|
+
: list.length >= state.pageSize;
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
list,
|
|
151
|
+
hasMore,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const fetchSelectOptions = async (
|
|
156
|
+
renderItem: FormDataItem & { [key: string]: any },
|
|
157
|
+
options: { reset?: boolean; keyword?: string } = {}
|
|
158
|
+
) => {
|
|
159
|
+
if (renderItem.type !== "select" || !renderItem.loadDataFunc) return;
|
|
160
|
+
const state = ensureSelectLoadState(renderItem);
|
|
161
|
+
|
|
162
|
+
if (state.loading) return;
|
|
163
|
+
|
|
164
|
+
if (options.keyword != undefined) {
|
|
165
|
+
state.keyword = options.keyword;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (options.reset) {
|
|
169
|
+
state.pageNum = 1;
|
|
170
|
+
state.hasMore = true;
|
|
171
|
+
renderItem.optionList = [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!state.hasMore) return;
|
|
175
|
+
|
|
176
|
+
state.loading = true;
|
|
177
|
+
try {
|
|
178
|
+
const res = (await renderItem.loadDataFunc(buildSelectLoadParams(renderItem, state))) as SelectLoadResult;
|
|
179
|
+
const normalizedRes = normalizeSelectLoadResult(res, state);
|
|
180
|
+
const list = normalizedRes.list;
|
|
181
|
+
if (options.reset) {
|
|
182
|
+
renderItem.optionList = [];
|
|
183
|
+
}
|
|
184
|
+
mergeSelectOptionList(renderItem, list);
|
|
185
|
+
|
|
186
|
+
state.hasMore = normalizedRes.hasMore;
|
|
187
|
+
if (list.length > 0) {
|
|
188
|
+
state.pageNum += 1;
|
|
189
|
+
}
|
|
190
|
+
} finally {
|
|
191
|
+
state.loading = false;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const selectSearchTimerMap = new WeakMap<any, number>();
|
|
196
|
+
|
|
197
|
+
const onSelectSearch = (keyword: string, renderItem: FormDataItem & { [key: string]: any }) => {
|
|
198
|
+
if (renderItem.type !== "select") return;
|
|
199
|
+
if (!renderItem.loadDataFunc) return;
|
|
200
|
+
|
|
201
|
+
const timer = selectSearchTimerMap.get(renderItem);
|
|
202
|
+
if (timer) {
|
|
203
|
+
clearTimeout(timer);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const nextTimer = window.setTimeout(() => {
|
|
207
|
+
fetchSelectOptions(renderItem, { reset: true, keyword: keyword || "" });
|
|
208
|
+
}, 300);
|
|
209
|
+
selectSearchTimerMap.set(renderItem, nextTimer);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const onSelectPopupScroll = (e: Event, renderItem: FormDataItem & { [key: string]: any }) => {
|
|
213
|
+
if (renderItem.type !== "select") return;
|
|
214
|
+
if (!renderItem.loadDataFunc) return;
|
|
215
|
+
|
|
216
|
+
const target = e.target as HTMLElement;
|
|
217
|
+
if (!target) return;
|
|
218
|
+
|
|
219
|
+
const distanceToBottom = target.scrollHeight - target.scrollTop - target.clientHeight;
|
|
220
|
+
if (distanceToBottom <= 24) {
|
|
221
|
+
fetchSelectOptions(renderItem);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
61
225
|
/**
|
|
62
226
|
* 富文本相关配置
|
|
63
227
|
*/
|
|
@@ -82,12 +246,11 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
|
|
|
82
246
|
let { fieldType, fieldRules, label } = renderItem;
|
|
83
247
|
// 处理校验规则
|
|
84
248
|
const rules: any = [];
|
|
85
|
-
const operateType = ["select", "
|
|
249
|
+
const operateType = ["select", "treeSelect", "date", "datePicker"].includes(renderItem.type) ? "选择" : "输入";
|
|
86
250
|
for (const fieldRule of fieldRules || []) {
|
|
87
|
-
const { msg, required, min, max } = fieldRule;
|
|
251
|
+
const { msg, required, min, max, regex } = fieldRule;
|
|
88
252
|
|
|
89
253
|
// 图片需要额外处理
|
|
90
|
-
|
|
91
254
|
if (
|
|
92
255
|
(renderItem.type == "uploadOss" || renderItem.type == "uploadTos" || renderItem.type == "upload") &&
|
|
93
256
|
renderItem.fieldType == "string"
|
|
@@ -95,12 +258,13 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
|
|
|
95
258
|
fieldType = "array";
|
|
96
259
|
}
|
|
97
260
|
|
|
261
|
+
|
|
98
262
|
if (required) {
|
|
99
263
|
rules.push({
|
|
100
264
|
type: renderItem.type == "uploadOss" || renderItem.type == "uploadTos" ? "array" : renderItem.fieldType,
|
|
101
265
|
required: true,
|
|
102
266
|
message: msg || `请${operateType}${label}`,
|
|
103
|
-
trigger: "blur",
|
|
267
|
+
trigger: ["blur", "change"],
|
|
104
268
|
});
|
|
105
269
|
}
|
|
106
270
|
if (min) {
|
|
@@ -108,7 +272,7 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
|
|
|
108
272
|
type: fieldType,
|
|
109
273
|
min: min,
|
|
110
274
|
message: msg || `${label}需大于等于${min}${fieldType == "string" ? "个字符" : ""}`,
|
|
111
|
-
trigger: "blur",
|
|
275
|
+
trigger: ["blur", "change"],
|
|
112
276
|
});
|
|
113
277
|
}
|
|
114
278
|
|
|
@@ -117,10 +281,21 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
|
|
|
117
281
|
type: fieldType,
|
|
118
282
|
max: max,
|
|
119
283
|
message: `${label}需小于等于${max}${fieldType == "string" ? "个字符" : ""}`,
|
|
120
|
-
trigger: "blur",
|
|
284
|
+
trigger: ["blur", "change"],
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
if (regex) {
|
|
288
|
+
rules.push({
|
|
289
|
+
type: fieldType,
|
|
290
|
+
pattern: regex,
|
|
291
|
+
message: msg || `${label}格式错误`,
|
|
292
|
+
trigger: ["blur", "change"],
|
|
121
293
|
});
|
|
122
294
|
}
|
|
123
295
|
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
124
299
|
renderItem.rules = rules;
|
|
125
300
|
|
|
126
301
|
// 处理下拉选择
|
|
@@ -134,8 +309,12 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
|
|
|
134
309
|
renderItem.placeholder = "加载中" + point.join("");
|
|
135
310
|
}, 500);
|
|
136
311
|
|
|
137
|
-
|
|
138
|
-
|
|
312
|
+
if (renderItem.type == "select") {
|
|
313
|
+
await fetchSelectOptions(renderItem as FormDataItem & { [key: string]: any }, { reset: true });
|
|
314
|
+
} else {
|
|
315
|
+
const res = await renderItem.loadDataFunc();
|
|
316
|
+
renderItem["optionList"] = res as any[];
|
|
317
|
+
}
|
|
139
318
|
clearInterval(interval);
|
|
140
319
|
renderItem.placeholder = `请选择${renderItem.label}`;
|
|
141
320
|
}
|
|
@@ -213,12 +392,21 @@ const initFormState = (renderData: FormDataItem[], initFromData = {}) => {
|
|
|
213
392
|
renderItem.optionList = [];
|
|
214
393
|
}
|
|
215
394
|
|
|
395
|
+
if (renderItem.type == "select" && renderItem.isMultiple) {
|
|
396
|
+
if (!_.isArray(initValue)) {
|
|
397
|
+
initValue = [];
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
216
401
|
if (!initValue && renderItem.optionList) {
|
|
217
|
-
|
|
402
|
+
if (!(renderItem.type == "select" && renderItem.isMultiple)) {
|
|
403
|
+
const valueKey = renderItem.fieldNames?.value || (renderItem.type == "treeSelect" ? "id" : "value");
|
|
404
|
+
initValue = renderItem["optionList"][0]?.[valueKey];
|
|
405
|
+
}
|
|
218
406
|
}
|
|
219
407
|
|
|
220
408
|
if (renderItem.loadDataFunc) {
|
|
221
|
-
initValue = undefined;
|
|
409
|
+
initValue = renderItem.type == "select" && renderItem.isMultiple ? [] : undefined;
|
|
222
410
|
}
|
|
223
411
|
} else if (renderItem.type == "uploadOss") {
|
|
224
412
|
if (!_.isArray(initValue)) {
|
|
@@ -433,47 +621,26 @@ defineExpose({
|
|
|
433
621
|
</script>
|
|
434
622
|
|
|
435
623
|
<template>
|
|
436
|
-
<a-form
|
|
437
|
-
|
|
438
|
-
:model="formState"
|
|
439
|
-
name="basic"
|
|
440
|
-
autocomplete="off"
|
|
441
|
-
:layout="layout"
|
|
442
|
-
:required-mark="requiredMark"
|
|
443
|
-
>
|
|
624
|
+
<a-form ref="formRef" :model="formState" name="basic" autocomplete="off" :layout="layout"
|
|
625
|
+
:required-mark="requiredMark">
|
|
444
626
|
<div v-for="(renderItem, index) in renderData">
|
|
445
|
-
<a-form-item
|
|
446
|
-
:label-col="{ span: renderItem.hideLabel ? 0 : renderItem.labelSpan || labelSpan }"
|
|
627
|
+
<a-form-item :label-col="{ span: renderItem.hideLabel ? 0 : renderItem.labelSpan || labelSpan }"
|
|
447
628
|
:wrapper-col="{ span: renderItem.hideLabel ? undefined : renderItem.valueSpan || valueSpan }"
|
|
448
629
|
v-show="!renderItem.visibleHook || (renderItem.visibleHook && renderItem.visibleHook(formState))"
|
|
449
|
-
:label="renderItem.label && !renderItem.hideLabel ? renderItem.label : ''"
|
|
450
|
-
:
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
?
|
|
455
|
-
|
|
456
|
-
"
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
>
|
|
464
|
-
<div
|
|
465
|
-
:style="{
|
|
466
|
-
pointerEvents: renderItem.disabled ? 'none' : 'auto',
|
|
467
|
-
opacity: renderItem.disabled ? 0.6 : 1,
|
|
468
|
-
cursor: renderItem.disabled ? 'not-allowed' : 'auto',
|
|
469
|
-
}"
|
|
470
|
-
>
|
|
471
|
-
<a-input
|
|
472
|
-
v-model:value="formState[renderItem.key]"
|
|
473
|
-
v-if="renderItem.type == 'input' && [undefined, 'string'].includes(renderItem.fieldType)"
|
|
474
|
-
type="text"
|
|
475
|
-
:placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
|
|
476
|
-
>
|
|
630
|
+
:label="renderItem.label && !renderItem.hideLabel ? renderItem.label : ''" :name="renderItem.key"
|
|
631
|
+
:colon="renderItem.colon == undefined ? true : renderItem.colon" :rules="!renderItem.visibleHook || (renderItem.visibleHook && renderItem.visibleHook(formState))
|
|
632
|
+
? renderItem.rules
|
|
633
|
+
: undefined
|
|
634
|
+
" :style="{
|
|
635
|
+
marginBottom: isSearchForm ? '20px' : '0px',
|
|
636
|
+
}" :tooltip="renderItem.tooltip" :label-align="renderItem.labelAlign || 'right'">
|
|
637
|
+
<div :style="{
|
|
638
|
+
opacity: renderItem.disabled ? 0.6 : 1,
|
|
639
|
+
cursor: renderItem.disabled ? 'not-allowed' : 'auto',
|
|
640
|
+
}">
|
|
641
|
+
<a-input v-model:value="formState[renderItem.key]"
|
|
642
|
+
v-if="renderItem.type == 'input' && [undefined, 'string'].includes(renderItem.fieldType)" type="text"
|
|
643
|
+
:disabled="renderItem.disabled" :placeholder="renderItem.placeholder || `请输入${renderItem.label}`">
|
|
477
644
|
<template v-if="renderItem.prefix" v-slot:prefix>
|
|
478
645
|
<component :is="renderItem.prefix" />
|
|
479
646
|
</template>
|
|
@@ -482,12 +649,9 @@ defineExpose({
|
|
|
482
649
|
</template>
|
|
483
650
|
</a-input>
|
|
484
651
|
|
|
485
|
-
<a-input
|
|
486
|
-
v-
|
|
487
|
-
|
|
488
|
-
type="number"
|
|
489
|
-
:placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
|
|
490
|
-
>
|
|
652
|
+
<a-input v-model:value.number="formState[renderItem.key]"
|
|
653
|
+
v-if="renderItem.type == 'input' && renderItem.fieldType == 'number'" type="number"
|
|
654
|
+
:disabled="renderItem.disabled" :placeholder="renderItem.placeholder || `请输入${renderItem.label}`">
|
|
491
655
|
<template v-if="renderItem.prefix" v-slot:prefix>
|
|
492
656
|
<component :is="renderItem.prefix" />
|
|
493
657
|
</template>
|
|
@@ -496,35 +660,25 @@ defineExpose({
|
|
|
496
660
|
</template>
|
|
497
661
|
</a-input>
|
|
498
662
|
|
|
499
|
-
<a-input-password
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
type="password"
|
|
503
|
-
:placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
|
|
504
|
-
>
|
|
663
|
+
<a-input-password v-model:value="formState[renderItem.key]" v-if="renderItem.type == 'password'"
|
|
664
|
+
type="password" :disabled="renderItem.disabled"
|
|
665
|
+
:placeholder="renderItem.placeholder || `请输入${renderItem.label}`">
|
|
505
666
|
<template v-if="renderItem.prefix" v-slot:prefix>
|
|
506
667
|
<component :is="renderItem.prefix" />
|
|
507
668
|
</template>
|
|
508
669
|
</a-input-password>
|
|
509
670
|
|
|
510
|
-
<a-input
|
|
511
|
-
v-
|
|
512
|
-
|
|
513
|
-
type="text"
|
|
514
|
-
:placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
|
|
515
|
-
>
|
|
671
|
+
<a-input v-model:value="formState[renderItem.key]"
|
|
672
|
+
v-if="renderItem.type == 'code' && [undefined, 'string'].includes(renderItem.fieldType)" type="text"
|
|
673
|
+
:disabled="renderItem.disabled" :placeholder="renderItem.placeholder || `请输入${renderItem.label}`">
|
|
516
674
|
<template v-if="renderItem.prefix" v-slot:prefix>
|
|
517
675
|
<component :is="renderItem.prefix" />
|
|
518
676
|
</template>
|
|
519
677
|
<template v-slot:suffix>
|
|
520
|
-
<span
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
"
|
|
525
|
-
class="send-notice send-notice-activity"
|
|
526
|
-
@click="sendNoticeClick(renderItem)"
|
|
527
|
-
>
|
|
678
|
+
<span v-if="
|
|
679
|
+
(!renderItem.activityFunc || (renderItem.activityFunc && renderItem.activityFunc(formState))) &&
|
|
680
|
+
renderItem.sendStatus != 'sending'
|
|
681
|
+
" class="send-notice send-notice-activity" @click="sendNoticeClick(renderItem)">
|
|
528
682
|
{{ renderItem.codeName }}
|
|
529
683
|
</span>
|
|
530
684
|
<span v-else class="send-notice">
|
|
@@ -533,69 +687,50 @@ defineExpose({
|
|
|
533
687
|
</template>
|
|
534
688
|
</a-input>
|
|
535
689
|
|
|
536
|
-
<a-textarea
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
:
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
<a-radio-group v-if="renderItem.type == 'radio'" v-model:value="formState[renderItem.key]">
|
|
690
|
+
<a-textarea v-model:value="formState[renderItem.key]" v-if="renderItem.type == 'textarea'"
|
|
691
|
+
:disabled="renderItem.disabled" :placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
|
|
692
|
+
:auto-size="{ minRows: 2, maxRows: 10 }" />
|
|
693
|
+
|
|
694
|
+
<a-select style="min-width: 170px; margin-right: 15px" v-model:value="formState[renderItem.key]"
|
|
695
|
+
:placeholder="renderItem.placeholder || `请选择`" v-if="renderItem.type == 'select'"
|
|
696
|
+
:disabled="renderItem.disabled" :fieldNames="renderItem.fieldNames || { label: 'label', value: 'value' }"
|
|
697
|
+
:options="renderItem.optionList" :mode="renderItem.isMultiple ? 'multiple' : undefined"
|
|
698
|
+
:show-search="renderItem.enableSearch || !!renderItem.loadDataFunc"
|
|
699
|
+
:filter-option="renderItem.loadDataFunc ? false : !!renderItem.enableSearch"
|
|
700
|
+
:option-filter-prop="renderItem.fieldNames?.label || 'label'"
|
|
701
|
+
:loading="(renderItem as any).__selectState?.loading"
|
|
702
|
+
@search="(keyword) => onSelectSearch(keyword, renderItem as any)"
|
|
703
|
+
@popupScroll="(e) => onSelectPopupScroll(e, renderItem as any)"></a-select>
|
|
704
|
+
|
|
705
|
+
<a-radio-group v-if="renderItem.type == 'radio'" v-model:value="formState[renderItem.key]"
|
|
706
|
+
:disabled="renderItem.disabled">
|
|
554
707
|
<a-radio v-for="item in renderItem.optionList" :value="item[renderItem.fieldNames?.value || 'value']">
|
|
555
708
|
{{ item[renderItem.fieldNames?.label || "label"] }}
|
|
556
709
|
</a-radio>
|
|
557
710
|
</a-radio-group>
|
|
558
711
|
|
|
559
|
-
<a-date-picker
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
format="YYYY-MM-DD HH:mm:ss"
|
|
566
|
-
value-format="YYYY-MM-DD HH:mm:ss"
|
|
567
|
-
/>
|
|
568
|
-
|
|
569
|
-
<a-tree-select
|
|
570
|
-
style="min-width: 170px; margin-right: 15px"
|
|
571
|
-
v-model:value="formState[renderItem.key]"
|
|
572
|
-
:tree-data="renderItem.optionList"
|
|
573
|
-
v-if="renderItem.type == 'treeSelect'"
|
|
712
|
+
<a-date-picker v-model:value="formState[renderItem.key]" v-if="renderItem.type === 'datePicker'" show-time
|
|
713
|
+
:disabled="renderItem.disabled" :placeholder="`请选择${renderItem.label}`" class="ant-input"
|
|
714
|
+
format="YYYY-MM-DD HH:mm:ss" value-format="YYYY-MM-DD HH:mm:ss" />
|
|
715
|
+
|
|
716
|
+
<a-tree-select style="min-width: 170px; margin-right: 15px" v-model:value="formState[renderItem.key]"
|
|
717
|
+
:tree-data="renderItem.optionList" v-if="renderItem.type == 'treeSelect'" :disabled="renderItem.disabled"
|
|
574
718
|
:fieldNames="renderItem.fieldNames || { children: 'children', label: 'name', value: 'id' }"
|
|
575
|
-
:placeholder="renderItem.placeholder || `请选择`"
|
|
576
|
-
></a-tree-select>
|
|
719
|
+
:placeholder="renderItem.placeholder || `请选择`"></a-tree-select>
|
|
577
720
|
|
|
578
721
|
<a-upload
|
|
579
722
|
v-if="renderItem.type == 'uploadOss' || renderItem.type == 'uploadTos' || renderItem.type == 'upload'"
|
|
580
|
-
:file-list="formState[renderItem.key]"
|
|
581
|
-
:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
uploadChange(e, renderItem);
|
|
592
|
-
}
|
|
593
|
-
"
|
|
594
|
-
:headers="{
|
|
595
|
-
'Cache-Control': 'max-age=15552000',
|
|
596
|
-
}"
|
|
597
|
-
:list-type="renderItem.uploadType == 'pic' ? 'picture-card' : undefined"
|
|
598
|
-
>
|
|
723
|
+
:file-list="formState[renderItem.key]" :multiple="true" :max-count="renderItem?.maxCount || 1"
|
|
724
|
+
@preview="handlePicPreview" :customRequest="(e) => {
|
|
725
|
+
uploadRequest(e, renderItem);
|
|
726
|
+
}
|
|
727
|
+
" @change="
|
|
728
|
+
(e) => {
|
|
729
|
+
uploadChange(e, renderItem);
|
|
730
|
+
}
|
|
731
|
+
" :headers="{
|
|
732
|
+
'Cache-Control': 'max-age=15552000',
|
|
733
|
+
}" :list-type="renderItem.uploadType == 'pic' ? 'picture-card' : undefined">
|
|
599
734
|
<div v-if="renderItem.uploadType == 'pic'">
|
|
600
735
|
<plus-outlined />
|
|
601
736
|
<div style="margin-top: 8px">{{ renderItem.label }}</div>
|
|
@@ -609,18 +744,19 @@ defineExpose({
|
|
|
609
744
|
</div>
|
|
610
745
|
</a-upload>
|
|
611
746
|
|
|
612
|
-
<component v-if="renderItem.type == 'component'" :is="renderItem.component"
|
|
613
|
-
|
|
747
|
+
<component v-if="renderItem.type == 'component'" :is="renderItem.component"
|
|
748
|
+
v-model:[renderItem.key]="formState[renderItem.key]"></component>
|
|
749
|
+
</div>
|
|
614
750
|
</a-form-item>
|
|
615
751
|
</div>
|
|
616
752
|
|
|
617
753
|
<div v-if="uSlots.button || showResetButton || showSubmitButton" style="display: flex; justify-content: center">
|
|
618
754
|
<slot name="button"></slot>
|
|
619
755
|
<a-space size="small">
|
|
620
|
-
<a-button v-if="showResetButton" @click="resetForm" style="padding: 0 30px">
|
|
756
|
+
<a-button v-if="showResetButton" @click="resetForm" @keydown.enter.prevent="resetForm" style="padding: 0 30px">
|
|
621
757
|
{{ resetButtonText || "取消" }}
|
|
622
758
|
</a-button>
|
|
623
|
-
<a-button type="primary" @click="confirmClick" v-if="showSubmitButton" style="padding: 0 30px">
|
|
759
|
+
<a-button type="primary" @click="confirmClick" @keydown.enter.prevent="confirmClick" v-if="showSubmitButton" style="padding: 0 30px">
|
|
624
760
|
{{ submitButtonText || "确认" }}
|
|
625
761
|
</a-button>
|
|
626
762
|
</a-space>
|
|
@@ -60,6 +60,7 @@ const confirmClick = async () => {
|
|
|
60
60
|
<a-button
|
|
61
61
|
class="modal-btn modal-cancel-btn"
|
|
62
62
|
@click="show = false"
|
|
63
|
+
@keydown.enter.prevent="show = false"
|
|
63
64
|
>
|
|
64
65
|
取消
|
|
65
66
|
</a-button>
|
|
@@ -71,6 +72,7 @@ const confirmClick = async () => {
|
|
|
71
72
|
backgroundColor: confrimBtnBgColor,
|
|
72
73
|
}"
|
|
73
74
|
@click="confirmClick"
|
|
75
|
+
@keydown.enter.prevent="confirmClick"
|
|
74
76
|
:loading="loading"
|
|
75
77
|
>
|
|
76
78
|
确定
|
|
@@ -2,10 +2,21 @@
|
|
|
2
2
|
// import "viewerjs/dist/viewer.css";
|
|
3
3
|
import { api as viewerApi } from "v-viewer";
|
|
4
4
|
import PForm from "../p-form/p-form.vue";
|
|
5
|
-
import { h, ref, reactive, onMounted, toRaw } from "vue";
|
|
5
|
+
import { h, ref, reactive, onMounted, toRaw, computed } from "vue";
|
|
6
6
|
import * as _ from "../../utils/dataUtils";
|
|
7
7
|
import dayjs from "dayjs";
|
|
8
|
-
import
|
|
8
|
+
import * as XLSX from "xlsx";
|
|
9
|
+
import {
|
|
10
|
+
Table as ATable,
|
|
11
|
+
Button as AButton,
|
|
12
|
+
Textarea as ATextarea,
|
|
13
|
+
Modal as AModal,
|
|
14
|
+
Radio as ARadio,
|
|
15
|
+
Checkbox as ACheckbox,
|
|
16
|
+
CheckboxGroup as ACheckboxGroup,
|
|
17
|
+
InputNumber as AInputNumber,
|
|
18
|
+
message,
|
|
19
|
+
} from "ant-design-vue";
|
|
9
20
|
import type { FormDataItem } from "../p-form/index.d";
|
|
10
21
|
import type { TableColumn } from "./index.d";
|
|
11
22
|
import { useSlots } from "vue";
|
|
@@ -21,6 +32,8 @@ interface Props {
|
|
|
21
32
|
searchRenderData?: FormDataItem[];
|
|
22
33
|
initSearchFormData?: any;
|
|
23
34
|
data?: any[];
|
|
35
|
+
enableExport?: boolean;
|
|
36
|
+
exportFileName?: string;
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
const {
|
|
@@ -31,6 +44,8 @@ const {
|
|
|
31
44
|
editColumns = [],
|
|
32
45
|
tableColumns = [],
|
|
33
46
|
initSearchFormData = [],
|
|
47
|
+
enableExport = true,
|
|
48
|
+
exportFileName = "table_export",
|
|
34
49
|
} = defineProps<Props>();
|
|
35
50
|
|
|
36
51
|
const selectedIds = defineModel<string[]>("selectedIds", { default: [] });
|
|
@@ -66,6 +81,15 @@ const rowSelection = {
|
|
|
66
81
|
const editableData = reactive({});
|
|
67
82
|
|
|
68
83
|
const loading = ref(false);
|
|
84
|
+
const exportVisible = ref(false);
|
|
85
|
+
const exportLoading = ref(false);
|
|
86
|
+
|
|
87
|
+
const exportForm = reactive({
|
|
88
|
+
mode: "current",
|
|
89
|
+
page: 1,
|
|
90
|
+
count: 100,
|
|
91
|
+
columns: [] as string[],
|
|
92
|
+
});
|
|
69
93
|
|
|
70
94
|
let searchQuery = {};
|
|
71
95
|
|
|
@@ -73,25 +97,6 @@ const renderTableColumns = (tableColumns: TableColumn[]) => {
|
|
|
73
97
|
const returnColumns: any = [];
|
|
74
98
|
for (const [index, renderItem] of tableColumns.entries()) {
|
|
75
99
|
if (renderItem.type == "operate") {
|
|
76
|
-
// renderItem.customRender = ({ text, record }) => {
|
|
77
|
-
// const buttons: any = [];
|
|
78
|
-
// for (const buttonItem of renderItem.operateButtons) {
|
|
79
|
-
// if (buttonItem.visibleFunc && !buttonItem.visibleFunc(record)) continue;
|
|
80
|
-
// buttons.push(
|
|
81
|
-
// h(
|
|
82
|
-
// "a",
|
|
83
|
-
// {
|
|
84
|
-
// onClick: () => {
|
|
85
|
-
// buttonItem.click(record);
|
|
86
|
-
// },
|
|
87
|
-
// },
|
|
88
|
-
// buttonItem.label
|
|
89
|
-
// )
|
|
90
|
-
// );
|
|
91
|
-
// }
|
|
92
|
-
// return h("div", { style: { display: "flex", justifyContent: "space-around" } }, buttons);
|
|
93
|
-
// };
|
|
94
|
-
|
|
95
100
|
if (!renderItem.width) {
|
|
96
101
|
renderItem.width = renderItem.operateButtons.length * 60;
|
|
97
102
|
}
|
|
@@ -175,6 +180,193 @@ const renderTableColumns = (tableColumns: TableColumn[]) => {
|
|
|
175
180
|
|
|
176
181
|
const columns = renderTableColumns(tableColumns);
|
|
177
182
|
|
|
183
|
+
const getAllExportColumns = () => {
|
|
184
|
+
return tableColumns.filter((item) => item.type !== "operate" && !!item.key);
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const getExportColumns = (selectedKeys?: string[]) => {
|
|
188
|
+
const allExportColumns = getAllExportColumns();
|
|
189
|
+
if (selectedKeys === undefined) {
|
|
190
|
+
return allExportColumns;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const selectedSet = new Set((selectedKeys || []).map((key) => String(key)));
|
|
194
|
+
return allExportColumns.filter((item) => selectedSet.has(String(item.key)));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const exportColumnOptions = computed(() => {
|
|
198
|
+
return getAllExportColumns().map((col) => {
|
|
199
|
+
const title = col.title as any;
|
|
200
|
+
return {
|
|
201
|
+
label: typeof title === "string" ? title : col.key || "",
|
|
202
|
+
value: String(col.key),
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const onExportColumnsChange = (values: Array<string | number>) => {
|
|
208
|
+
console.log("selected export columns: ", values);
|
|
209
|
+
exportForm.columns = (values || []).map((item) => String(item));
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const resetExportColumns = () => {
|
|
213
|
+
exportForm.columns = getAllExportColumns().map((item) => String(item.key));
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const getCellValue = (record: any, column: TableColumn) => {
|
|
217
|
+
const key = column.key || "";
|
|
218
|
+
let value = key.includes(".") ? _.get(record, key.split(".")) : _.get(record, key);
|
|
219
|
+
|
|
220
|
+
if (column.mapPath) {
|
|
221
|
+
try {
|
|
222
|
+
const mapInfoStr: any = localStorage.getItem("map");
|
|
223
|
+
const mapInfo = JSON.parse(mapInfoStr || "{}");
|
|
224
|
+
const mapItem = _.get(mapInfo, column.mapPath) || {};
|
|
225
|
+
value = mapItem[value] ?? value;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// ignore map parse error and fallback to raw value
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (column.type === "date" && value) {
|
|
232
|
+
value = dayjs(value).format(column.format || "YYYY-MM-DD HH:mm:ss");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (column.type === "pic") {
|
|
236
|
+
if (_.isEmpty(value)) return "";
|
|
237
|
+
const pics = _.isArray(value) ? value : [value];
|
|
238
|
+
value = pics.join(",");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (_.isEmpty(value) && !_.isEmpty(column.default)) {
|
|
242
|
+
value = column.default;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (_.isArray(value) || (typeof value === "object" && value !== null)) {
|
|
246
|
+
return JSON.stringify(value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return value ?? "";
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const downloadXlsx = (rows: any[], exportColumns: TableColumn[]) => {
|
|
253
|
+
const headers = exportColumns.map((col: any) => {
|
|
254
|
+
const title = col.title as any;
|
|
255
|
+
return typeof title === "string" ? title : col.key || "";
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const dataRows = rows.map((row: any) => {
|
|
259
|
+
return exportColumns.map((col: any) => getCellValue(row, col));
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const worksheet = XLSX.utils.aoa_to_sheet([headers, ...dataRows]);
|
|
263
|
+
const workbook = XLSX.utils.book_new();
|
|
264
|
+
XLSX.utils.book_append_sheet(workbook, worksheet, "Sheet1");
|
|
265
|
+
XLSX.writeFile(workbook, `${exportFileName}_${dayjs().format("YYYYMMDDHHmmss")}.xlsx`);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const openExportModal = () => {
|
|
269
|
+
exportForm.mode = "current";
|
|
270
|
+
exportForm.page = pageQuery.p;
|
|
271
|
+
exportForm.count = pageQuery.pc;
|
|
272
|
+
resetExportColumns();
|
|
273
|
+
exportVisible.value = true;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const toggleExportMode = (mode: string) => {
|
|
277
|
+
exportForm.mode = exportForm.mode === mode ? "" : mode;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const getExportRowsByMode = async () => {
|
|
281
|
+
const sourceRows = data && data.length > 0 ? data : tableData.data || [];
|
|
282
|
+
|
|
283
|
+
if (exportForm.mode === "current") {
|
|
284
|
+
return sourceRows;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (getData) {
|
|
288
|
+
const _searchInitFormData = initSearchFormData ? JSON.parse(JSON.stringify(initSearchFormData)) : {};
|
|
289
|
+
const baseQuery = { ..._searchInitFormData, ..._.cloneDeep(searchQuery), od: pageQuery.od } as any;
|
|
290
|
+
|
|
291
|
+
if (exportForm.mode === "page") {
|
|
292
|
+
const _tableData = await getData({
|
|
293
|
+
...baseQuery,
|
|
294
|
+
p: Math.max(1, Number(exportForm.page) || 1),
|
|
295
|
+
pc: pageQuery.pc,
|
|
296
|
+
});
|
|
297
|
+
return _tableData.data || [];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (exportForm.mode === "count") {
|
|
301
|
+
const _tableData = await getData({
|
|
302
|
+
...baseQuery,
|
|
303
|
+
p: 1,
|
|
304
|
+
pc: Math.max(1, Number(exportForm.count) || 1),
|
|
305
|
+
});
|
|
306
|
+
return _tableData.data || [];
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (exportForm.mode === "all") {
|
|
310
|
+
const _tableData = await getData({
|
|
311
|
+
...baseQuery,
|
|
312
|
+
p: 1,
|
|
313
|
+
pc: Math.max(1, Number(tableData.count) || 1),
|
|
314
|
+
});
|
|
315
|
+
return _tableData.data || [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (exportForm.mode === "page") {
|
|
322
|
+
const page = Math.max(1, Number(exportForm.page) || 1);
|
|
323
|
+
const start = (page - 1) * pageQuery.pc;
|
|
324
|
+
return sourceRows.slice(start, start + pageQuery.pc);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (exportForm.mode === "count") {
|
|
328
|
+
return sourceRows.slice(0, Math.max(1, Number(exportForm.count) || 1));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (exportForm.mode === "all") {
|
|
332
|
+
return sourceRows.slice(0, tableData.count || sourceRows.length);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return sourceRows;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const confirmExport = async () => {
|
|
339
|
+
try {
|
|
340
|
+
if (!exportForm.mode) {
|
|
341
|
+
message.warning("请选择导出方式");
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (_.isEmpty(exportForm.columns)) {
|
|
345
|
+
message.warning("请选择导出列");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
exportLoading.value = true;
|
|
349
|
+
const exportColumns = getExportColumns(exportForm.columns);
|
|
350
|
+
|
|
351
|
+
if (_.isEmpty(exportColumns)) {
|
|
352
|
+
message.warning("请选择导出列");
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const rows = await getExportRowsByMode();
|
|
356
|
+
if (_.isEmpty(rows)) {
|
|
357
|
+
message.warning("暂无可导出的数据");
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
downloadXlsx(rows, exportColumns);
|
|
361
|
+
exportVisible.value = false;
|
|
362
|
+
message.success("导出成功");
|
|
363
|
+
} catch (error: any) {
|
|
364
|
+
message.error(error?.message || "导出失败");
|
|
365
|
+
} finally {
|
|
366
|
+
exportLoading.value = false;
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
178
370
|
const renderTableData = async () => {
|
|
179
371
|
loading.value = true;
|
|
180
372
|
if (getData) {
|
|
@@ -213,8 +405,7 @@ const onPageSizeChange = (current, pc) => {
|
|
|
213
405
|
const searchClick = async () => {
|
|
214
406
|
const query = await SearchFormRef.value.getFormData();
|
|
215
407
|
|
|
216
|
-
|
|
217
|
-
searchQuery = _.pickBy(query, (value, key) => {
|
|
408
|
+
searchQuery = _.pickBy(query || {}, (value, key) => {
|
|
218
409
|
return value !== "" && value != undefined;
|
|
219
410
|
});
|
|
220
411
|
|
|
@@ -283,14 +474,18 @@ defineExpose({
|
|
|
283
474
|
layout="inline"
|
|
284
475
|
>
|
|
285
476
|
<template v-slot:button>
|
|
286
|
-
<a-button type="primary" @click="searchClick" style="margin-left: 20px"> 搜索 </a-button>
|
|
287
|
-
<a-button style="margin-left: 10px" @click="resetSearch"> 重置 </a-button>
|
|
477
|
+
<a-button type="primary" @click="searchClick" @keydown.enter.prevent="searchClick" style="margin-left: 20px"> 搜索 </a-button>
|
|
478
|
+
<a-button style="margin-left: 10px" @click="resetSearch" @keydown.enter.prevent="resetSearch"> 重置 </a-button>
|
|
288
479
|
</template>
|
|
289
480
|
</PForm>
|
|
290
481
|
</div>
|
|
291
482
|
|
|
292
|
-
<div
|
|
483
|
+
<div
|
|
484
|
+
v-if="uSlots.button"
|
|
485
|
+
style="display: flex; justify-content: flex-end; gap: 10px; margin-bottom: 10px; padding: 20px"
|
|
486
|
+
>
|
|
293
487
|
<slot name="button"></slot>
|
|
488
|
+
<a-button v-if="enableExport" type="primary" @click="openExportModal" @keydown.enter.prevent="openExportModal">导出Excel</a-button>
|
|
294
489
|
</div>
|
|
295
490
|
|
|
296
491
|
<a-table
|
|
@@ -337,19 +532,63 @@ defineExpose({
|
|
|
337
532
|
v-if="!item.visibleFunc || (item.visibleFunc && item.visibleFunc(record))"
|
|
338
533
|
class="operate-a"
|
|
339
534
|
@click="item.click(record)"
|
|
535
|
+
@keydown.enter.prevent="item.click(record)"
|
|
536
|
+
tabindex="0"
|
|
537
|
+
role="button"
|
|
340
538
|
>
|
|
341
539
|
{{ item.label }}</a
|
|
342
540
|
>
|
|
343
541
|
</span>
|
|
344
|
-
<a class="operate-a" v-if="editableData[record.index]" @click="save(record.index)">保存</a>
|
|
345
|
-
<a class="operate-a" v-if="editableData[record.index]" @click="cancel(record.index)">退出</a>
|
|
346
|
-
<a v-if="editColumns.length > 0" class="operate-a" @click="edit(record.index)">编辑</a>
|
|
542
|
+
<a class="operate-a" v-if="editableData[record.index]" @click="save(record.index)" @keydown.enter.prevent="save(record.index)" tabindex="0" role="button">保存</a>
|
|
543
|
+
<a class="operate-a" v-if="editableData[record.index]" @click="cancel(record.index)" @keydown.enter.prevent="cancel(record.index)" tabindex="0" role="button">退出</a>
|
|
544
|
+
<a v-if="editColumns.length > 0" class="operate-a" @click="edit(record.index)" @keydown.enter.prevent="edit(record.index)" tabindex="0" role="button">编辑</a>
|
|
347
545
|
</div>
|
|
348
546
|
</template>
|
|
349
547
|
|
|
350
548
|
<slot name="bodyCell" v-bind="{ column, text, record, index }" />
|
|
351
549
|
</template>
|
|
352
550
|
</a-table>
|
|
551
|
+
|
|
552
|
+
<a-modal v-model:open="exportVisible" title="导出Excel" :confirm-loading="exportLoading" @ok="confirmExport">
|
|
553
|
+
<div class="export-panel">
|
|
554
|
+
<div class="export-columns-wrap">
|
|
555
|
+
<div class="export-columns-title">导出列(默认全选)</div>
|
|
556
|
+
<a-checkbox-group
|
|
557
|
+
v-model:value="exportForm.columns"
|
|
558
|
+
class="export-columns-group"
|
|
559
|
+
:options="exportColumnOptions"
|
|
560
|
+
>
|
|
561
|
+
</a-checkbox-group>
|
|
562
|
+
</div>
|
|
563
|
+
|
|
564
|
+
<div class="export-mode-list">
|
|
565
|
+
<label class="export-mode-item" :class="{ selected: exportForm.mode === 'current' }">
|
|
566
|
+
<a-radio :checked="exportForm.mode === 'current'" @click="toggleExportMode('current')"
|
|
567
|
+
>导出当前页数据</a-radio
|
|
568
|
+
>
|
|
569
|
+
</label>
|
|
570
|
+
<label class="export-mode-item" :class="{ selected: exportForm.mode === 'page' }">
|
|
571
|
+
<a-radio :checked="exportForm.mode === 'page'" @click="toggleExportMode('page')">导出指定页码数据</a-radio>
|
|
572
|
+
</label>
|
|
573
|
+
<label class="export-mode-item" :class="{ selected: exportForm.mode === 'count' }">
|
|
574
|
+
<a-radio :checked="exportForm.mode === 'count'" @click="toggleExportMode('count')">导出指定条数数据</a-radio>
|
|
575
|
+
</label>
|
|
576
|
+
<label class="export-mode-item" :class="{ selected: exportForm.mode === 'all' }">
|
|
577
|
+
<a-radio :checked="exportForm.mode === 'all'" @click="toggleExportMode('all')">导出全部数据(count)</a-radio>
|
|
578
|
+
</label>
|
|
579
|
+
</div>
|
|
580
|
+
|
|
581
|
+
<div v-if="exportForm.mode === 'page'" class="export-input-row">
|
|
582
|
+
<span class="export-input-label">页码:</span>
|
|
583
|
+
<a-input-number v-model:value="exportForm.page" :min="1" :precision="0" style="width: 180px" />
|
|
584
|
+
</div>
|
|
585
|
+
|
|
586
|
+
<div v-if="exportForm.mode === 'count'" class="export-input-row">
|
|
587
|
+
<span class="export-input-label">条数:</span>
|
|
588
|
+
<a-input-number v-model:value="exportForm.count" :min="1" :precision="0" style="width: 180px" />
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
</a-modal>
|
|
353
592
|
</template>
|
|
354
593
|
|
|
355
594
|
<style>
|
|
@@ -376,4 +615,76 @@ defineExpose({
|
|
|
376
615
|
.operate-a {
|
|
377
616
|
color: #126cff;
|
|
378
617
|
}
|
|
618
|
+
|
|
619
|
+
.export-panel {
|
|
620
|
+
display: flex;
|
|
621
|
+
flex-direction: column;
|
|
622
|
+
gap: 12px;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.export-columns-wrap {
|
|
626
|
+
padding: 10px 12px;
|
|
627
|
+
border: 1px solid #e6eaf2;
|
|
628
|
+
border-radius: 8px;
|
|
629
|
+
background: #f8fafc;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.export-columns-title {
|
|
633
|
+
margin-bottom: 8px;
|
|
634
|
+
color: #334155;
|
|
635
|
+
font-weight: 500;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.export-columns-group {
|
|
639
|
+
display: grid;
|
|
640
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
641
|
+
gap: 8px;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
.export-column-item {
|
|
645
|
+
margin-inline-start: 0 !important;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
.export-mode-list {
|
|
649
|
+
display: flex;
|
|
650
|
+
flex-direction: column;
|
|
651
|
+
gap: 8px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.export-mode-item {
|
|
655
|
+
display: flex;
|
|
656
|
+
align-items: center;
|
|
657
|
+
min-height: 36px;
|
|
658
|
+
padding: 0 12px;
|
|
659
|
+
border: 1px solid #e6eaf2;
|
|
660
|
+
border-radius: 8px;
|
|
661
|
+
background: #f8fafc;
|
|
662
|
+
transition: all 0.2s ease;
|
|
663
|
+
cursor: pointer;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.export-mode-item:hover {
|
|
667
|
+
border-color: #8cb8ff;
|
|
668
|
+
background: #eef5ff;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.export-mode-item.selected {
|
|
672
|
+
border-color: #3b82f6;
|
|
673
|
+
background: #e8f1ff;
|
|
674
|
+
box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.15);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
.export-input-row {
|
|
678
|
+
display: flex;
|
|
679
|
+
align-items: center;
|
|
680
|
+
gap: 8px;
|
|
681
|
+
padding: 10px 12px;
|
|
682
|
+
border-radius: 8px;
|
|
683
|
+
background: #f7f9fc;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.export-input-label {
|
|
687
|
+
color: #334155;
|
|
688
|
+
font-weight: 500;
|
|
689
|
+
}
|
|
379
690
|
</style>
|
package/dist/utils/dataUtils.ts
CHANGED
|
@@ -35,33 +35,77 @@ export const isEmpty = (value: any): boolean => {
|
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
export const cloneDeep = <T>(value: T, weakMap = new WeakMap()): T => {
|
|
38
|
-
|
|
38
|
+
// 1. 基础类型直接返回
|
|
39
|
+
if (value === null || typeof value !== "object") return value;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
// 2. 处理循环引用
|
|
42
|
+
if (weakMap.has(value as any)) {
|
|
43
|
+
return weakMap.get(value as any);
|
|
44
|
+
}
|
|
41
45
|
|
|
42
46
|
let result: any;
|
|
43
47
|
|
|
48
|
+
// 3. RegExp
|
|
49
|
+
if (value instanceof RegExp) {
|
|
50
|
+
result = new RegExp(value.source, value.flags);
|
|
51
|
+
weakMap.set(value, result);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 4. Date
|
|
56
|
+
if (value instanceof Date) {
|
|
57
|
+
result = new Date(value.getTime());
|
|
58
|
+
weakMap.set(value, result);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 5. Map
|
|
63
|
+
if (value instanceof Map) {
|
|
64
|
+
result = new Map();
|
|
65
|
+
weakMap.set(value, result);
|
|
66
|
+
|
|
67
|
+
value.forEach((v, k) => {
|
|
68
|
+
result.set(k, cloneDeep(v, weakMap));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 6. Set
|
|
75
|
+
if (value instanceof Set) {
|
|
76
|
+
result = new Set();
|
|
77
|
+
weakMap.set(value, result);
|
|
78
|
+
|
|
79
|
+
value.forEach((v) => {
|
|
80
|
+
result.add(cloneDeep(v, weakMap));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 7. Array
|
|
44
87
|
if (Array.isArray(value)) {
|
|
45
88
|
result = [];
|
|
46
89
|
weakMap.set(value, result);
|
|
90
|
+
|
|
47
91
|
for (const item of value) {
|
|
48
92
|
result.push(cloneDeep(item, weakMap));
|
|
49
93
|
}
|
|
94
|
+
|
|
50
95
|
return result;
|
|
51
96
|
}
|
|
52
97
|
|
|
53
|
-
|
|
98
|
+
// 8. 普通对象
|
|
99
|
+
result = Object.create(Object.getPrototypeOf(value));
|
|
54
100
|
weakMap.set(value, result);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
101
|
+
|
|
102
|
+
for (const key of Object.keys(value as any)) {
|
|
103
|
+
result[key] = cloneDeep((value as any)[key], weakMap);
|
|
59
104
|
}
|
|
60
105
|
|
|
61
106
|
return result;
|
|
62
107
|
};
|
|
63
108
|
|
|
64
|
-
|
|
65
109
|
export const isArray = (value: any) => {
|
|
66
110
|
return Array.isArray(value);
|
|
67
111
|
};
|
|
@@ -134,21 +178,3 @@ const paramsToString = (params) => {
|
|
|
134
178
|
return url
|
|
135
179
|
}
|
|
136
180
|
|
|
137
|
-
|
|
138
|
-
export const navTo = (url, params = {}) => {
|
|
139
|
-
uni.navigateTo({ url: url + paramsToString(params) })
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
export const dirTo = (url, params = {}) => {
|
|
144
|
-
uni.redirectTo({ url: url + paramsToString(params) })
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export const switchTo = (url) => {
|
|
148
|
-
uni.switchTab({ url })
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export const backTo = (delta = 1) => {
|
|
152
|
-
uni.navigateBack({ delta })
|
|
153
|
-
}
|
|
154
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "p-pc-ui",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"module": "dist/index.ts",
|
|
6
6
|
"main": "dist/index.ts",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"license": "ISC",
|
|
19
19
|
"description": "",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"vue": "3.5.13"
|
|
21
|
+
"vue": "3.5.13",
|
|
22
|
+
"xlsx": "^0.18.5"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"ts-node": "^10.9.2",
|