@deot/vc-components 1.0.23 → 1.0.25

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/index.cjs CHANGED
@@ -12,6 +12,7 @@ const vcHooks = require('@deot/vc-hooks');
12
12
  const helperWheel = require('@deot/helper-wheel');
13
13
  const helperValidator = require('@deot/helper-validator');
14
14
  const helperCache = require('@deot/helper-cache');
15
+ const PhotoSwipeLightbox = require('photoswipe/lightbox');
15
16
  const Load = require('@deot/helper-load');
16
17
  const helperScheduler = require('@deot/helper-scheduler');
17
18
  const lodash = require('lodash');
@@ -1087,7 +1088,7 @@ const Button = /* @__PURE__ */ vue.defineComponent({
1087
1088
  slots
1088
1089
  }) {
1089
1090
  const vm = vue.getCurrentInstance();
1090
- const hasSlot = vue.ref(true);
1091
+ const isHover = vue.ref(false);
1091
1092
  const isLoading = vue.ref(false);
1092
1093
  const group = vue.inject('vc-button-group', {
1093
1094
  size: 'medium',
@@ -1096,10 +1097,11 @@ const Button = /* @__PURE__ */ vue.defineComponent({
1096
1097
  });
1097
1098
  const classes = vue.computed(() => ({
1098
1099
  'is-circle': props.circle || group.circle,
1099
- 'is-alone': !hasSlot.value,
1100
+ 'is-alone': !slots?.default,
1100
1101
  'is-round': props.round,
1101
1102
  'is-long': props.long,
1102
1103
  'is-disabled': props.disabled,
1104
+ 'is-hover': isHover.value,
1103
1105
  [`is-${props.size}`]: true,
1104
1106
  [`is-${props.type}`]: true
1105
1107
  }));
@@ -1112,9 +1114,6 @@ const Button = /* @__PURE__ */ vue.defineComponent({
1112
1114
  });
1113
1115
  }
1114
1116
  };
1115
- vue.onMounted(() => {
1116
- hasSlot.value = slots.default !== undefined;
1117
- });
1118
1117
  return () => {
1119
1118
  return vue.createVNode(Debounce, {
1120
1119
  "tag": props.tag,
@@ -1125,15 +1124,19 @@ const Button = /* @__PURE__ */ vue.defineComponent({
1125
1124
  "wait": props.wait,
1126
1125
  "disabled": props.disabled,
1127
1126
  "type": props.htmlType,
1128
- "onClick": handleClick
1127
+ "onClick": handleClick,
1128
+ "onMouseenter": () => isHover.value = true,
1129
+ "onMouseleave": () => isHover.value = false
1129
1130
  }, {
1130
1131
  default: () => [props.icon && vue.createVNode(Icon, {
1131
1132
  "type": props.icon
1132
- }, null), isLoading.value && vue.createVNode(Spin, {
1133
+ }, null), slots.icon && slots?.icon?.({
1134
+ hover: isHover.value
1135
+ }), isLoading.value && vue.createVNode(Spin, {
1133
1136
  "size": 12,
1134
1137
  "foreground": props.type === 'default' ? '#ccc' : '#fff',
1135
1138
  "class": "vc-button__loading"
1136
- }, null), hasSlot.value && vue.createVNode("span", null, [slots?.default?.()])]
1139
+ }, null), slots?.default && vue.createVNode("span", null, [slots?.default?.()])]
1137
1140
  });
1138
1141
  };
1139
1142
  }
@@ -1549,7 +1552,8 @@ const useCheckbox = () => {
1549
1552
  "is-indeterminate": props.indeterminate,
1550
1553
  "is-checked": checked.value,
1551
1554
  "is-disabled": props.disabled,
1552
- "is-focus": isFocus.value
1555
+ "is-focus": isFocus.value,
1556
+ "is-error": !!formItem?.message?.value
1553
1557
  };
1554
1558
  });
