vue-super-crud 1.7.2 → 1.8.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue-super-crud",
3
- "version": "1.7.2",
3
+ "version": "1.8.0",
4
4
  "main": "lib/super-crud.min.js",
5
5
  "files": [
6
6
  "lib",
@@ -9,8 +9,9 @@ export const renderItem = {
9
9
  },
10
10
  dict: {
11
11
  strict: true,
12
- type: [String, Object],
12
+ type: [String, Object, Function],
13
13
  properties: {
14
+ key: String,
14
15
  request: Function,
15
16
  label: String, // 数据字典中label字段的属性名
16
17
  value: String, // 数据字典中value字段的属性名
@@ -54,6 +55,7 @@ export const renderItem = {
54
55
  },
55
56
  }, // 验证规则
56
57
  position: Boolean, // 是否开启位置渲染
58
+ formatData: [Object, String], // 响应式数据格式化
57
59
  };
58
60
 
59
61
  export const buttonItem = {
@@ -77,6 +77,30 @@ export default {
77
77
  type: String,
78
78
  default: "small",
79
79
  },
80
+ persistPageSize: Boolean, // 是否持久化拖动宽度
81
+ persistWidth: Boolean, // 是否持久化拖动宽度
82
+ delayRender: Boolean, // 是否启用延迟渲染
83
+ delayRenderConfig: {
84
+ // 延迟渲染配置
85
+ type: [Boolean, Object],
86
+ default: () => ({
87
+ batchSize: 16,
88
+ maxProcessTime: 16,
89
+ frameDelay: 2,
90
+ strategy: "cell",
91
+ }),
92
+ properties: {
93
+ batchSize: Number, // 每批处理的元素数量,数值越大渲染越快,但可能导致卡顿
94
+ maxProcessTime: Number, // 每帧最大处理时间(ms),防止单帧处理时间过长
95
+ frameDelay: Number, // 批次间的帧间隔,数值越大越平滑,但渲染完成时间越长
96
+ },
97
+ }, // 是否启用延迟渲染
98
+ virtualized: Boolean, // 是否启用虚拟列表
99
+ itemSize: {
100
+ // 延迟渲染、虚拟列表的固定行高
101
+ type: Number,
102
+ default: 40,
103
+ },
80
104
  summaryData: Array, // 外部传入的统计数据
81
105
  // 是否禁用
82
106
  disabled: Boolean,
@@ -140,6 +164,7 @@ export default {
140
164
  },
141
165
  },
142
166
  }),
167
+ addChild: buttonItem, // 子级新增按钮配置
143
168
  batchDelete: buttonItem, // 批量删除按钮配置
144
169
  delete: buttonItem, // 删除按钮配置
145
170
  view: buttonItem, // 查看按钮配置,仅限`dialog`模式
@@ -250,9 +275,6 @@ export default {
250
275
  default: () => ({
251
276
  pageNum: "pageNum",
252
277
  pageSize: "pageSize",
253
- detailResult: "data",
254
- listResult: "data",
255
- total: "total",
256
278
  }),
257
279
  },
258
280
  // 是否本地生成唯一标识
@@ -281,7 +303,6 @@ export default {
281
303
  type: [Boolean, Object],
282
304
  default: () => ({
283
305
  show: true,
284
- width: 290,
285
306
  resetBtn: true,
286
307
  }),
287
308
  properties: {
@@ -337,6 +358,7 @@ export default {
337
358
  layout: "total, sizes, prev, pager, next, jumper",
338
359
  background: true,
339
360
  pagerCount: 5,
361
+ memorizeScroll: true, // 临时保存分页滚动位置
340
362
  }),
341
363
  },
342
364
  // 空状态配置
