p-pc-ui 1.3.13 → 1.3.14

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.
@@ -0,0 +1,167 @@
1
+ import type { FormDataItem } from "./index";
2
+ import * as _ from "../../utils/dataUtils";
3
+
4
+ export type FormRenderItem = FormDataItem & {
5
+ rawKey: string;
6
+ rules: any[];
7
+ optionList?: any[];
8
+ };
9
+
10
+ export const toFormKey = (key: string) => key.replaceAll(".", "__");
11
+
12
+ export const toRawKey = (key: string) => key.replaceAll("__", ".");
13
+
14
+ export const createFormRules = (renderItem: FormDataItem) => {
15
+ let { fieldType, fieldRules, label } = renderItem;
16
+ const rules: any[] = [];
17
+ const operateType = ["select", "treeSelect", "date", "datePicker"].includes(renderItem.type) ? "选择" : "输入";
18
+
19
+ for (const fieldRule of fieldRules || []) {
20
+ const { msg, required, min, max, regex } = fieldRule;
21
+
22
+ if (
23
+ (renderItem.type === "uploadOss" || renderItem.type === "uploadTos" || renderItem.type === "upload") &&
24
+ renderItem.fieldType === "string"
25
+ ) {
26
+ fieldType = "array";
27
+ }
28
+
29
+ if (required) {
30
+ rules.push({
31
+ type: renderItem.type === "uploadOss" || renderItem.type === "uploadTos" ? "array" : renderItem.fieldType,
32
+ required: true,
33
+ message: msg || `请${operateType}${label}`,
34
+ trigger: ["blur", "change"],
35
+ });
36
+ }
37
+
38
+ if (min) {
39
+ rules.push({
40
+ type: fieldType,
41
+ min,
42
+ message: msg || `${label}需大于等于${min}${fieldType === "string" ? "个字符" : ""}`,
43
+ trigger: ["blur", "change"],
44
+ });
45
+ }
46
+
47
+ if (max) {
48
+ rules.push({
49
+ type: fieldType,
50
+ max,
51
+ message: `${label}需小于等于${max}${fieldType === "string" ? "个字符" : ""}`,
52
+ trigger: ["blur", "change"],
53
+ });
54
+ }
55
+
56
+ if (regex) {
57
+ rules.push({
58
+ type: fieldType,
59
+ pattern: regex,
60
+ message: msg || `${label}格式错误`,
61
+ trigger: ["blur", "change"],
62
+ });
63
+ }
64
+ }
65
+
66
+ return rules;
67
+ };
68
+
69
+ export const getMapOptionList = (mapInfo: any, mapPath?: string[]) => {
70
+ if (!mapPath) return undefined;
71
+ const mapItem = _.get(mapInfo || {}, mapPath);
72
+ if (!mapItem) return undefined;
73
+
74
+ return Object.keys(mapItem).map((key) => ({
75
+ label: mapItem[key],
76
+ value: key,
77
+ }));
78
+ };
79
+
80
+ export const normalizeFormRenderData = (renderData: FormDataItem[], mapInfo: any = {}) => {
81
+ return renderData.map((item) => {
82
+ const rawKey = item.key;
83
+ const optionList = "optionList" in item && Array.isArray(item.optionList) ? [...item.optionList] : undefined;
84
+ const mapOptionList =
85
+ item.type === "select" || item.type === "treeSelect" || item.type === "radio"
86
+ ? getMapOptionList(mapInfo, item.mapPath)
87
+ : undefined;
88
+
89
+ return {
90
+ ...item,
91
+ rawKey,
92
+ key: toFormKey(rawKey),
93
+ rules: createFormRules(item),
94
+ ...(optionList || mapOptionList ? { optionList: mapOptionList || optionList || [] } : {}),
95
+ } as FormRenderItem;
96
+ });
97
+ };
98
+
99
+ export const getInitialFormValue = (renderItem: FormRenderItem, initFormData: Record<string, any> = {}) => {
100
+ if (renderItem.default !== undefined) {
101
+ return renderItem.default;
102
+ }
103
+
104
+ let initValue = _.get(initFormData, renderItem.rawKey);
105
+
106
+ if (initValue === undefined) {
107
+ switch (renderItem.fieldType) {
108
+ case "array":
109
+ initValue = [];
110
+ break;
111
+ case "string":
112
+ initValue =
113
+ renderItem.type === "uploadOss" || renderItem.type === "uploadTos" || renderItem.type === "upload" ? [] : "";
114
+ break;
115
+ case "number":
116
+ initValue = 0;
117
+ break;
118
+ case "date":
119
+ initValue = "";
120
+ break;
121
+ default:
122
+ initValue = undefined;
123
+ break;
124
+ }
125
+ }
126
+
127
+ if (renderItem.type === "select" || renderItem.type === "treeSelect" || renderItem.type === "radio") {
128
+ if (!Array.isArray(renderItem.optionList)) {
129
+ renderItem.optionList = [];
130
+ }
131
+
132
+ if (renderItem.type === "select" && renderItem.isMultiple && !Array.isArray(initValue)) {
133
+ initValue = [];
134
+ }
135
+
136
+ if ((initValue === undefined || initValue === "") && renderItem.optionList.length > 0) {
137
+ if (!(renderItem.type === "select" && renderItem.isMultiple)) {
138
+ const valueKey = renderItem.fieldNames?.value || (renderItem.type === "treeSelect" ? "id" : "value");
139
+ initValue = renderItem.optionList[0]?.[valueKey];
140
+ }
141
+ }
142
+ } else if (renderItem.type === "uploadOss") {
143
+ const values = Array.isArray(initValue) ? initValue : initValue ? [initValue] : [];
144
+ initValue = values.map((val) => ({
145
+ key: val,
146
+ url: renderItem.baseOssUrl + val,
147
+ }));
148
+ } else if (renderItem.type === "uploadTos") {
149
+ const values = Array.isArray(initValue) ? initValue : initValue ? [initValue] : [];
150
+ initValue = values.map((val) => ({
151
+ key: val,
152
+ url: renderItem.baseTosUrl + val,
153
+ }));
154
+ }
155
+
156
+ return initValue;
157
+ };
158
+
159
+ export const initFormStateFromRenderData = (renderData: FormRenderItem[], initFormData: Record<string, any> = {}) => {
160
+ const formState: Record<string, any> = {};
161
+
162
+ for (const renderItem of renderData) {
163
+ formState[renderItem.key] = getInitialFormValue(renderItem, initFormData);
164
+ }
165
+
166
+ return formState;
167
+ };
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
- import { ref, reactive, onMounted, render, onBeforeMount, useSlots, toRaw, shallowRef, isRef, isReactive } from "vue";
2
+ import { ref, reactive, onBeforeMount, useSlots, toRaw, isRef, isReactive } from "vue";
3
3
  import type { FormDataItem } from "./index";