1555
1559
  vue.watch(
@@ -3200,7 +3204,8 @@ const useInput = (input) => {
3200
3204
  const classes = vue.computed(() => {
3201
3205
  return {
3202
3206
  "is-focus": isFocus.value,
3203
- "is-disabled": props.disabled
3207
+ "is-disabled": props.disabled,
3208
+ "is-error": !!formItem?.message?.value
3204
3209
  };
3205
3210
  });
3206
3211
  const currentMaxlength = vue.computed(() => {
@@ -8501,7 +8506,7 @@ const props$P = {
8501
8506
  },
8502
8507
  width: {
8503
8508
  type: Number,
8504
- default: 300
8509
+ default: 600
8505
8510
  },
8506
8511
  height: {
8507
8512
  type: Number,
@@ -8527,6 +8532,8 @@ const props$P = {
8527
8532
  maskStyle: [Object, String],
8528
8533
  wrapperClass: [Object, String],
8529
8534
  wrapperStyle: [Object, String],
8535
+ contentStyle: [Object, String],
8536
+ contentClass: [Object, String],
8530
8537
  closeWithCancel: {
8531
8538
  type: Boolean,
8532
8539
  default: true
@@ -8686,7 +8693,8 @@ const DrawerView = /* @__PURE__ */ vue.defineComponent({
8686
8693
  "class": "vc-drawer__content-container"
8687
8694
  }, [vue.createVNode(Scroller, {
8688
8695
  "native": false,
8689
- "contentClass": "vc-drawer__content"
8696
+ "contentClass": [props.contentClass, 'vc-drawer__content'],
8697
+ "contentStyle": props.contentStyle
8690
8698
  }, {
8691
8699
  default: () => [typeof props.content === 'string' ? vue.createVNode("div", {
8692
8700
  "innerHTML": props.content
@@ -8718,7 +8726,7 @@ const Drawer$ = new Portal(DrawerView, {
8718
8726
  multiple: true
8719
8727
  });
8720
8728
  const destroy$3 = () => Drawer$.destroy();
8721
- const open$1 = (options) => {
8729
+ const open$2 = (options) => {
8722
8730
  const leaf = Drawer$.popup({
8723
8731
  ...options,
8724
8732
  onFulfilled: options.onClose,
@@ -8728,7 +8736,7 @@ const open$1 = (options) => {
8728
8736
  leaf.wrapper.toggle?.(true);
8729
8737
  return leaf;
8730
8738
  };
8731
- const Drawer = Object.assign(DrawerView, { open: open$1, destroy: destroy$3 });
8739
+ const Drawer = Object.assign(DrawerView, { open: open$2, destroy: destroy$3 });
8732
8740
 
8733
8741
  const MDrawer = Drawer;
8734
8742
  const MDrawerView = DrawerView;
@@ -8827,7 +8835,7 @@ const useForm = (expose, options = {}) => {
8827
8835
  const instance = vue.getCurrentInstance();
8828
8836
  const props = instance.props;
8829
8837
  const fields = [];
8830
- vue.provide("form", {
8838
+ vue.provide("vc-form", {
8831
8839
  props,
8832
8840
  add: (field) => {
8833
8841
  field && fields.push(field);
@@ -8975,10 +8983,12 @@ const props$L = {
8975
8983
  default: false
8976
8984
  },
8977
8985
  labelPosition: {
8978
- type: String,
8979
- default: "right"
8986
+ type: String
8980
8987
  },
8981
- contentStyle: String
8988
+ contentStyle: [Object, String],
8989
+ contentClass: [Object, String],
8990
+ labelStyle: [Object, String],
8991
+ labelClass: [Object, String]
8982
8992
  };
8983
8993
 
8984
8994
  const filterEmpty = (val) => {
@@ -8991,7 +9001,7 @@ const toRules = (rules) => {
8991
9001
  return rules instanceof Array ? rules : rules ? [rules] : [];
8992
9002
  };
8993
9003
  const useFormItem = (expose) => {
8994
- const form = vue.inject("form");
9004
+ const form = vue.inject("vc-form");
8995
9005
  const instance = vue.getCurrentInstance();
8996
9006
  const props = instance.props;
8997
9007
  const { slots } = instance;
@@ -9053,10 +9063,13 @@ const useFormItem = (expose) => {
9053
9063
  });
9054
9064
  const labelStyle = vue.computed(() => {
9055
9065
  const labelWidth = props.labelWidth === 0 || props.labelWidth ? props.labelWidth : isNest.value ? 0 : form.props.labelWidth;
9056
- return {
9057
- width: labelPosition.value !== "top" && labelWidth && labelWidth > 0 ? `${labelWidth}px` : "auto",
9058
- textAlign: labelPosition.value === "top" ? "left" : labelPosition.value
9059
- };
9066
+ return [
9067
+ {
9068
+ width: labelPosition.value !== "top" && labelWidth && labelWidth > 0 ? `${labelWidth}px` : "auto",
9069
+ textAlign: labelPosition.value === "top" ? "left" : labelPosition.value
9070
+ },
9071
+ props.labelStyle
9072
+ ];
9060
9073
  });
9061
9074
  const contentStyle = vue.computed(() => {
9062
9075
  const labelWidth = props.labelWidth === 0 || props.labelWidth ? props.labelWidth : form.props.labelWidth;
@@ -9268,14 +9281,14 @@ const FormItem = /* @__PURE__ */ vue.defineComponent({
9268
9281
  })];
9269
9282
  return vue.createVNode("div", {
9270
9283
  "class": ['vc-form-item', classes.value]
9271
- }, [vue.createVNode("div", {
9284
+ }, [(label || slots.label) && vue.createVNode("div", {
9272
9285
  "style": labelStyle.value,
9273
- "class": "vc-form-item__label",
9286
+ "class": ['vc-form-item__label', props.labelClass],
9274
9287
  "for": labelFor
9275
9288
  }, [vue.createVNode("label", null, [label || slots.label?.()])]), vue.createVNode("div", {
9276
9289
  "class": "vc-form-item__wrapper"
9277
9290
  }, [vue.createVNode("div", {
9278
- "class": "vc-form-item__content",
9291
+ "class": ['vc-form-item__content', props.contentClass],
9279
9292
  "style": contentStyle.value
9280
9293
  }, [slots.default?.(), slots.error ? slots.error({
9281
9294
  show: showError.value,
@@ -9501,7 +9514,7 @@ const ObjectFit = {
9501
9514
  FILL: 'fill',
9502
9515
  SCALE_DOWN: 'scale-down'
9503
9516
  };
9504
- const Image = /* @__PURE__ */ vue.defineComponent({
9517
+ const Image$1 = /* @__PURE__ */ vue.defineComponent({
9505
9518
  name: COMPONENT_NAME$W,
9506
9519
  inheritAttrs: false,
9507
9520
  props: props$H,
@@ -9696,7 +9709,7 @@ const Image = /* @__PURE__ */ vue.defineComponent({
9696
9709
  }
9697
9710
  });
9698
9711
 
9699
- const MImage = Image;
9712
+ const MImage = Image$1;
9700
9713
 
9701
9714
  const props$G = {
9702
9715
  tag: {
@@ -9734,7 +9747,7 @@ const props$F = {
9734
9747
  /** @jsxImportSource vue */
9735
9748
 
9736
9749
  const COMPONENT_NAME$U = 'vc-image-preview';
9737
- const ImagePreview = /* @__PURE__ */ vue.defineComponent({
9750
+ const ImagePreview$1 = /* @__PURE__ */ vue.defineComponent({
9738
9751
  name: COMPONENT_NAME$U,
9739
9752
  props: props$F,
9740
9753
  setup(props, {
@@ -9748,6 +9761,81 @@ const ImagePreview = /* @__PURE__ */ vue.defineComponent({
9748
9761
  }
9749
9762
  });
9750
9763
 
9764
+ const MAX_WIDTH = window.innerWidth;
9765
+ const MAX_HEIGHT = window.innerHeight;
9766
+ const getFitSize = (src) => {
9767
+ return new Promise((resolve) => {
9768
+ const img = new Image();
9769
+ let width;
9770
+ let height;
9771
+ img.onload = () => {
9772
+ const owidth = img.naturalWidth || img.width;
9773
+ const oheight = img.naturalHeight || img.height;
9774
+ if (owidth > oheight) {
9775
+ width = Math.min(MAX_WIDTH, owidth);
9776
+ height = width / owidth * oheight;
9777
+ resolve({
9778
+ width,
9779
+ height
9780
+ });
9781
+ } else {
9782
+ height = Math.min(MAX_HEIGHT, oheight);
9783
+ width = height / oheight * owidth;
9784
+ resolve({
9785
+ width,
9786
+ height
9787
+ });
9788
+ }
9789
+ };
9790
+ img.onerror = () => resolve({});
9791
+ img.src = src;
9792
+ });
9793
+ };
9794
+ const open$1 = async (options) => {
9795
+ const e = VcInstance.globalEvent;
9796
+ const data = options.data.map((i) => {
9797
+ if (typeof i === "string") {
9798
+ return {
9799
+ src: i
9800
+ };
9801
+ }
9802
+ return {
9803
+ ...i,
9804
+ src: i.source || i.src
9805
+ };
9806
+ });
9807
+ for (let i = 0; i < data.length; i++) {
9808
+ if (!data[i].width) {
9809
+ data[i] = {
9810
+ ...data[i],
9811
+ ...await getFitSize(data[i].src)
9812
+ };
9813
+ }
9814
+ }
9815
+ const lightbox = new PhotoSwipeLightbox({
9816
+ pswpModule: () => import('photoswipe'),
9817
+ closeTitle: "关闭(Esc)",
9818
+ zoomTitle: "缩放",
9819
+ arrowPrevTitle: "上一张",
9820
+ arrowNextTitle: "下一张",
9821
+ errorMsg: "网络异常 图片加载失败",
9822
+ indexIndicatorSep: " / ",
9823
+ initialZoomLevel: "fit"
9824
+ });
9825
+ lightbox.init();
9826
+ lightbox.loadAndOpen(
9827
+ options.current || 0,
9828
+ data,
9829
+ // 下面无效,需要给官方支持
9830
+ {
9831
+ x: e?.clientX,
9832
+ y: e?.clientY
9833
+ }
9834
+ );
9835
+ };
9836
+
9837
+ const ImagePreview = Object.assign(ImagePreview$1, { open: open$1 });
9838
+
9751
9839
  const MImagePreview = ImagePreview;
9752
9840
 
9753
9841
  const props$E = {
@@ -10308,10 +10396,14 @@ const props$z = {
10308
10396
  validator: (v) => /(small|medium|large)/.test(v),
10309
10397
  default: "small"
10310
10398
  },
10399
+ contentStyle: [Object, String],
10311
10400
  contentClass: [Object, String],
10312
10401
  width: {
10313
10402
  type: Number
10314
10403
  },
10404
+ height: {
10405
+ type: Number
10406
+ },
10315
10407
  mask: {
10316
10408
  type: Boolean,
10317
10409
  default: true
@@ -10350,9 +10442,8 @@ const props$z = {
10350
10442
  type: [String, Boolean],
10351
10443
  default: "取消"
10352
10444
  },
10353
- wrapperStyle: {
10354
- type: [String, Object]
10355
- },
10445
+ wrapperStyle: [String, Object],
10446
+ wrapperClass: [String, Object],
10356
10447
  footer: {
10357
10448
  type: Boolean,
10358
10449
  default: true
@@ -10403,6 +10494,10 @@ const ModalView = /* @__PURE__ */ vue.defineComponent({
10403
10494
  const x = vue.ref(0);
10404
10495
  const y = vue.ref(0);
10405
10496
  const isActive = vue.ref(false);
10497
+
10498
+ // 注: 服务端渲染为0, 在客服端激活前,展示端存在问题【高度不定】
10499
+ const MAX_HEIGHT = vcShared.IS_SERVER ? 0 : window.innerHeight - 20;
10500
+ const MAX_WIDTH = vcShared.IS_SERVER ? 0 : window.innerWidth - 20;
10406
10501
  const defaultSize = vue.computed(() => {
10407
10502
  let width = 0;
10408
10503
  let height = 0;
@@ -10421,17 +10516,21 @@ const ModalView = /* @__PURE__ */ vue.defineComponent({
10421
10516
  break;
10422
10517
  }
10423
10518
  return {
10424
- width: props.width || width,
10425
- height
10519
+ width: Math.min(props.width || width, MAX_WIDTH),
10520
+ height: Math.min(props.height || height, MAX_HEIGHT)
10426
10521
  };
10427
10522
  });
10428
10523
  const basicStyle = vue.computed(() => {
10429
- return {
10524
+ const result = {
10430
10525
  width: `${defaultSize.value.width}px`,
10431
- minHeight: `${defaultSize.value.height}px`,
10432
- // 注: 服务端渲染为0, 在客服端激活前,展示端存在问题【高度不定】
10433
- maxHeight: vcShared.IS_SERVER ? 0 : `${window.innerHeight - 20}px`
10526
+ maxHeight: `${MAX_HEIGHT}px`
10434
10527
  };
10528
+ if (props.height) {
10529
+ result.height = `${defaultSize.value.height}px`;
10530
+ } else {
10531
+ result.minHeight = `${defaultSize.value.height}px`;
10532
+ }
10533
+ return result;
10435
10534
  });
10436
10535
  const draggableStyle = vue.computed(() => {
10437
10536
  if (vcShared.IS_SERVER || !props.draggable) return {};
@@ -10554,6 +10653,7 @@ const ModalView = /* @__PURE__ */ vue.defineComponent({
10554
10653
  * container在最大值时,需要移除,宽度才会缩回去
10555
10654
  */
10556
10655
  const handleContentResize = () => {
10656
+ if (props.height) return;
10557
10657
  const needRefreshScroller = !!scroller.value.wrapper.style.getPropertyValue('height');
10558
10658
  const needRefreshContainer = !!container.value.style.getPropertyValue('height');
10559
10659
  needRefreshContainer && container.value.style.removeProperty('height');
@@ -10652,7 +10752,7 @@ const ModalView = /* @__PURE__ */ vue.defineComponent({
10652
10752
  "style": [props.wrapperStyle || {}, props.draggable ? {
10653
10753
  top: 0
10654
10754
  } : {}],
10655
- "class": "vc-modal__wrapper",
10755
+ "class": [props.wrapperClass, 'vc-modal__wrapper'],
10656
10756
  "onClick": e => handleClose(e, false)
10657
10757
  }, [vue.createVNode(TransitionScale, {
10658
10758
  "mode": "part",
@@ -10699,7 +10799,8 @@ const ModalView = /* @__PURE__ */ vue.defineComponent({
10699
10799
  "height": isTransitionEnd.value ? row.height : void 0,
10700
10800
  "contentClass": [{
10701
10801
  'is-confirm': props.mode
10702
- }, props.contentClass, 'vc-modal__content']
10802
+ }, props.contentClass, 'vc-modal__content'],
10803
+ "contentStyle": props.contentStyle
10703
10804
  }, {
10704
10805
  default: () => [typeof props.content === 'string' ? vue.createVNode("div", {
10705
10806
  "innerHTML": props.content
@@ -12318,8 +12419,12 @@ const Progress = /* @__PURE__ */ vue.defineComponent({
12318
12419
  setup(props, {
12319
12420
  slots
12320
12421
  }) {
12422
+ const currentPercent = vue.computed(() => {
12423
+ const v = Number(props.percent);
12424
+ return v >= 100 ? 100 : v;
12425
+ });
12321
12426
  const currentStatus = vue.computed(() => {
12322
- if (Number(props.percent) >= 100) {
12427
+ if (currentPercent.value === 100) {
12323
12428
  return 'success';
12324
12429
  }
12325
12430
  return props.status;
@@ -12331,6 +12436,7 @@ const Progress = /* @__PURE__ */ vue.defineComponent({
12331
12436
  const binds = vue.computed(() => {
12332
12437
  return {
12333
12438
  ...props,
12439
+ percent: currentPercent.value,
12334
12440
  status: currentStatus.value,
12335
12441
  color: currentColor.value
12336
12442
  };
@@ -12408,7 +12514,8 @@ const useRadio = () => {
12408
12514
  return {
12409
12515
  "is-checked": checked.value,
12410
12516
  "is-disabled": isDisabled.value,
12411
- "is-focus": isFocus.value
12517
+ "is-focus": isFocus.value,
12518
+ "is-error": !!formItem?.message?.value
12412
12519
  };
12413
12520
  });
12414
12521
  vue.watch(
@@ -12592,8 +12699,7 @@ const RadioGroup = /* @__PURE__ */ vue.defineComponent({
12592
12699
  return () => {
12593
12700
  if (props.fragment) return slots.default?.();
12594
12701
  return vue.createVNode("div", {
12595
- "class": "vc-radio-group",
12596
- "style": classes.value,
12702
+ "class": [classes.value, 'vc-radio-group'],
12597
12703
  "name": props.name
12598
12704
  }, [slots?.default?.()]);
12599
12705
  };
@@ -15638,6 +15744,8 @@ const props$c = {
15638
15744
  stripe: Boolean,
15639
15745
  // 是否带有纵向边框
15640
15746
  border: Boolean,
15747
+ // 是否分割线
15748
+ divider: Boolean,
15641
15749
  // 非常影响表格虚拟滚动的性能,按容器的高度手动优化该值
15642
15750
  // 后续考虑移除,动态计算
15643
15751
  rows: {
@@ -15778,6 +15886,7 @@ const Table = /* @__PURE__ */ vue.defineComponent({
15778
15886
  'vc-table--fit': props.fit,
15779
15887
  'vc-table--striped': props.stripe,
15780
15888
  'vc-table--border': props.border || states.isGroup,
15889
+ 'vc-table--divider': props.border || props.divider,
15781
15890
  'vc-table--group': states.isGroup,
15782
15891
  'vc-table--fluid-height': props.maxHeight,
15783
15892
  'vc-table--scrollable-x': layout.states.scrollX,
@@ -16562,7 +16671,7 @@ const defaultRenderCell = (rowData = {}) => {
16562
16671
  }
16563
16672
  const line = column.line || VcInstance.options.TableColumn?.line;
16564
16673
  if (line && value) {
16565
- const base = rowData.isHead || rowData.isTail ? 30 : 20;
16674
+ const base = rowData.isHead || rowData.isTail ? 36 : 24; // 目前gap是12
16566
16675
  const style = {
16567
16676
  // 目前左右pading为10
16568
16677
  // TODO: 含有border还要-1
@@ -16910,7 +17019,11 @@ const props$a = {
16910
17019
  closable: {
16911
17020
  type: Boolean,
16912
17021
  default: false
16913
- }
17022
+ },
17023
+ barStyle: [Object, String],
17024
+ contentStyle: [Object, String],
17025
+ barClass: [Object, String],
17026
+ contentClass: [Object, String]
16914
17027
  };
16915
17028
 
16916
17029
  const useTabs = (options = {}) => {
@@ -17163,10 +17276,10 @@ const Tabs = /* @__PURE__ */ vue.defineComponent({
17163
17276
  "class": "vc-tabs__extra"
17164
17277
  }, [slots.extra?.()]), vue.createVNode("div", {
17165
17278
  "ref": wrapper,
17166
- "style": {
17279
+ "style": [props.barStyle, {
17167
17280
  padding: tabs.scrollable.value ? '0 24px' : 0
17168
- },
17169
- "class": "vc-tabs__bar"
17281
+ }],
17282
+ "class": [props.barClass, 'vc-tabs__bar']
17170
17283
  }, [tabs.scrollable.value && vue.createVNode(Icon, {
17171
17284
  "class": "vc-tabs__icon is-left",
17172
17285
  "type": "left",
@@ -17206,8 +17319,8 @@ const Tabs = /* @__PURE__ */ vue.defineComponent({
17206
17319
  }, null)]);
17207
17320
  })])])]), vue.createVNode("div", {
17208
17321
  "ref": content,
17209
- "style": tabs.contentStyle.value,
17210
- "class": "vc-tabs__content"
17322
+ "style": [props.contentStyle, tabs.contentStyle.value],
17323
+ "class": [props.contentClass, 'vc-tabs__content']
17211
17324
  }, [slots.default?.()])]);
17212
17325
  };
17213
17326
  }
@@ -17822,7 +17935,8 @@ const useTextarea = (textarea, expose) => {
17822
17935
  const classes = vue.computed(() => {
17823
17936
  return {
17824
17937
  "is-focus": isFocus.value,
17825
- "is-disabled": props.disabled
17938
+ "is-disabled": props.disabled,
17939
+ "is-error": !!formItem?.message?.value
17826
17940
  };
17827
17941
  });
17828
17942
  const listeners = vue.computed(() => {
@@ -18740,10 +18854,342 @@ const Upload = vue.defineComponent({
18740
18854
  const MUpload = Upload;
18741
18855
 
18742
18856
  const props = {
18743
- tag: {
18744
- type: String,
18745
- default: "div"
18746
- }
18857
+ picker: {
18858
+ type: Array,
18859
+ default: () => ["image"]
18860
+ },
18861
+ sortable: {
18862
+ type: Boolean,
18863
+ default: false
18864
+ },
18865
+ mask: {
18866
+ type: Boolean,
18867
+ default: false
18868
+ },
18869
+ /**
18870
+ * vc-upload组件的属性
18871
+ */
18872
+ uploadOptions: {
18873
+ type: Object,
18874
+ default() {
18875
+ return {};
18876
+ }
18877
+ },
18878
+ /**
18879
+ * 数据源['xxx.jpg', ....]
18880
+ */
18881
+ modelValue: {
18882
+ type: [String, Array, Object],
18883
+ // { value: '', label: '' }
18884
+ default: () => []
18885
+ },
18886
+ // 数据字典
18887
+ keyValue: {
18888
+ type: Object,
18889
+ default: () => {
18890
+ return {
18891
+ label: "label",
18892
+ value: "value"
18893
+ };
18894
+ }
18895
+ },
18896
+ output: {
18897
+ type: [String, Function],
18898
+ default: "object",
18899
+ validator: (v) => /(string|object)/.test(v)
18900
+ },
18901
+ /**
18902
+ * 可上传的最大值,跟upload内的Max不同,有可能是对象类型,对应的Upload做限制
18903
+ */
18904
+ max: {
18905
+ type: [Number, Object],
18906
+ default: Number.MAX_SAFE_INTEGER
18907
+ },
18908
+ disabled: {
18909
+ type: Boolean,
18910
+ default: false
18911
+ },
18912
+ /**
18913
+ * 上传成功后对数据的格式化
18914
+ */
18915
+ formatter: Function,
18916
+ // TODO 下面两个重复了,需删除
18917
+ /**
18918
+ * 盒子className
18919
+ */
18920
+ boxClass: String,
18921
+ imagePreviewOptions: {
18922
+ type: Object,
18923
+ default: () => ({})
18924
+ },
18925
+ imageClass: String,
18926
+ videoClass: String,
18927
+ audioClass: String,
18928
+ fileClass: String,
18929
+ compressOptions: {
18930
+ type: Object,
18931
+ default: () => {
18932
+ return {
18933
+ compress: false,
18934
+ // 是否开启图片压缩
18935
+ width: 0,
18936
+ // 图片缩放最大宽度,为0默认源图片宽度
18937
+ height: 0,
18938
+ // 图片缩放最大高度,为0默认源图片高度
18939
+ filetype: "image/jpeg",
18940
+ // 文件类型
18941
+ encoderOptions: 0.92
18942
+ // 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,使用默认值 0.92
18943
+ };
18944
+ }
18945
+ },
18946
+ showMessage: Boolean,
18947
+ gallery: Boolean
18948
+ };
18949
+
18950
+ const recognizer = (url) => {
18951
+ const reg = /\.(jpe?g|png|gif|bmp|webp|image|heic|mp4|mov|avi|mpg|mpeg|rmvb)/ig;
18952
+ const result = url.match(reg);
18953
+ return result && result.length ? /.(jpe?g|png|gif|bmp|webp|image|heic)/ig.test(result[result.length - 1]) ? "image" : "video" : "file";
18954
+ };
18955
+ const FILE_ACCEPT_MAP = {
18956
+ DOC_ACCEPTS: ".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
18957
+ EXCEL_ACCEPTS: ".csv,.xls,.xlsx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18958
+ PPT_ACCEPTS: ".ppt,.pptx,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation",
18959
+ PDF_ACCEPTS: ".pdf,application/pdf",
18960
+ TXT_ACCEPTS: "text/plain",
18961
+ HTML_ACCEPTS: "text/html"
18962
+ };
18963
+
18964
+ const {
18965
+ DOC_ACCEPTS,
18966
+ EXCEL_ACCEPTS,
18967
+ PPT_ACCEPTS,
18968
+ PDF_ACCEPTS,
18969
+ TXT_ACCEPTS,
18970
+ HTML_ACCEPTS
18971
+ } = FILE_ACCEPT_MAP;
18972
+ const usePicker = (expose) => {
18973
+ const instance = vue.getCurrentInstance();
18974
+ const props = instance.props;
18975
+ const { emit } = instance;
18976
+ const formItem = vue.inject("vc-form-item", {});
18977
+ const allowKeepString = vue.computed(() => {
18978
+ return typeof props.modelValue === "string";
18979
+ });
18980
+ const allowKeepObject = vue.computed(() => {
18981
+ const v = props.modelValue;
18982
+ return props.output === "object" && props.max === 1 && !Array.isArray(v) && typeof v === "object";
18983
+ });
18984
+ const currentValue = vue.ref({
18985
+ image: [],
18986
+ video: [],
18987
+ audio: [],
18988
+ file: []
18989
+ });
18990
+ const currentUploadOptions = vue.ref({
18991
+ image: {
18992
+ accept: "image/gif,image/jpeg,image/jpg,image/png",
18993
+ ...props.uploadOptions.image || {}
18994
+ },
18995
+ video: {
18996
+ accept: "video/*",
18997
+ ...props.uploadOptions.video || {}
18998
+ },
18999
+ audio: {
19000
+ accept: "audio/*",
19001
+ ...props.uploadOptions.audio || {}
19002
+ },
19003
+ file: {
19004
+ accept: `${DOC_ACCEPTS},${EXCEL_ACCEPTS},${PPT_ACCEPTS},${PDF_ACCEPTS},${TXT_ACCEPTS},${HTML_ACCEPTS}`,
19005
+ ...props.uploadOptions.file || {}
19006
+ }
19007
+ });
19008
+ const dynamicMax = vue.computed(() => {
19009
+ const image = currentValue.value.image || [];
19010
+ const video = currentValue.value.video || [];
19011
+ const audio = currentValue.value.audio || [];
19012
+ const file = currentValue.value.file || [];
19013
+ const imageCount = image.length || 0;
19014
+ const videoCount = video.length || 0;
19015
+ const audioCount = audio.length || 0;
19016
+ const fileCount = file.length || 0;
19017
+ if (typeof props.max === "number") {
19018
+ const curNum = imageCount + videoCount + audioCount + fileCount;
19019
+ const leftNum = props.max - curNum;
19020
+ return {
19021
+ image: leftNum,
19022
+ video: leftNum,
19023
+ audio: leftNum,
19024
+ file: leftNum
19025
+ };
19026
+ } else if (typeof props.max === "object") {
19027
+ const {
19028
+ image: $image,
19029
+ video: $video,
19030
+ audio: $audio,
19031
+ file: $file
19032
+ } = props.max;
19033
+ const max = {};
19034
+ $image && (max.image = $image - imageCount);
19035
+ $video && (max.video = $video - videoCount);
19036
+ $audio && (max.audio = $audio - audioCount);
19037
+ $file && (max.file = $file - fileCount);
19038
+ return max;
19039
+ }
19040
+ return {};
19041
+ });
19042
+ const sync = () => {
19043
+ let v = props.picker.reduce((pre, cur) => pre.concat(currentValue.value[cur] || []), []).filter((i) => !i.errorFlag).map((i) => {
19044
+ if (props.output === "string") return i[props.keyValue.value];
19045
+ if (typeof props.output === "function") return props.output(i) || i;
19046
+ return i;
19047
+ });
19048
+ if (allowKeepString.value) {
19049
+ v = v.map((i) => i[props.keyValue.value] || i).join(",");
19050
+ } else if (allowKeepObject.value) {
19051
+ v = v[0] || null;
19052
+ }
19053
+ emit("update:modelValue", v);
19054
+ emit("change", v);
19055
+ formItem.change?.(v);
19056
+ };
19057
+ const handleFileBefore = async (vFile, fileList, type) => {
19058
+ if (props?.compressOptions?.compress && type === "image") ;
19059
+ const onFileBefore = instance.vnode.props?.onFileBefore || (() => {
19060
+ });
19061
+ return await onFileBefore(vFile, fileList, type) || vFile;
19062
+ };
19063
+ const handleFileStart = (vFile, type) => {
19064
+ currentValue.value[type].push(vFile);
19065
+ emit("file-start", vFile, type);
19066
+ };
19067
+ const handleFileProgress = (e, vFile, type) => {
19068
+ if (parseInt(e.percent, 10) <= 100) {
19069
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19070
+ if (vFile.uploadId === item.uploadId) {
19071
+ return {
19072
+ ...item,
19073
+ percent: e.percent
19074
+ };
19075
+ }
19076
+ return item;
19077
+ });
19078
+ }
19079
+ };
19080
+ const handleFileSuccess = (response, vFile, cycle, type) => {
19081
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19082
+ if (item.uploadId === vFile.uploadId) {
19083
+ return {
19084
+ type,
19085
+ [props.keyValue.label]: vFile.name,
19086
+ // 外部需要满足response中带source
19087
+ [props.keyValue.value]: response.source
19088
+ };
19089
+ }
19090
+ return item;
19091
+ });
19092
+ emit("file-success", response, vFile, cycle, type);
19093
+ };
19094
+ const handleError = (err, type) => {
19095
+ props.showMessage && err.message && Message.error(err.message);
19096
+ emit("error", err, type);
19097
+ };
19098
+ const handleFileError = (response, vFile, cycle, type) => {
19099
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19100
+ if (item.uploadId === vFile.uploadId) {
19101
+ return {
19102
+ ...item,
19103
+ ...response,
19104
+ // 文件基础信息
19105
+ type,
19106
+ [props.keyValue.label]: vFile.name,
19107
+ errorFlag: (/* @__PURE__ */ new Date()).getTime()
19108
+ };
19109
+ }
19110
+ return item;
19111
+ });
19112
+ emit("file-error", response, vFile, cycle, type);
19113
+ };
19114
+ const handleFileComplete = (response, type) => {
19115
+ sync();
19116
+ emit("complete", response, type);
19117
+ };
19118
+ const handleDelete = async (index, type) => {
19119
+ const onRemoveBefore = instance.vnode.props?.onRemoveBefore || (() => {
19120
+ });
19121
+ await onRemoveBefore(index, type);
19122
+ const target = currentValue.value[type];
19123
+ const item = target[index];
19124
+ if (!item) {
19125
+ console.error("【vc-upload-picker】: 没有找到要删除的元素");
19126
+ return;
19127
+ }
19128
+ if (item.errorFlag) {
19129
+ currentValue.value[type] = target.filter(
19130
+ (it) => it.uploadId != item.uploadId
19131
+ );
19132
+ return;
19133
+ }
19134
+ target.splice(index, 1);
19135
+ sync();
19136
+ };
19137
+ const parseModelValue = (v) => {
19138
+ const initialData = { image: [], video: [], audio: [], file: [] };
19139
+ if (allowKeepString.value) {
19140
+ v = (props.max === 1 ? [v] : v.split(",")).filter((i) => !!i);
19141
+ } else if (allowKeepObject.value) {
19142
+ v = [v].filter((i) => i && !!i[props.keyValue.value]);
19143
+ }
19144
+ if (!Array.isArray(v) || !v.length) return initialData;
19145
+ return v.reduce((pre, cur) => {
19146
+ const value = cur[props.keyValue.value] || (typeof cur === "object" ? "" : cur);
19147
+ const label = cur[props.keyValue.label] || value.replace(/^.*\/([^/]+)$/, "$1");
19148
+ const type = cur.type || (props.picker.length === 1 ? props.picker[0] : recognizer(value));
19149
+ switch (type) {
19150
+ case "image":
19151
+ case "video":
19152
+ case "audio":
19153
+ case "file":
19154
+ pre[type].push({
19155
+ // 文件类型
19156
+ type,
19157
+ // 文件名
19158
+ [props.keyValue.label]: label,
19159
+ // 源文件地址
19160
+ [props.keyValue.value]: value,
19161
+ // 上传进度
19162
+ percent: null,
19163
+ // 错误标记
19164
+ errorFlag: false
19165
+ });
19166
+ return pre;
19167
+ default:
19168
+ return pre;
19169
+ }
19170
+ }, initialData);
19171
+ };
19172
+ vue.watch(
19173
+ () => props.modelValue,
19174
+ (v) => {
19175
+ currentValue.value = parseModelValue(v);
19176
+ },
19177
+ { immediate: true }
19178
+ );
19179
+ expose();
19180
+ return {
19181
+ currentValue,
19182
+ currentUploadOptions,
19183
+ dynamicMax,
19184
+ handleDelete,
19185
+ handleFileBefore,
19186
+ handleFileStart,
19187
+ handleFileProgress,
19188
+ handleFileSuccess,
19189
+ handleFileError,
19190
+ handleError,
19191
+ handleFileComplete
19192
+ };
18747
19193
  };
18748
19194
 
18749
19195
  /** @jsxImportSource vue */
@@ -18752,13 +19198,111 @@ const COMPONENT_NAME = 'vc-upload-picker';
18752
19198
  const UploadPicker = /* @__PURE__ */ vue.defineComponent({
18753
19199
  name: COMPONENT_NAME,
18754
19200
  props: props,
19201
+ emits: ['update:modelValue', 'file-success', 'file-start', 'success', 'error', 'complete', 'change', 'remove-before'],
18755
19202
  setup(props, {
18756
- slots
19203
+ slots,
19204
+ expose
18757
19205
  }) {
19206
+ const instance = vue.getCurrentInstance();
19207
+ const currentPicker = vue.computed(() => {
19208
+ return props.picker.reduce((pre, cur) => {
19209
+ switch (cur) {
19210
+ case 'image':
19211
+ pre.push({
19212
+ type: cur,
19213
+ item: 'div'
19214
+ // item: ImageItem
19215
+ });
19216
+ return pre;
19217
+ case 'video':
19218
+ pre.push({
19219
+ type: cur,
19220
+ item: 'div'
19221
+ // item: VideoItem
19222
+ });
19223
+ return pre;
19224
+ case 'audio':
19225
+ pre.push({
19226
+ type: cur,
19227
+ item: 'div'
19228
+ // item: AudioItem
19229
+ });
19230
+ return pre;
19231
+ case 'file':
19232
+ pre.push({
19233
+ type: cur,
19234
+ item: 'div'
19235
+ // item: FileItem
19236
+ });
19237
+ return pre;
19238
+ default:
19239
+ return pre;
19240
+ }
19241
+ }, []);
19242
+ });
19243
+ const handleClick = (e, type) => {
19244
+ const options = VcInstance.options.UploadPicker || {};
19245
+ if (typeof props.gallery === 'function' || props.gallery && options.gallery) {
19246
+ const fn = typeof props.gallery === 'function' ? props.gallery : options.gallery;
19247
+
19248
+ // 阻止原生事件,如video, file不走gallery, 可以跳过;
19249
+ fn(instance, type) && e.stopPropagation();
19250
+ }
19251
+ };
19252
+ const base = usePicker(expose);
18758
19253
  return () => {
18759
19254
  return vue.createVNode("div", {
18760
19255
  "class": "vc-upload-picker"
18761
- }, [slots?.default?.()]);
19256
+ }, [currentPicker.value.map((picker, $index) => {
19257
+ return vue.createVNode(vue.Fragment, {
19258
+ "key": `${picker}-${$index}`
19259
+ }, [base.currentValue.value[picker.type].map((item, index) => {
19260
+ const Item = picker.item;
19261
+ return vue.createVNode(Item, {
19262
+ "key": typeof item === 'object' ? item.uid : item,
19263
+ "it": item,
19264
+ "disabled": props.disabled,
19265
+ "image-preview-options": props.imagePreviewOptions,
19266
+ "imageClass": props.imageClass,
19267
+ "videoClass": props.videoClass,
19268
+ "audioClass": props.audioClass,
19269
+ "fileClass": props.fileClass,
19270
+ "index": index,
19271
+ "data": item,
19272
+ "class": "vc-upload-picker__item",
19273
+ "onDelete": () => base.handleDelete(index, picker.type)
19274
+ }, {
19275
+ default: scopeData => {
19276
+ return slots.default ? slots.default({
19277
+ it: scopeData?.it,
19278
+ current: scopeData?.current,
19279
+ index,
19280
+ name: picker.type
19281
+ }) : scopeData;
19282
+ }
19283
+ });
19284
+ }), vue.withDirectives(vue.createVNode(Upload, vue.mergeProps(base.currentUploadOptions.value[picker.type], {
19285
+ "max": base.dynamicMax[picker.type],
19286
+ "class": "vc-upload-picker__upload",
19287
+ "onFileBefore": (vFile, fileList) => base.handleFileBefore(vFile, fileList, picker.type),
19288
+ "onFileStart": vFile => base.handleFileStart(vFile, picker.type),
19289
+ "onFileProgress": (e, vFile) => base.handleFileProgress(e, vFile, picker.type),
19290
+ "onFileSuccess": (response, vFile, cycle) => base.handleFileSuccess(response, vFile, cycle, picker.type),
19291
+ "onFileError": (response, vFile, cycle) => base.handleFileError(response, vFile, cycle, picker.type),
19292
+ "onError": e => base.handleError(e, picker.type),
19293
+ "onComplete": response => base.handleFileComplete(response, picker.type)
19294
+ }), {
19295
+ default: () => [slots?.[`${picker.type}-upload`] ? slots[`${picker.type}-upload`]?.() : vue.createVNode("div", {
19296
+ "class": [props.boxClass, 'vc-upload-picker__box'],
19297
+ "onClick": e => handleClick(e, picker.type)
19298
+ }, [vue.createVNode(Icon, {
19299
+ "type": "mini-plus",
19300
+ "class": "vc-upload-picker__plus-icon"
19301
+ }, null), vue.createVNode("span", {
19302
+ "style": "margin-top: 8px"
19303
+ }, [vue.createTextVNode("\u4E0A\u4F20")])])]
19304
+ }), [[vue.vShow, !props.disabled && base.dynamicMax.value[picker.type] >= 1]])]);
19305
+ })]);
18762
19306
  };
18763
19307
  }
18764
19308
  });
@@ -18797,7 +19341,7 @@ exports.Fragment = Fragment;
18797
19341
  exports.HTMLToImage = HTMLToImage;
18798
19342
  exports.Icon = Icon;
18799
19343
  exports.IconManager = IconManager;
18800
- exports.Image = Image;
19344
+ exports.Image = Image$1;
18801
19345
  exports.ImageCrop = ImageCrop;
18802
19346
  exports.ImagePreview = ImagePreview;
18803
19347
  exports.ImageProcessing = ImageProcessing;