@@ -405,6 +427,7 @@ export default {
405
427
  calcWidth: Number,
406
428
  defaultWidth: Number,
407
429
  sameRowSpan: [String, Boolean],
430
+ spanProp: [String, Boolean],
408
431
  fixed: [String, Boolean],
409
432
  handles: {
410
433
  type: Array,
@@ -465,6 +488,7 @@ export default {
465
488
  hiddenList: Boolean, // 是否只隐藏列表
466
489
  children: Array, // 嵌套列子列
467
490
  sameRowSpan: [String, Boolean], // 是否合并单元格
491
+ spanProp: [String, Boolean], // 是否合并单元格
468
492
  spanMethod: Function, // 合并单元格方法
469
493
  isEdit: [Boolean, Function], // 是否允许编辑
470
494
  summary: {
package/src/index.js CHANGED
@@ -12,6 +12,9 @@ import tabs from "pak/tabs";
12
12
  import verifyInput from "pak/verifyInput";
13
13
  import button from "pak/button";
14
14
  import position from "pak/core/components/position";
15
+ import grid from "pak/grid/index.vue";
16
+ import cell from "pak/grid/cell.vue";
17
+ import lazyRender from "pak/lazyRender/index.vue";
15
18
 
16
19
  import { mergeTemplate } from "./template";
17
20
  import directive from "pak/directive";
@@ -33,28 +36,30 @@ const components = [
33
36
  verifyInput,
34
37
  button,
35
38
  position,
39
+ grid,
40
+ cell,
41
+ lazyRender,
36
42
  ];
37
43
 
38
44
  const install = function (Vue, opts = {}) {
39
45
  // 判断是否安装
40
46
  if (install.installed) return;
41
47
  install.installed = true;
42
- if (opts.template) {
43
- mergeTemplate(opts.template);
44
- }
45
- window.Vue = Vue;
46
48
 
47
- Vue.prototype.$scOpt = opts;
48
- Vue.prototype.$scDialog = dialog;
49
- Vue.prototype.$scDict = globalDict(Vue, opts.dict || {});
49
+ // 合并外部代码模板
50
+ opts.template && mergeTemplate(opts.template);
50
51
 
52
+ // 初始化配置管理器
51
53
  configManager.create(config, opts);
52
- // Vue.use(Contextmenu);
54
+
53
55
  // 遍历注册全局组件
54
- components.forEach((component) => {
55
- Vue.component(component.name, component);
56
- });
56
+ components.forEach((component) => Vue.component(component.name, component));
57
+
58
+ Vue.prototype.$scOpt = opts;
59
+ Vue.prototype.$scDialog = dialog;
60
+ Vue.prototype.$scDict = globalDict(Vue, opts.dict || {});
57
61
  directive(Vue);
62
+ window.Vue = Vue;
58
63
  };
59
64
 
60
65
  // 判断是否是直接引入文件
@@ -241,12 +241,11 @@ export default {
241
241
  numberFormat: (item) => {
242
242
  const config = {
243
243
  precision: 2, // 精度(小数位数)
244
- round: true, // 是否四舍五入
244
+ round: false, // 是否四舍五入
245
245
  toFixed: false, // 是否固定小数位数
246
246
  thousandth: false, // 是否显示千分位
247
247
  prefix: "", // 前缀
248
248
  suffix: "", // 后缀
249
- keepZero: false, // 是否保留末尾0
250
249
  ...(get(item, "formatData") || item),
251
250
  };
252
251
 
@@ -258,23 +257,50 @@ export default {
258
257
  };
259
258
 
260
259
  // 处理数字精度
261
- const formatPrecision = (num) => {
260
+ const formatPrecision = (num, originalStr = "") => {
262
261
  if (typeof num !== "number") return num;
262
+ let result;
263
+ if (config.precision !== 0) {
264
+ const endsWithDot = originalStr.endsWith(".");
265
+ // 检查原始字符串是否以0结尾的小数
266
+ const endsWithZero =
267
+ originalStr.includes(".") && originalStr.endsWith("0");
268
+ const decimalPlaces = endsWithZero
269
+ ? originalStr.split(".")[1].length
270
+ : 0;
271
+
272
+ // 使用字符串操作来保持精确度
273
+ const numStr = String(num);
274
+ const parts = numStr.split(".");
275
+ if (parts.length > 1) {
276
+ const integerPart = parts[0];
277
+ const decimalPart = parts[1];
278
+ if (config.round) {
279
+ result = Number(Number(num).toFixed(config.precision));
280
+ } else {
281
+ // 直接截取所需的小数位数
282
+ result = Number(
283
+ integerPart + "." + decimalPart.slice(0, config.precision)
284
+ );
285
+ }
286
+ } else {
287
+ result = num;
288
+ }
263
289
 
264
- if (config.precision !== undefined) {
265
- const multiplier = Math.pow(10, config.precision);
266
- num = config.round
267
- ? Math.round(num * multiplier) / multiplier
268
- : Math.floor(num * multiplier) / multiplier;
269
- }
270
-
271
- if (config.toFixed && config.precision !== undefined) {
272
- num = num.toFixed(config.precision);
273
- if (!config.keepZero) {
274
- num = String(Number(num));
290
+ // 如果原始输入以0结尾,使用原始小数位数
291
+ if (endsWithZero && !config.toFixed) {
292
+ result = Number(result).toFixed(
293
+ Math.min(config.precision, decimalPlaces)
294
+ );
295
+ } else if (endsWithDot && !String(result).includes(".")) {
296
+ result = result + ".";
297
+ } else if (config.toFixed) {
298
+ result = Number(result).toFixed(config.precision);
275
299
  }
300
+ } else {
301
+ result = Number(num).toFixed(0);
276
302
  }
277
- return num;
303
+ return result;
278
304
  };
279
305
 
280
306
  // 处理多个小数点的情况(只保留第一个)
@@ -289,6 +315,11 @@ export default {
289
315
 
290
316
  return {
291
317
  input: (value) => {
318
+ // 处理空值情况
319
+ if (value === undefined || value === null || value === "") {
320
+ return "";
321
+ }
322
+
292
323
  try {
293
324
  let rawStr = "";
294
325
  if (typeof value === "string") {
@@ -297,25 +328,15 @@ export default {
297
328
  rawStr = String(value);
298
329
  }
299
330
 
300
- // 校验是否为有效的数字格式(允许负号和小数点)
301
- if (!/^-?\d*\.?\d*$/.test(rawStr)) {
302
- return value;
303
- }
304
-
305
- // 检查原始字符串是否以小数点结尾
306
- const endsWithDot = rawStr.endsWith(".");
307
-
308
331
  const num = Number(rawStr);
332
+ // 如果转换后是 NaN,返回空字符串
309
333
  if (isNaN(num)) {
310
- return value;
334
+ return "";
311
335
  }
312
- let formatted = formatPrecision(num);
313
- formatted = String(formatted);
314
336
 
315
- // 如果原始输入以小数点结尾,但格式化后中没有小数点,则补回
316
- if (endsWithDot && !formatted.includes(".")) {
317
- formatted += ".";
318
- }
337
+ // 传入原始字符串,用于处理特殊情况
338
+ let formatted = formatPrecision(num, rawStr);
339
+ formatted = String(formatted);
319
340
 
320
341
  if (config.thousandth) {
321
342
  formatted = formatThousandth(formatted);
@@ -324,7 +345,7 @@ export default {
324
345
  return `${config.prefix}${formatted}${config.suffix}`;
325
346
  } catch (error) {
326
347
  console.warn("数字格式化失败:", error);
327
- return value;
348
+ return "";
328
349
  }
329
350
  },
330
351
  // 输出时去除格式化(还原为原始数字字符串)
@@ -354,50 +375,16 @@ export default {
354
375
  },
355
376
  };
356
377
  },
357
- // 输入格式化
358
- inputFormat: (item) => {
359
- // 预设的正则规则
360
- const REGEX_RULES = {
361
- number: /[^\d]/g, // 仅数字
362
- phone: /[^\d]/g, // 手机号
363
- decimal: /[^\d.]/g, // 小数
364
- letter: /[^a-zA-Z]/g, // 仅字母
365
- chinese: /[^\u4e00-\u9fa5]/g, // 仅中文
366
- letterNumber: /[^a-zA-Z0-9]/g, // 字母和数字
367
- email: /[^a-zA-Z0-9@._-]/g, // 邮箱
368
- custom: null, // 自定义正则
369
- };
370
-
378
+ // 自定义正则格式化
379
+ regexFormat: (item) => {
371
380
  // 默认配置
372
381
  const config = {
373
- inputType: "number", // 格式化类型
374
- pattern: "", // 自定义正则
375
- maxLength: undefined, // 最大长度
376
- decimal: undefined, // 小数位数(type为decimal时使用)
382
+ pattern: "", // 自定义正则表达式
383
+ flags: "g", // 正则标志,默认全局匹配
384
+ replace: "", // 替换值,默认为空字符串
377
385
  ...(get(item, "formatData") || item),
378
386
  };
379
387
 
380
- // 处理小数格式化
381
- const formatDecimal = (value) => {
382
- if (!value) return value;
383
-
384
- // 先去除非数字和小数点
385
- let formatted = value.replace(/[^\d.]/g, "");
386
-
387
- // 只保留第一个小数点
388
- const parts = formatted.split(".");
389
- if (parts.length > 2) {
390
- formatted = parts[0] + "." + parts.slice(1).join("");
391
- }
392
-
393
- // 处理小数位数
394
- if (config.decimal !== undefined && parts[1]) {
395
- formatted = parts[0] + "." + parts[1].slice(0, config.decimal);
396
- }
397
-
398
- return formatted;
399
- };
400
-
401
388
  return {
402
389
  input: (value) => {
403
390
  if (!value) return value;
@@ -405,28 +392,38 @@ export default {
405
392
  try {
406
393
  let formatted = String(value);
407
394
 
408
- // 处理最大长度
409
- if (config.maxLength) {
410
- formatted = formatted.slice(0, config.maxLength);
411
- }
412
-
413
- // 小数特殊处理
414
- if (config.inputType === "decimal") {
415
- return formatDecimal(formatted);
395
+ // 验证并应用正则表达式
396
+ if (config.pattern) {
397
+ let regex;
398
+ if (config.pattern instanceof RegExp) {
399
+ // 如果已经是RegExp对象,直接使用
400
+ regex = config.pattern;
401
+ } else {
402
+ // 如果是字符串,创建RegExp对象
403
+ regex = new RegExp(config.pattern, config.flags);
404
+ }
405
+
406
+ // 如果是验证模式(以^开头且以$结尾),则进行完整匹配
407
+ if (
408
+ config.pattern.toString().startsWith("/^") &&
409
+ config.pattern.toString().endsWith("$/")
410
+ ) {
411
+ return regex.test(formatted) ? formatted : "";
412
+ } else {
413
+ // 否则进行替换
414
+ formatted = formatted.replace(regex, config.replace);
415
+ }
416
416
  }
417
417
 
418
- // 优先使用自定义正则,如果没有则使用预设规则
419
- const rule = config.pattern
420
- ? new RegExp(config.pattern, "g")
421
- : REGEX_RULES[config.inputType];
422
-
423
- return formatted.replace(rule, "");
418
+ return formatted;
424
419
  } catch (error) {
425
- console.warn("输入格式化失败:", error);
420
+ console.warn("自定义正则格式化失败:", error);
426
421
  return value;
427
422
  }
428
423
  },
429
- output: (value) => value,
424
+ output: (value) => {
425
+ return value;
426
+ },
430
427
  };
431
428
  },
432
429
  // 百分比格式化