4
+ import { initFormStateFromRenderData, normalizeFormRenderData, type FormRenderItem } from "./form-utils";
4
5
  import {
5
6
  Button as AButton,
6
7
  message,
@@ -50,7 +51,7 @@ const props = withDefaults(defineProps<Props>(), {
50
51
  });
51
52
 
52
53
  // 渲染后的表单
53
- const renderData = ref<(FormDataItem & { rules: [] })[]>([]);
54
+ const renderData = ref<FormRenderItem[]>([]);
54
55
 
55
56
  // 表单数据
56
57
  const formState: any = reactive({});
@@ -78,6 +79,7 @@ type SelectLoadState = {
78
79
  keyword: string;
79
80
  hasMore: boolean;
80
81
  loading: boolean;
82
+ requestId: number;
81
83
  };
82
84
 
83
85
  const getSelectValueField = (renderItem: any) => {
@@ -92,6 +94,7 @@ const ensureSelectLoadState = (renderItem: FormDataItem & { [key: string]: any }
92
94
  keyword: "",
93
95
  hasMore: true,
94
96
  loading: false,
97
+ requestId: 0,
95
98
  } as SelectLoadState;
96
99
  }
97
100
 
@@ -174,8 +177,11 @@ const fetchSelectOptions = async (
174
177
  if (!state.hasMore) return;
175
178
 
176
179
  state.loading = true;
180
+ const requestId = state.requestId + 1;
181
+ state.requestId = requestId;
177
182
  try {
178
183
  const res = (await renderItem.loadDataFunc(buildSelectLoadParams(renderItem, state))) as SelectLoadResult;
184
+ if (requestId !== state.requestId) return;
179
185
  const normalizedRes = normalizeSelectLoadResult(res, state);
180
186
  const list = normalizedRes.list;
181
187
  if (options.reset) {
@@ -188,7 +194,9 @@ const fetchSelectOptions = async (
188
194
  state.pageNum += 1;
189
195
  }
190
196
  } finally {
191
- state.loading = false;
197
+ if (requestId === state.requestId) {
198
+ state.loading = false;
199
+ }
192
200
  }
193
201
  };
194
202
 
@@ -237,103 +245,44 @@ onBeforeMount(async () => {
237
245
  * 处理渲染表单
238
246
  */
239
247
  const disposeRenderData = async (renderData: FormDataItem[]) => {
240
- const _renderData = _.cloneDeep(renderData).map((val) => ({
241
- ...val,
242
- rules: [] as any,
243
- })) as (FormDataItem & { rules: [] })[];
244
-
245
- for (const renderItem of _renderData) {
246
- let { fieldType, fieldRules, label } = renderItem;
247
- // 处理校验规则
248
- const rules: any = [];
249
- const operateType = ["select", "treeSelect", "date", "datePicker"].includes(renderItem.type) ? "选择" : "输入";
250
- for (const fieldRule of fieldRules || []) {
251
- const { msg, required, min, max, regex } = fieldRule;
252
-
253
- // 图片需要额外处理
254
- if (
255
- (renderItem.type == "uploadOss" || renderItem.type == "uploadTos" || renderItem.type == "upload") &&
256
- renderItem.fieldType == "string"
257
- ) {
258
- fieldType = "array";
259
- }
248
+ let mapInfo = {};
249
+ try {
250
+ mapInfo = JSON.parse(localStorage.getItem("map") || "{}");
251
+ } catch (error) {
252
+ mapInfo = {};
253
+ }
260
254
 
261
- if (required) {
262
- rules.push({
263
- type: renderItem.type == "uploadOss" || renderItem.type == "uploadTos" ? "array" : renderItem.fieldType,
264
- required: true,
265
- message: msg || `请${operateType}${label}`,
266
- trigger: ["blur", "change"],
267
- });
268
- }
269
- if (min) {
270
- rules.push({
271
- type: fieldType,
272
- min: min,
273
- message: msg || `${label}需大于等于${min}${fieldType == "string" ? "个字符" : ""}`,
274
- trigger: ["blur", "change"],
275
- });
276
- }
255
+ const _renderData = normalizeFormRenderData(renderData, mapInfo);
277
256
 
278
- if (max) {
279
- rules.push({
280
- type: fieldType,
281
- max: max,
282
- message: `${label}需小于等于${max}${fieldType == "string" ? "个字符" : ""}`,
283
- trigger: ["blur", "change"],
284
- });
285
- }
286
- if (regex) {
287
- rules.push({
288
- type: fieldType,
289
- pattern: regex,
290
- message: msg || `${label}格式错误`,
291
- trigger: ["blur", "change"],
292
- });
257
+ await Promise.all(
258
+ _renderData.map(async (renderItem) => {
259
+ if (
260
+ (renderItem.type !== "treeSelect" && renderItem.type !== "select" && renderItem.type !== "radio") ||
261
+ !renderItem.loadDataFunc
262
+ ) {
263
+ return;
293
264
  }
294
- }
295
265
 
296
- renderItem.rules = rules;
266
+ let point: string[] = [];
267
+ const interval = window.setInterval(() => {
268
+ if (point.length >= 3) point = [];
269
+ point.push(".");
270
+ renderItem.placeholder = "加载中" + point.join("");
271
+ }, 500);
297
272
 
298
- // 处理下拉选择
299
- if (renderItem.type == "treeSelect" || renderItem.type == "select" || renderItem.type == "radio") {
300
- // 立即执行函数, 获取下拉的列表
301
- if (renderItem.loadDataFunc) {
302
- let point: string[] = [];
303
- const interval = setInterval(() => {
304
- if (point.length >= 3) point = [];
305
- point.push(".");
306
- renderItem.placeholder = "加载中" + point.join("");
307
- }, 500);
308
-
309
- if (renderItem.type == "select") {
273
+ try {
274
+ if (renderItem.type === "select") {
310
275
  await fetchSelectOptions(renderItem as FormDataItem & { [key: string]: any }, { reset: true });
311
276
  } else {
312
277
  const res = await renderItem.loadDataFunc();
313
- renderItem["optionList"] = res as any[];
278
+ renderItem.optionList = Array.isArray(res) ? res : res?.data || [];
314
279
  }
280
+ } finally {
315
281
  clearInterval(interval);
316
282
  renderItem.placeholder = `请选择${renderItem.label}`;
317
283
  }
318
-
319
- // 渲染搜索框下拉框
320
- if (renderItem.mapPath) {
321
- const mapInfoStr = localStorage.getItem("map");
322
- if (mapInfoStr) {
323
- const mapInfo = JSON.parse(mapInfoStr);
324
- const mapItem = _.get(mapInfo, renderItem.mapPath);
325
- const optionList: any[] = [];
326
- for (const key in mapItem) {
327
- optionList.push({
328
- label: mapItem[key],
329
- value: key,
330
- });
331
- }
332
- renderItem["optionList"] = optionList;
333
- }
334
- }
335
- }
336
- }
284
+ }),
285
+ );
337
286
 
338
287
  return _renderData;
339
288
  };
@@ -341,110 +290,14 @@ const disposeRenderData = async (renderData: FormDataItem[]) => {
341
290
  /**
342
291
  * 初始化表单数据
343
292
  */
344
- const initFormState = (renderData: FormDataItem[], initFromData = {}) => {
345
- const _formState: any = {};
346
- for (const renderItem of renderData) {
347
- // 处理json字段 例如 data.name = data:{name:xxx}
348
- renderItem["key"] = renderItem["key"].replaceAll(".", "__");
349
- let initValue;
350
- // 判断渲染表单是否有默认值
351
- if (renderItem.default != undefined) {
352
- initValue = renderItem.default;
353
- _formState[renderItem.key] = initValue;
354
- continue;
355
- }
356
-
357
- initValue = _.get(initFromData, renderItem["key"].split("__").join("."));
358
-
359
- if (initValue == undefined) {
360
- switch (renderItem.fieldType) {
361
- case "array":
362
- initValue = [];
363
- break;
364
- case "string":
365
- // 如果是图片类型, 并且是string类型,代表用户只需要1张图片,并且已文本的形式存放
366
- if (renderItem.type == "uploadOss" || renderItem.type == "uploadTos" || renderItem.type == "upload") {
367
- initValue = [];
368
- } else {
369
- initValue = "";
370
- }
371
- break;
372
- case "number":
373
- initValue = 0;
374
- break;
375
- case "date":
376
- initValue: "";
377
- break;
378
- default:
379
- initValue = undefined;
380
- break;
381
- }
382
- }
383
-
384
- // 判断是否是下拉组件 并且设置默认值
385
- const renderItemType = renderItem.type;
386
- if (renderItemType == "select" || renderItemType == "treeSelect" || renderItemType == "radio") {
387
- // 初始化值避免为空导致后续异步操作push失败
388
- if (_.isEmpty(renderItem.optionList)) {
389
- renderItem.optionList = [];
390
- }
391
-
392
- if (renderItem.type == "select" && renderItem.isMultiple) {
393
- if (!_.isArray(initValue)) {
394
- initValue = [];
395
- }
396
- }
397
-
398
- if (!initValue && renderItem.optionList) {
399
- if (!(renderItem.type == "select" && renderItem.isMultiple)) {
400
- const valueKey = renderItem.fieldNames?.value || (renderItem.type == "treeSelect" ? "id" : "value");
401
- initValue = renderItem["optionList"][0]?.[valueKey];
402
- }
403
- }
404
-
405
- if (renderItem.loadDataFunc) {
406
- initValue = renderItem.type == "select" && renderItem.isMultiple ? [] : undefined;
407
- }
408
- } else if (renderItem.type == "uploadOss") {
409
- if (!_.isArray(initValue)) {
410
- initValue = [initValue];
411
- }
412
-
413
- initValue = initValue.map((val) => {
414
- return {
415
- key: val,
416
- url: renderItem.baseOssUrl + val,
417
- };
418
- });
419
- } else if (renderItem.type == "uploadTos") {
420
- if (!_.isArray(initValue)) {
421
- initValue = [initValue];
422
- }
423
- initValue = initValue.map((val) => {
424
- return {
425
- key: val,
426
- url: renderItem.baseTosUrl + val,
427
- };
428
- });
429
- }
430
-
431
- _formState[renderItem["key"]] = initValue;
432
- }
433
-
434
- return _formState;
435
- };
293
+ const initFormState = (renderData: FormRenderItem[], initFromData = {}) => initFormStateFromRenderData(renderData, initFromData);
436
294
 
437
295
  const handlePicPreview = (file: any) => {
438
- const url = URL.createObjectURL(file.originFileObj);
439
-
440
- viewerApi({ images: [url || file.url || file.key || file.thumbUrl] });
441
- };
296
+ const url = file.url || file.key || file.thumbUrl || (file.originFileObj ? URL.createObjectURL(file.originFileObj) : "");
442
297
 
443
- const replaceUrl = (str, urlToReplace, replacement) => {
444
- // 转义特殊字符
445
- let escapedUrl = urlToReplace.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
446
- let pattern = new RegExp(escapedUrl, "g");
447
- return str.replace(pattern, replacement);
298
+ if (url) {
299
+ viewerApi({ images: [url] });
300
+ }
448
301
  };
449
302
 
450
303
  /**
@@ -464,6 +317,7 @@ const getFormData = async (options: { no_check?: Boolean } = {}) => {
464
317
  const returnFormData = {};
465
318
  for (const renderItem of renderData.value) {
466
319
  const { key } = renderItem;
320
+ const rawKey = renderItem.rawKey || key;
467
321
 
468
322
  // 如果控制显隐函数,判断值是否有效
469
323
  if (renderItem?.visibleHook && !renderItem.visibleHook(formData)) {
@@ -475,25 +329,33 @@ const getFormData = async (options: { no_check?: Boolean } = {}) => {
475
329
  const keys = fileData.map((val) => {
476
330
  return val?.response?.key || val.key;
477
331
  });
478
- returnFormData[key] = renderItem?.fieldType == "string" ? keys[0] : keys;
332
+ if (rawKey.includes(".")) {
333
+ _.set(returnFormData, rawKey, renderItem?.fieldType == "string" ? keys[0] : keys);
334
+ } else {
335
+ returnFormData[rawKey] = renderItem?.fieldType == "string" ? keys[0] : keys;
336
+ }
479
337
  }
480
338
  // 获取file对象
481
339
  else if (renderItem.type == "upload") {
482
340
  const formatData = formState[key].map((val) => ({ base64: val.thumbUrl, file: val.originFileObj }));
483
- returnFormData[key] = formatData;
341
+ if (rawKey.includes(".")) {
342
+ _.set(returnFormData, rawKey, formatData);
343
+ } else {
344
+ returnFormData[rawKey] = formatData;
345
+ }
484
346
  }
485
347
 
486
348
  // 转换成上传对象
487
- else if (key.includes("__")) {
488
- _.set(returnFormData, key.split("__"), formData[key]);
349
+ else if (rawKey.includes(".")) {
350
+ _.set(returnFormData, rawKey, formData[key]);
489
351
  } else if (renderItem.type == "datePicker") {
490
- returnFormData[key] = dayjs(formData[key]).format("YYYY-MM-DD HH:mm:ss");
352
+ returnFormData[rawKey] = formData[key] ? dayjs(formData[key]).format("YYYY-MM-DD HH:mm:ss") : "";
491
353
  }
492
354
  else if ( isRef(formData[key]) || isReactive(formData[key]) ) {
493
355
 
494
- returnFormData[key] = toRaw(formData[key]);
356
+ returnFormData[rawKey] = toRaw(formData[key]);
495
357
  } else {
496
- returnFormData[key] = formData[key];
358
+ returnFormData[rawKey] = formData[key];
497
359
  }
498
360
  }
499
361
 
@@ -676,7 +538,7 @@ defineExpose({
676
538
 
677
539
  <a-input
678
540
  v-model:value.number="formState[renderItem.key]"
679
- v-if="renderItem.type == 'input' && renderItem.fieldType == 'number'"
541
+ v-if="(renderItem.type == 'input' && renderItem.fieldType == 'number') || renderItem.type == 'number'"
680
542
  type="number"
681
543
  :disabled="renderItem.disabled"
682
544
  :placeholder="renderItem.placeholder || `请输入${renderItem.label}`"
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <Modal v-model:visible="visible" :title="title" :width="modalWidth" okText="确定" cancel-text="取消" @ok="ok" centered
2
+ <Modal v-model:open="visible" :title="title" :width="modalWidth" okText="确定" cancel-text="取消" @ok="ok" centered
3
3
  @cancel="cancel">
4
4
  <div style="max-height: 600px; overflow-y: auto; padding-right: 80px;margin-top: 30px;">
5
5
  <p-form v-if="formRenderData.length > 0" ref="PFormRef" :render-data="formRenderData"
@@ -51,16 +51,16 @@ onMounted(() => {
51
51
  initFormValue = {};
52
52
  }
53
53
 
54
+ const renderDataMap = new Map(_.cloneDeep(renderFormData).map((val) => [val.key, val]));
54
55
  const filterRenderData: any = [];
55
56
  for (const renderKey of renderKeys || renderFormData.map((val) => val.key)) {
56
- const renderItem = _.cloneDeep(renderFormData).find((val) => val.key == renderKey);
57
+ const renderItem = renderDataMap.get(renderKey);
57
58
  if (!_.isEmpty(renderItem)) {
58
59
  filterRenderData.push(renderItem);
59
60
  }
60
61
  }
61
62
 
62
63
  formRenderData.value = filterRenderData;
63
- console.log(formRenderData)
64
64
  })
65
65
 
66
66
 
@@ -81,4 +81,4 @@ const cancel = () => {
81
81
 
82
82
  </script>
83
83
 
84
- <style scoped></style>
84
+ <style scoped></style>
@@ -2,7 +2,7 @@
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, computed } from "vue";
5
+ import { ref, reactive, onMounted, toRaw, computed } from "vue";
6
6
  import * as _ from "../../utils/dataUtils";
7
7
  import dayjs from "dayjs";
8
8
  import * as XLSX from "xlsx";
@@ -17,8 +17,10 @@ import {
17
17
  InputNumber as AInputNumber,
18
18
  message,
19
19
  } from "ant-design-vue";
20
+ import type { TableProps } from "ant-design-vue";
20
21
  import type { FormDataItem } from "../p-form/index.d";
21
22
  import type { TableColumn } from "./index.d";
23
+ import { buildTableColumns, getCellValue as getTableCellValue } from "./table-utils";
22
24
  import { useSlots } from "vue";
23
25
  const SearchFormRef = ref();
24
26
 
@@ -48,7 +50,9 @@ const {
48
50
  exportFileName = "table_export",
49
51
  } = defineProps<Props>();
50
52
 
51
- const selectedIds = defineModel<string[]>("selectedIds", { default: [] });
53
+ type RowKey = string | number;
54
+
55
+ const selectedIds = defineModel<RowKey[]>("selectedIds", { default: [] });
52
56
 
53
57
  const pageQuery = reactive({
54
58
  p: 1,
@@ -65,13 +69,15 @@ const tableData = reactive({
65
69
  },
66
70
  });
67
71
 
68
- const emits = defineEmits(["select"]);
72
+ const emits = defineEmits<{
73
+ select: [selectedRowKeys: RowKey[]];
74
+ }>();
69
75
 
70
- const rowSelection = {
76
+ const rowSelection: TableProps<any>["rowSelection"] = {
71
77
  get selectedRowKeys() {
72
78
  return selectedIds.value;
73
79
  },
74
- onChange: (selectedRowKeys: string[], _selectedRows: any[]) => {
80
+ onChange: (selectedRowKeys) => {
75
81
  selectedIds.value = selectedRowKeys;
76
82
  emits("select", selectedRowKeys);
77
83
  },
@@ -92,93 +98,23 @@ const exportForm = reactive({
92
98
  });
93
99
 
94
100
  let searchQuery = {};
101
+ let tableRequestId = 0;
95
102
 
96
- const renderTableColumns = (tableColumns: TableColumn[]) => {
97
- const returnColumns: any = [];
98
- for (const [index, renderItem] of tableColumns.entries()) {
99
- if (renderItem.type == "operate") {
100
- if (!renderItem.width) {
101
- renderItem.width = renderItem.operateButtons.length * 60;
102
- }
103
-
104
- if (editColumns.length > 0) {
105
- renderItem.width += 110;
106
- }
107
- renderItem.align = "center";
108
- }
109
-
110
- // 处理默认字段
111
- if (!_.isEmpty(renderItem.default)) {
112
- renderItem.customRender = ({ text, record }) => {
113
- return h("span", text || renderItem.default);
114
- };
115
- }
116
-
117
- // 解析json字段
118
- if (renderItem.key && renderItem.key.includes(".")) {
119
- renderItem.customRender = ({ text, record }) => {
120
- return h("span", _.get(record, renderItem.key!.split(".")));
121
- };
122
- }
123
-
124
- if (renderItem.type == "pic") {
125
- renderItem.customRender = ({ text, record }) => {
126
- const key = renderItem.key;
127
- let pics = key.includes(".") ? _.get(record, key.split(".")) : _.get(record, key);
128
- if (_.isEmpty(pics)) return h("span", "暂无");
129
- pics = _.isArray(pics) ? pics : [pics];
130
- const format_pics: any = [];
131
- for (const pic_item of pics) {
132
- if (!pic_item.includes("http") && renderItem.showOssUrlFunc) {
133
- format_pics.push(renderItem.showOssUrlFunc(pic_item));
134
- } else {
135
- format_pics.push(pic_item);
136
- }
137
- }
138
-
139
- return h(
140
- "a",
141
- {
142
- onClick: () => {
143
- viewerApi({ images: format_pics });
144
- },
145
- },
146
- h("img", {
147
- src: format_pics[0],
148
- style: {
149
- width: (renderItem.picWidth || 60) + "px",
150
- height: (renderItem.picHeight || 60) + "px",
151
- marginLeft: "10px",
152
- },
153
- }),
154
- );
155
- };
156
- }
157
-
158
- // 处理MAP字段
159
- if (renderItem.mapPath) {
160
- const mapInfoStr: any = localStorage.getItem("map");
161
- const mapInfo = JSON.parse(mapInfoStr);
162
- const mapItem = _.get(mapInfo, renderItem.mapPath);
163
- renderItem.customRender = ({ text, record }) => {
164
- return h("span", mapItem[text]);
165
- };
166
- }
167
-
168
- if (renderItem.type == "date") {
169
- renderItem.customRender = ({ text, record }) => {
170
- return h("span", dayjs(text).format(renderItem.format || "YYYY-MM-DD HH:mm:ss"));
171
- };
172
- }
173
-
174
- renderItem["dataIndex"] = renderItem["key"];
175
- returnColumns.push(renderItem);
103
+ const mapInfo = computed(() => {
104
+ try {
105
+ return JSON.parse(localStorage.getItem("map") || "{}");
106
+ } catch (error) {
107
+ return {};
176
108
  }
109
+ });
177
110
 
178
- return returnColumns;
179
- };
180
-
181
- const columns = renderTableColumns(tableColumns);
111
+ const columns = computed(() =>
112
+ buildTableColumns(tableColumns, {
113
+ editColumns,
114
+ mapInfo: mapInfo.value,
115
+ openImageViewer: (images) => viewerApi({ images }),
116
+ }),
117
+ );
182
118
 
183
119
  const getAllExportColumns = () => {
184
120
  return tableColumns.filter((item) => item.type !== "operate" && !!item.key);
@@ -213,42 +149,6 @@ const resetExportColumns = () => {
213
149
  exportForm.columns = getAllExportColumns().map((item) => String(item.key));
214
150
  };
215
151
 
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
152
  const downloadXlsx = (rows: any[], exportColumns: TableColumn[]) => {
253
153
  const headers = exportColumns.map((col: any) => {
254
154
  const title = col.title as any;
@@ -256,7 +156,7 @@ const downloadXlsx = (rows: any[], exportColumns: TableColumn[]) => {
256
156
  });
257
157
 
258
158
  const dataRows = rows.map((row: any) => {
259
- return exportColumns.map((col: any) => getCellValue(row, col));
159
+ return exportColumns.map((col: any) => getTableCellValue(row, col, mapInfo.value));
260
160
  });
261
161
 
262
162
  const worksheet = XLSX.utils.aoa_to_sheet([headers, ...dataRows]);
@@ -368,23 +268,26 @@ const confirmExport = async () => {
368
268
  };
369
269
 
370
270
  const renderTableData = async () => {
271
+ const requestId = ++tableRequestId;
371
272
  loading.value = true;
372
- if (getData) {
373
- const _searchInitFormData = initSearchFormData ? JSON.parse(JSON.stringify(initSearchFormData)) : {};
374
- const _pageQuery = JSON.parse(JSON.stringify(pageQuery));
375
- const _tableData = await getData({ ..._searchInitFormData, ..._pageQuery, ..._.cloneDeep(searchQuery) });
376
- tableData.data = _tableData.data;
377
- tableData.count = _tableData.count;
378
- tableData.pages = _tableData.pages;
379
- } else if (data) {
380
- tableData.data = data;
273
+ try {
274
+ if (getData) {
275
+ const _searchInitFormData = initSearchFormData ? JSON.parse(JSON.stringify(initSearchFormData)) : {};
276
+ const _pageQuery = JSON.parse(JSON.stringify(pageQuery));
277
+ const _tableData = await getData({ ..._searchInitFormData, ..._pageQuery, ..._.cloneDeep(searchQuery) });
278
+ if (requestId !== tableRequestId) return;
279
+ tableData.data = _tableData.data;
280
+ tableData.count = _tableData.count;
281
+ tableData.pages = _tableData.pages;
282
+ } else if (data) {
283
+ tableData.data = data;
284
+ tableData.count = data.length;
285
+ }
286
+ } finally {
287
+ if (requestId === tableRequestId) {
288
+ loading.value = false;
289
+ }
381
290
  }
382
-
383
- tableData.data.forEach((item, index) => {
384
- item["index"] = index;
385
- });
386
-
387
- loading.value = false;
388
291
  };
389
292
 
390
293
  const refresh = () => {
@@ -441,16 +344,20 @@ const getTableData = () => {
441
344
  return toRaw(tableData)["data"];
442
345
  };
443
346
 
444
- const edit = (index: string) => {
445
- editableData[index] = _.cloneDeep(tableData.data[index]);
347
+ const getRowEditKey = (record: any, index: number) => record?.id ?? index;
348
+
349
+ const edit = (record: any, index: number) => {
350
+ editableData[getRowEditKey(record, index)] = _.cloneDeep(record);
446
351
  };
447
- const save = (key: string) => {
448
- Object.assign(tableData.data.filter((item) => key === item.key)[0], editableData[key]);
352
+
353
+ const save = (record: any, index: number) => {
354
+ const key = getRowEditKey(record, index);
355
+ Object.assign(record, editableData[key]);
449
356
  delete editableData[key];
450
357
  };
451
358
 
452
- const cancel = (key: string) => {
453
- delete editableData[key];
359
+ const cancel = (record: any, index: number) => {
360
+ delete editableData[getRowEditKey(record, index)];
454
361
  };
455
362
 
456
363
  onMounted(() => {
@@ -514,8 +421,8 @@ defineExpose({
514
421
  <template v-if="editColumns.includes(column.key as string)">
515
422
  <div>
516
423
  <a-textarea
517
- v-if="editableData[record.index]"
518
- v-model:value="editableData[record.index][column.key]"
424
+ v-if="editableData[getRowEditKey(record, index)]"
425
+ v-model:value="editableData[getRowEditKey(record, index)][column.key]"
519
426
  style="margin: -5px 0"
520
427
  :auto-size="true"
521
428
  />
@@ -536,9 +443,9 @@ defineExpose({
536
443
  {{ item.label }}</a
537
444
  >
538
445
  </span>
539
- <a class="operate-a" v-if="editableData[record.index]" @click="save(record.index)">保存</a>
540
- <a class="operate-a" v-if="editableData[record.index]" @click="cancel(record.index)">退出</a>
541
- <a v-if="editColumns.length > 0" class="operate-a" @click="edit(record.index)">编辑</a>
446
+ <a class="operate-a" v-if="editableData[getRowEditKey(record, index)]" @click="save(record, index)">保存</a>
447
+ <a class="operate-a" v-if="editableData[getRowEditKey(record, index)]" @click="cancel(record, index)">退出</a>
448
+ <a v-if="editColumns.length > 0" class="operate-a" @click="edit(record, index)">编辑</a>
542
449
  </div>
543
450
  </template>
544
451
 
@@ -0,0 +1,115 @@
1
+ import { h } from "vue";
2
+ import dayjs from "dayjs";
3
+ import type { TableColumn } from "./index.d";
4
+ import * as _ from "../../utils/dataUtils";
5
+
6
+ type BuildTableColumnOptions = {
7
+ editColumns?: string[];
8
+ mapInfo?: any;
9
+ openImageViewer?: (images: string[]) => void;
10
+ };
11
+
12
+ export const getColumnValue = (record: any, key?: string) => {
13
+ if (!key) return undefined;
14
+ return key.includes(".") ? _.get(record, key.split(".")) : _.get(record, key);
15
+ };
16
+
17
+ export const getCellValue = (record: any, column: TableColumn, mapInfo: any = {}) => {
18
+ const key = column.key || "";
19
+ let value = getColumnValue(record, key);
20
+
21
+ if (column.mapPath) {
22
+ const mapItem = _.get(mapInfo || {}, column.mapPath) || {};
23
+ value = mapItem[value] ?? value;
24
+ }
25
+
26
+ if (column.type === "date" && value) {
27
+ value = dayjs(value).format(column.format || "YYYY-MM-DD HH:mm:ss");
28
+ }
29
+
30
+ if (column.type === "pic") {
31
+ if (_.isEmpty(value)) return "";
32
+ const pics = _.isArray(value) ? value : [value];
33
+ return pics.join(",");
34
+ }
35
+
36
+ if (_.isEmpty(value) && !_.isEmpty(column.default)) {
37
+ value = column.default;
38
+ }
39
+
40
+ if (_.isArray(value) || (typeof value === "object" && value !== null)) {
41
+ return JSON.stringify(value);
42
+ }
43
+
44
+ return value ?? "";
45
+ };
46
+
47
+ export const buildTableColumns = (tableColumns: TableColumn[], options: BuildTableColumnOptions = {}) => {
48
+ const { editColumns = [], mapInfo = {}, openImageViewer } = options;
49
+
50
+ return tableColumns.map((column) => {
51
+ const renderItem: any = { ...column };
52
+
53
+ if (renderItem.type === "operate") {
54
+ renderItem.width = renderItem.width || renderItem.operateButtons.length * 60;
55
+
56
+ if (editColumns.length > 0) {
57
+ renderItem.width += 110;
58
+ }
59
+
60
+ renderItem.align = "center";
61
+ }
62
+
63
+ if (!_.isEmpty(renderItem.default)) {
64
+ renderItem.customRender = ({ text }: any) => h("span", text || renderItem.default);
65
+ }
66
+
67
+ if (renderItem.key && renderItem.key.includes(".")) {
68
+ renderItem.customRender = ({ record }: any) => h("span", getColumnValue(record, renderItem.key));
69
+ }
70
+
71
+ if (renderItem.type === "pic") {
72
+ renderItem.customRender = ({ record }: any) => {
73
+ let pics = getColumnValue(record, renderItem.key);
74
+ if (_.isEmpty(pics)) return h("span", "暂无");
75
+
76
+ pics = _.isArray(pics) ? pics : [pics];
77
+ const formatPics = pics.map((picItem: string) => {
78
+ if (!picItem.includes("http") && renderItem.showOssUrlFunc) {
79
+ return renderItem.showOssUrlFunc(picItem);
80
+ }
81
+
82
+ return picItem;
83
+ });
84
+
85
+ return h(
86
+ "a",
87
+ {
88
+ onClick: () => openImageViewer?.(formatPics),
89
+ },
90
+ h("img", {
91
+ src: formatPics[0],
92
+ style: {
93
+ width: `${renderItem.picWidth || 60}px`,
94
+ height: `${renderItem.picHeight || 60}px`,
95
+ marginLeft: "10px",
96
+ },
97
+ }),
98
+ );
99
+ };
100
+ }
101
+
102
+ if (renderItem.mapPath) {
103
+ const mapItem = _.get(mapInfo || {}, renderItem.mapPath) || {};
104
+ renderItem.customRender = ({ text }: any) => h("span", mapItem[text] ?? text ?? "");
105
+ }
106
+
107
+ if (renderItem.type === "date") {
108
+ renderItem.customRender = ({ text }: any) =>
109
+ h("span", text ? dayjs(text).format(renderItem.format || "YYYY-MM-DD HH:mm:ss") : "");
110
+ }
111
+
112
+ renderItem.dataIndex = renderItem.key;
113
+ return renderItem;
114
+ });
115
+ };
package/dist/env.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ declare module "*.vue" {
2
+ import type { DefineComponent } from "vue";
3
+
4
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, any>;
5
+ export default component;
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "p-pc-ui",
3
- "version": "1.3.13",
3
+ "version": "1.3.14",
4
4
  "module": "dist/index.ts",
5
5
  "main": "dist/index.ts",
6
6
  "files": [