@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.js CHANGED
@@ -1,4 +1,4 @@
1
- import { reactive, defineComponent, createVNode, ref, watch, getCurrentInstance, computed, TransitionGroup, Transition as Transition$1, h, inject, onMounted, provide, shallowRef, onUnmounted, withDirectives, vShow, createApp, onBeforeUnmount, Fragment as Fragment$1, Teleport, withModifiers, nextTick, isVNode, onBeforeMount, mergeProps, createTextVNode, toRaw, onUpdated, useAttrs as useAttrs$1 } from 'vue';
1
+ import { reactive, defineComponent, createVNode, ref, watch, getCurrentInstance, computed, TransitionGroup, Transition as Transition$1, h, inject, provide, shallowRef, onMounted, onUnmounted, withDirectives, vShow, createApp, onBeforeUnmount, Fragment as Fragment$1, Teleport, withModifiers, nextTick, isVNode, onBeforeMount, mergeProps, createTextVNode, toRaw, onUpdated, useAttrs as useAttrs$1 } from 'vue';
2
2
  import { debounce, isEqualWith, pick, startCase, throttle, cloneDeep, max, merge as merge$1, isEmpty as isEmpty$1, kebabCase } from 'lodash-es';
3
3
  import { Resize } from '@deot/helper-resize';
4
4
  import { Utils as Utils$1, IS_SERVER as IS_SERVER$1 } from '@deot/vc-shared';
@@ -10,6 +10,7 @@ import { useAttrs, useScrollbar, getInstance } from '@deot/vc-hooks';
10
10
  import { Wheel } from '@deot/helper-wheel';
11
11
  import { Validator } from '@deot/helper-validator';
12
12
  import { Storage } from '@deot/helper-cache';
13
+ import PhotoSwipeLightbox from 'photoswipe/lightbox';
13
14
  import * as Load from '@deot/helper-load';
14
15
  import { style } from '@deot/helper-load';
15
16
  import { Interrupter } from '@deot/helper-scheduler';
@@ -1065,7 +1066,7 @@ const Button = /* @__PURE__ */ defineComponent({
1065
1066
  slots
1066
1067
  }) {
1067
1068
  const vm = getCurrentInstance();
1068
- const hasSlot = ref(true);
1069
+ const isHover = ref(false);
1069
1070
  const isLoading = ref(false);
1070
1071
  const group = inject('vc-button-group', {
1071
1072
  size: 'medium',
@@ -1074,10 +1075,11 @@ const Button = /* @__PURE__ */ defineComponent({
1074
1075
  });
1075
1076
  const classes = computed(() => ({
1076
1077
  'is-circle': props.circle || group.circle,
1077
- 'is-alone': !hasSlot.value,
1078
+ 'is-alone': !slots?.default,
1078
1079
  'is-round': props.round,
1079
1080
  'is-long': props.long,
1080
1081
  'is-disabled': props.disabled,
1082
+ 'is-hover': isHover.value,
1081
1083
  [`is-${props.size}`]: true,
1082
1084
  [`is-${props.type}`]: true
1083
1085
  }));
@@ -1090,9 +1092,6 @@ const Button = /* @__PURE__ */ defineComponent({
1090
1092
  });
1091
1093
  }
1092
1094
  };
1093
- onMounted(() => {
1094
- hasSlot.value = slots.default !== undefined;
1095
- });
1096
1095
  return () => {
1097
1096
  return createVNode(Debounce, {
1098
1097
  "tag": props.tag,
@@ -1103,15 +1102,19 @@ const Button = /* @__PURE__ */ defineComponent({
1103
1102
  "wait": props.wait,
1104
1103
  "disabled": props.disabled,
1105
1104
  "type": props.htmlType,
1106
- "onClick": handleClick
1105
+ "onClick": handleClick,
1106
+ "onMouseenter": () => isHover.value = true,
1107
+ "onMouseleave": () => isHover.value = false
1107
1108
  }, {
1108
1109
  default: () => [props.icon && createVNode(Icon, {
1109
1110
  "type": props.icon
1110
- }, null), isLoading.value && createVNode(Spin, {
1111
+ }, null), slots.icon && slots?.icon?.({
1112
+ hover: isHover.value
1113
+ }), isLoading.value && createVNode(Spin, {
1111
1114
  "size": 12,
1112
1115
  "foreground": props.type === 'default' ? '#ccc' : '#fff',
1113
1116
  "class": "vc-button__loading"
1114
- }, null), hasSlot.value && createVNode("span", null, [slots?.default?.()])]
1117
+ }, null), slots?.default && createVNode("span", null, [slots?.default?.()])]
1115
1118
  });
1116
1119
  };
1117
1120
  }
@@ -1527,7 +1530,8 @@ const useCheckbox = () => {
1527
1530
  "is-indeterminate": props.indeterminate,
1528
1531
  "is-checked": checked.value,
1529
1532
  "is-disabled": props.disabled,
1530
- "is-focus": isFocus.value
1533
+ "is-focus": isFocus.value,
1534
+ "is-error": !!formItem?.message?.value
1531
1535
  };
1532
1536
  });
1533
1537
  watch(
@@ -3178,7 +3182,8 @@ const useInput = (input) => {
3178
3182
  const classes = computed(() => {
3179
3183
  return {
3180
3184
  "is-focus": isFocus.value,
3181
- "is-disabled": props.disabled
3185
+ "is-disabled": props.disabled,
3186
+ "is-error": !!formItem?.message?.value
3182
3187
  };
3183
3188
  });
3184
3189
  const currentMaxlength = computed(() => {
@@ -8479,7 +8484,7 @@ const props$P = {
8479
8484
  },
8480
8485
  width: {
8481
8486
  type: Number,
8482
- default: 300
8487
+ default: 600
8483
8488
  },
8484
8489
  height: {
8485
8490
  type: Number,
@@ -8505,6 +8510,8 @@ const props$P = {
8505
8510
  maskStyle: [Object, String],
8506
8511
  wrapperClass: [Object, String],
8507
8512
  wrapperStyle: [Object, String],
8513
+ contentStyle: [Object, String],
8514
+ contentClass: [Object, String],
8508
8515
  closeWithCancel: {
8509
8516
  type: Boolean,
8510
8517
  default: true
@@ -8664,7 +8671,8 @@ const DrawerView = /* @__PURE__ */ defineComponent({
8664
8671
  "class": "vc-drawer__content-container"
8665
8672
  }, [createVNode(Scroller, {
8666
8673
  "native": false,
8667
- "contentClass": "vc-drawer__content"
8674
+ "contentClass": [props.contentClass, 'vc-drawer__content'],
8675
+ "contentStyle": props.contentStyle
8668
8676
  }, {
8669
8677
  default: () => [typeof props.content === 'string' ? createVNode("div", {
8670
8678
  "innerHTML": props.content
@@ -8696,7 +8704,7 @@ const Drawer$ = new Portal(DrawerView, {
8696
8704
  multiple: true
8697
8705
  });
8698
8706
  const destroy$3 = () => Drawer$.destroy();
8699
- const open$1 = (options) => {
8707
+ const open$2 = (options) => {
8700
8708
  const leaf = Drawer$.popup({
8701
8709
  ...options,
8702
8710
  onFulfilled: options.onClose,
@@ -8706,7 +8714,7 @@ const open$1 = (options) => {
8706
8714
  leaf.wrapper.toggle?.(true);
8707
8715
  return leaf;
8708
8716
  };
8709
- const Drawer = Object.assign(DrawerView, { open: open$1, destroy: destroy$3 });
8717
+ const Drawer = Object.assign(DrawerView, { open: open$2, destroy: destroy$3 });
8710
8718
 
8711
8719
  const MDrawer = Drawer;
8712
8720
  const MDrawerView = DrawerView;
@@ -8805,7 +8813,7 @@ const useForm = (expose, options = {}) => {
8805
8813
  const instance = getCurrentInstance();
8806
8814
  const props = instance.props;
8807
8815
  const fields = [];
8808
- provide("form", {
8816
+ provide("vc-form", {
8809
8817
  props,
8810
8818
  add: (field) => {
8811
8819
  field && fields.push(field);
@@ -8953,10 +8961,12 @@ const props$L = {
8953
8961
  default: false
8954
8962
  },
8955
8963
  labelPosition: {
8956
- type: String,
8957
- default: "right"
8964
+ type: String
8958
8965
  },
8959
- contentStyle: String
8966
+ contentStyle: [Object, String],
8967
+ contentClass: [Object, String],
8968
+ labelStyle: [Object, String],
8969
+ labelClass: [Object, String]
8960
8970
  };
8961
8971
 
8962
8972
  const filterEmpty = (val) => {
@@ -8969,7 +8979,7 @@ const toRules = (rules) => {
8969
8979
  return rules instanceof Array ? rules : rules ? [rules] : [];
8970
8980
  };
8971
8981
  const useFormItem = (expose) => {
8972
- const form = inject("form");
8982
+ const form = inject("vc-form");
8973
8983
  const instance = getCurrentInstance();
8974
8984
  const props = instance.props;
8975
8985
  const { slots } = instance;
@@ -9031,10 +9041,13 @@ const useFormItem = (expose) => {
9031
9041
  });
9032
9042
  const labelStyle = computed(() => {
9033
9043
  const labelWidth = props.labelWidth === 0 || props.labelWidth ? props.labelWidth : isNest.value ? 0 : form.props.labelWidth;
9034
- return {
9035
- width: labelPosition.value !== "top" && labelWidth && labelWidth > 0 ? `${labelWidth}px` : "auto",
9036
- textAlign: labelPosition.value === "top" ? "left" : labelPosition.value
9037
- };
9044
+ return [
9045
+ {
9046
+ width: labelPosition.value !== "top" && labelWidth && labelWidth > 0 ? `${labelWidth}px` : "auto",
9047
+ textAlign: labelPosition.value === "top" ? "left" : labelPosition.value
9048
+ },
9049
+ props.labelStyle
9050
+ ];
9038
9051
  });
9039
9052
  const contentStyle = computed(() => {
9040
9053
  const labelWidth = props.labelWidth === 0 || props.labelWidth ? props.labelWidth : form.props.labelWidth;
@@ -9246,14 +9259,14 @@ const FormItem = /* @__PURE__ */ defineComponent({
9246
9259
  })];
9247
9260
  return createVNode("div", {
9248
9261
  "class": ['vc-form-item', classes.value]
9249
- }, [createVNode("div", {
9262
+ }, [(label || slots.label) && createVNode("div", {
9250
9263
  "style": labelStyle.value,
9251
- "class": "vc-form-item__label",
9264
+ "class": ['vc-form-item__label', props.labelClass],
9252
9265
  "for": labelFor
9253
9266
  }, [createVNode("label", null, [label || slots.label?.()])]), createVNode("div", {
9254
9267
  "class": "vc-form-item__wrapper"
9255
9268
  }, [createVNode("div", {
9256
- "class": "vc-form-item__content",
9269
+ "class": ['vc-form-item__content', props.contentClass],
9257
9270
  "style": contentStyle.value
9258
9271
  }, [slots.default?.(), slots.error ? slots.error({
9259
9272
  show: showError.value,
@@ -9479,7 +9492,7 @@ const ObjectFit = {
9479
9492
  FILL: 'fill',
9480
9493
  SCALE_DOWN: 'scale-down'
9481
9494
  };
9482
- const Image = /* @__PURE__ */ defineComponent({
9495
+ const Image$1 = /* @__PURE__ */ defineComponent({
9483
9496
  name: COMPONENT_NAME$W,
9484
9497
  inheritAttrs: false,
9485
9498
  props: props$H,
@@ -9674,7 +9687,7 @@ const Image = /* @__PURE__ */ defineComponent({
9674
9687
  }
9675
9688
  });
9676
9689
 
9677
- const MImage = Image;
9690
+ const MImage = Image$1;
9678
9691
 
9679
9692
  const props$G = {
9680
9693
  tag: {
@@ -9712,7 +9725,7 @@ const props$F = {
9712
9725
  /** @jsxImportSource vue */
9713
9726
 
9714
9727
  const COMPONENT_NAME$U = 'vc-image-preview';
9715
- const ImagePreview = /* @__PURE__ */ defineComponent({
9728
+ const ImagePreview$1 = /* @__PURE__ */ defineComponent({
9716
9729
  name: COMPONENT_NAME$U,
9717
9730
  props: props$F,
9718
9731
  setup(props, {
@@ -9726,6 +9739,81 @@ const ImagePreview = /* @__PURE__ */ defineComponent({
9726
9739
  }
9727
9740
  });
9728
9741
 
9742
+ const MAX_WIDTH = window.innerWidth;
9743
+ const MAX_HEIGHT = window.innerHeight;
9744
+ const getFitSize = (src) => {
9745
+ return new Promise((resolve) => {
9746
+ const img = new Image();
9747
+ let width;
9748
+ let height;
9749
+ img.onload = () => {
9750
+ const owidth = img.naturalWidth || img.width;
9751
+ const oheight = img.naturalHeight || img.height;
9752
+ if (owidth > oheight) {
9753
+ width = Math.min(MAX_WIDTH, owidth);
9754
+ height = width / owidth * oheight;
9755
+ resolve({
9756
+ width,
9757
+ height
9758
+ });
9759
+ } else {
9760
+ height = Math.min(MAX_HEIGHT, oheight);
9761
+ width = height / oheight * owidth;
9762
+ resolve({
9763
+ width,
9764
+ height
9765
+ });
9766
+ }
9767
+ };
9768
+ img.onerror = () => resolve({});
9769
+ img.src = src;
9770
+ });
9771
+ };
9772
+ const open$1 = async (options) => {
9773
+ const e = VcInstance.globalEvent;
9774
+ const data = options.data.map((i) => {
9775
+ if (typeof i === "string") {
9776
+ return {
9777
+ src: i
9778
+ };
9779
+ }
9780
+ return {
9781
+ ...i,
9782
+ src: i.source || i.src
9783
+ };
9784
+ });
9785
+ for (let i = 0; i < data.length; i++) {
9786
+ if (!data[i].width) {
9787
+ data[i] = {
9788
+ ...data[i],
9789
+ ...await getFitSize(data[i].src)
9790
+ };
9791
+ }
9792
+ }
9793
+ const lightbox = new PhotoSwipeLightbox({
9794
+ pswpModule: () => import('photoswipe'),
9795
+ closeTitle: "关闭(Esc)",
9796
+ zoomTitle: "缩放",
9797
+ arrowPrevTitle: "上一张",
9798
+ arrowNextTitle: "下一张",
9799
+ errorMsg: "网络异常 图片加载失败",
9800
+ indexIndicatorSep: " / ",
9801
+ initialZoomLevel: "fit"
9802
+ });
9803
+ lightbox.init();
9804
+ lightbox.loadAndOpen(
9805
+ options.current || 0,
9806
+ data,
9807
+ // 下面无效,需要给官方支持
9808
+ {
9809
+ x: e?.clientX,
9810
+ y: e?.clientY
9811
+ }
9812
+ );
9813
+ };
9814
+
9815
+ const ImagePreview = Object.assign(ImagePreview$1, { open: open$1 });
9816
+
9729
9817
  const MImagePreview = ImagePreview;
9730
9818
 
9731
9819
  const props$E = {
@@ -10286,10 +10374,14 @@ const props$z = {
10286
10374
  validator: (v) => /(small|medium|large)/.test(v),
10287
10375
  default: "small"
10288
10376
  },
10377
+ contentStyle: [Object, String],
10289
10378
  contentClass: [Object, String],
10290
10379
  width: {
10291
10380
  type: Number
10292
10381
  },
10382
+ height: {
10383
+ type: Number
10384
+ },
10293
10385
  mask: {
10294
10386
  type: Boolean,
10295
10387
  default: true
@@ -10328,9 +10420,8 @@ const props$z = {
10328
10420
  type: [String, Boolean],
10329
10421
  default: "取消"
10330
10422
  },
10331
- wrapperStyle: {
10332
- type: [String, Object]
10333
- },
10423
+ wrapperStyle: [String, Object],
10424
+ wrapperClass: [String, Object],
10334
10425
  footer: {
10335
10426
  type: Boolean,
10336
10427
  default: true
@@ -10381,6 +10472,10 @@ const ModalView = /* @__PURE__ */ defineComponent({
10381
10472
  const x = ref(0);
10382
10473
  const y = ref(0);
10383
10474
  const isActive = ref(false);
10475
+
10476
+ // 注: 服务端渲染为0, 在客服端激活前,展示端存在问题【高度不定】
10477
+ const MAX_HEIGHT = IS_SERVER$1 ? 0 : window.innerHeight - 20;
10478
+ const MAX_WIDTH = IS_SERVER$1 ? 0 : window.innerWidth - 20;
10384
10479
  const defaultSize = computed(() => {
10385
10480
  let width = 0;
10386
10481
  let height = 0;
@@ -10399,17 +10494,21 @@ const ModalView = /* @__PURE__ */ defineComponent({
10399
10494
  break;
10400
10495
  }
10401
10496
  return {
10402
- width: props.width || width,
10403
- height
10497
+ width: Math.min(props.width || width, MAX_WIDTH),
10498
+ height: Math.min(props.height || height, MAX_HEIGHT)
10404
10499
  };
10405
10500
  });
10406
10501
  const basicStyle = computed(() => {
10407
- return {
10502
+ const result = {
10408
10503
  width: `${defaultSize.value.width}px`,
10409
- minHeight: `${defaultSize.value.height}px`,
10410
- // 注: 服务端渲染为0, 在客服端激活前,展示端存在问题【高度不定】
10411
- maxHeight: IS_SERVER$1 ? 0 : `${window.innerHeight - 20}px`
10504
+ maxHeight: `${MAX_HEIGHT}px`
10412
10505
  };
10506
+ if (props.height) {
10507
+ result.height = `${defaultSize.value.height}px`;
10508
+ } else {
10509
+ result.minHeight = `${defaultSize.value.height}px`;
10510
+ }
10511
+ return result;
10413
10512
  });
10414
10513
  const draggableStyle = computed(() => {
10415
10514
  if (IS_SERVER$1 || !props.draggable) return {};
@@ -10532,6 +10631,7 @@ const ModalView = /* @__PURE__ */ defineComponent({
10532
10631
  * container在最大值时,需要移除,宽度才会缩回去
10533
10632
  */
10534
10633
  const handleContentResize = () => {
10634
+ if (props.height) return;
10535
10635
  const needRefreshScroller = !!scroller.value.wrapper.style.getPropertyValue('height');
10536
10636
  const needRefreshContainer = !!container.value.style.getPropertyValue('height');
10537
10637
  needRefreshContainer && container.value.style.removeProperty('height');
@@ -10630,7 +10730,7 @@ const ModalView = /* @__PURE__ */ defineComponent({
10630
10730
  "style": [props.wrapperStyle || {}, props.draggable ? {
10631
10731
  top: 0
10632
10732
  } : {}],
10633
- "class": "vc-modal__wrapper",
10733
+ "class": [props.wrapperClass, 'vc-modal__wrapper'],
10634
10734
  "onClick": e => handleClose(e, false)
10635
10735
  }, [createVNode(TransitionScale, {
10636
10736
  "mode": "part",
@@ -10677,7 +10777,8 @@ const ModalView = /* @__PURE__ */ defineComponent({
10677
10777
  "height": isTransitionEnd.value ? row.height : void 0,
10678
10778
  "contentClass": [{
10679
10779
  'is-confirm': props.mode
10680
- }, props.contentClass, 'vc-modal__content']
10780
+ }, props.contentClass, 'vc-modal__content'],
10781
+ "contentStyle": props.contentStyle
10681
10782
  }, {
10682
10783
  default: () => [typeof props.content === 'string' ? createVNode("div", {
10683
10784
  "innerHTML": props.content
@@ -12296,8 +12397,12 @@ const Progress = /* @__PURE__ */ defineComponent({
12296
12397
  setup(props, {
12297
12398
  slots
12298
12399
  }) {
12400
+ const currentPercent = computed(() => {
12401
+ const v = Number(props.percent);
12402
+ return v >= 100 ? 100 : v;
12403
+ });
12299
12404
  const currentStatus = computed(() => {
12300
- if (Number(props.percent) >= 100) {
12405
+ if (currentPercent.value === 100) {
12301
12406
  return 'success';
12302
12407
  }
12303
12408
  return props.status;
@@ -12309,6 +12414,7 @@ const Progress = /* @__PURE__ */ defineComponent({
12309
12414
  const binds = computed(() => {
12310
12415
  return {
12311
12416
  ...props,
12417
+ percent: currentPercent.value,
12312
12418
  status: currentStatus.value,
12313
12419
  color: currentColor.value
12314
12420
  };
@@ -12386,7 +12492,8 @@ const useRadio = () => {
12386
12492
  return {
12387
12493
  "is-checked": checked.value,
12388
12494
  "is-disabled": isDisabled.value,
12389
- "is-focus": isFocus.value
12495
+ "is-focus": isFocus.value,
12496
+ "is-error": !!formItem?.message?.value
12390
12497
  };
12391
12498
  });
12392
12499
  watch(
@@ -12570,8 +12677,7 @@ const RadioGroup = /* @__PURE__ */ defineComponent({
12570
12677
  return () => {
12571
12678
  if (props.fragment) return slots.default?.();
12572
12679
  return createVNode("div", {
12573
- "class": "vc-radio-group",
12574
- "style": classes.value,
12680
+ "class": [classes.value, 'vc-radio-group'],
12575
12681
  "name": props.name
12576
12682
  }, [slots?.default?.()]);
12577
12683
  };
@@ -15616,6 +15722,8 @@ const props$c = {
15616
15722
  stripe: Boolean,
15617
15723
  // 是否带有纵向边框
15618
15724
  border: Boolean,
15725
+ // 是否分割线
15726
+ divider: Boolean,
15619
15727
  // 非常影响表格虚拟滚动的性能,按容器的高度手动优化该值
15620
15728
  // 后续考虑移除,动态计算
15621
15729
  rows: {
@@ -15756,6 +15864,7 @@ const Table = /* @__PURE__ */ defineComponent({
15756
15864
  'vc-table--fit': props.fit,
15757
15865
  'vc-table--striped': props.stripe,
15758
15866
  'vc-table--border': props.border || states.isGroup,
15867
+ 'vc-table--divider': props.border || props.divider,
15759
15868
  'vc-table--group': states.isGroup,
15760
15869
  'vc-table--fluid-height': props.maxHeight,
15761
15870
  'vc-table--scrollable-x': layout.states.scrollX,
@@ -16540,7 +16649,7 @@ const defaultRenderCell = (rowData = {}) => {
16540
16649
  }
16541
16650
  const line = column.line || VcInstance.options.TableColumn?.line;
16542
16651
  if (line && value) {
16543
- const base = rowData.isHead || rowData.isTail ? 30 : 20;
16652
+ const base = rowData.isHead || rowData.isTail ? 36 : 24; // 目前gap是12
16544
16653
  const style = {
16545
16654
  // 目前左右pading为10
16546
16655
  // TODO: 含有border还要-1
@@ -16888,7 +16997,11 @@ const props$a = {
16888
16997
  closable: {
16889
16998
  type: Boolean,
16890
16999
  default: false
16891
- }
17000
+ },
17001
+ barStyle: [Object, String],
17002
+ contentStyle: [Object, String],
17003
+ barClass: [Object, String],
17004
+ contentClass: [Object, String]
16892
17005
  };
16893
17006
 
16894
17007
  const useTabs = (options = {}) => {
@@ -17141,10 +17254,10 @@ const Tabs = /* @__PURE__ */ defineComponent({
17141
17254
  "class": "vc-tabs__extra"
17142
17255
  }, [slots.extra?.()]), createVNode("div", {
17143
17256
  "ref": wrapper,
17144
- "style": {
17257
+ "style": [props.barStyle, {
17145
17258
  padding: tabs.scrollable.value ? '0 24px' : 0
17146
- },
17147
- "class": "vc-tabs__bar"
17259
+ }],
17260
+ "class": [props.barClass, 'vc-tabs__bar']
17148
17261
  }, [tabs.scrollable.value && createVNode(Icon, {
17149
17262
  "class": "vc-tabs__icon is-left",
17150
17263
  "type": "left",
@@ -17184,8 +17297,8 @@ const Tabs = /* @__PURE__ */ defineComponent({
17184
17297
  }, null)]);
17185
17298
  })])])]), createVNode("div", {
17186
17299
  "ref": content,
17187
- "style": tabs.contentStyle.value,
17188
- "class": "vc-tabs__content"
17300
+ "style": [props.contentStyle, tabs.contentStyle.value],
17301
+ "class": [props.contentClass, 'vc-tabs__content']
17189
17302
  }, [slots.default?.()])]);
17190
17303
  };
17191
17304
  }
@@ -17800,7 +17913,8 @@ const useTextarea = (textarea, expose) => {
17800
17913
  const classes = computed(() => {
17801
17914
  return {
17802
17915
  "is-focus": isFocus.value,
17803
- "is-disabled": props.disabled
17916
+ "is-disabled": props.disabled,
17917
+ "is-error": !!formItem?.message?.value
17804
17918
  };
17805
17919
  });
17806
17920
  const listeners = computed(() => {
@@ -18718,10 +18832,342 @@ const Upload = defineComponent({
18718
18832
  const MUpload = Upload;
18719
18833
 
18720
18834
  const props = {
18721
- tag: {
18722
- type: String,
18723
- default: "div"
18724
- }
18835
+ picker: {
18836
+ type: Array,
18837
+ default: () => ["image"]
18838
+ },
18839
+ sortable: {
18840
+ type: Boolean,
18841
+ default: false
18842
+ },
18843
+ mask: {
18844
+ type: Boolean,
18845
+ default: false
18846
+ },
18847
+ /**
18848
+ * vc-upload组件的属性
18849
+ */
18850
+ uploadOptions: {
18851
+ type: Object,
18852
+ default() {
18853
+ return {};
18854
+ }
18855
+ },
18856
+ /**
18857
+ * 数据源['xxx.jpg', ....]
18858
+ */
18859
+ modelValue: {
18860
+ type: [String, Array, Object],
18861
+ // { value: '', label: '' }
18862
+ default: () => []
18863
+ },
18864
+ // 数据字典
18865
+ keyValue: {
18866
+ type: Object,
18867
+ default: () => {
18868
+ return {
18869
+ label: "label",
18870
+ value: "value"
18871
+ };
18872
+ }
18873
+ },
18874
+ output: {
18875
+ type: [String, Function],
18876
+ default: "object",
18877
+ validator: (v) => /(string|object)/.test(v)
18878
+ },
18879
+ /**
18880
+ * 可上传的最大值,跟upload内的Max不同,有可能是对象类型,对应的Upload做限制
18881
+ */
18882
+ max: {
18883
+ type: [Number, Object],
18884
+ default: Number.MAX_SAFE_INTEGER
18885
+ },
18886
+ disabled: {
18887
+ type: Boolean,
18888
+ default: false
18889
+ },
18890
+ /**
18891
+ * 上传成功后对数据的格式化
18892
+ */
18893
+ formatter: Function,
18894
+ // TODO 下面两个重复了,需删除
18895
+ /**
18896
+ * 盒子className
18897
+ */
18898
+ boxClass: String,
18899
+ imagePreviewOptions: {
18900
+ type: Object,
18901
+ default: () => ({})
18902
+ },
18903
+ imageClass: String,
18904
+ videoClass: String,
18905
+ audioClass: String,
18906
+ fileClass: String,
18907
+ compressOptions: {
18908
+ type: Object,
18909
+ default: () => {
18910
+ return {
18911
+ compress: false,
18912
+ // 是否开启图片压缩
18913
+ width: 0,
18914
+ // 图片缩放最大宽度,为0默认源图片宽度
18915
+ height: 0,
18916
+ // 图片缩放最大高度,为0默认源图片高度
18917
+ filetype: "image/jpeg",
18918
+ // 文件类型
18919
+ encoderOptions: 0.92
18920
+ // 在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,使用默认值 0.92
18921
+ };
18922
+ }
18923
+ },
18924
+ showMessage: Boolean,
18925
+ gallery: Boolean
18926
+ };
18927
+
18928
+ const recognizer = (url) => {
18929
+ const reg = /\.(jpe?g|png|gif|bmp|webp|image|heic|mp4|mov|avi|mpg|mpeg|rmvb)/ig;
18930
+ const result = url.match(reg);
18931
+ return result && result.length ? /.(jpe?g|png|gif|bmp|webp|image|heic)/ig.test(result[result.length - 1]) ? "image" : "video" : "file";
18932
+ };
18933
+ const FILE_ACCEPT_MAP = {
18934
+ DOC_ACCEPTS: ".doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document",
18935
+ EXCEL_ACCEPTS: ".csv,.xls,.xlsx,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
18936
+ PPT_ACCEPTS: ".ppt,.pptx,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation",
18937
+ PDF_ACCEPTS: ".pdf,application/pdf",
18938
+ TXT_ACCEPTS: "text/plain",
18939
+ HTML_ACCEPTS: "text/html"
18940
+ };
18941
+
18942
+ const {
18943
+ DOC_ACCEPTS,
18944
+ EXCEL_ACCEPTS,
18945
+ PPT_ACCEPTS,
18946
+ PDF_ACCEPTS,
18947
+ TXT_ACCEPTS,
18948
+ HTML_ACCEPTS
18949
+ } = FILE_ACCEPT_MAP;
18950
+ const usePicker = (expose) => {
18951
+ const instance = getCurrentInstance();
18952
+ const props = instance.props;
18953
+ const { emit } = instance;
18954
+ const formItem = inject("vc-form-item", {});
18955
+ const allowKeepString = computed(() => {
18956
+ return typeof props.modelValue === "string";
18957
+ });
18958
+ const allowKeepObject = computed(() => {
18959
+ const v = props.modelValue;
18960
+ return props.output === "object" && props.max === 1 && !Array.isArray(v) && typeof v === "object";
18961
+ });
18962
+ const currentValue = ref({
18963
+ image: [],
18964
+ video: [],
18965
+ audio: [],
18966
+ file: []
18967
+ });
18968
+ const currentUploadOptions = ref({
18969
+ image: {
18970
+ accept: "image/gif,image/jpeg,image/jpg,image/png",
18971
+ ...props.uploadOptions.image || {}
18972
+ },
18973
+ video: {
18974
+ accept: "video/*",
18975
+ ...props.uploadOptions.video || {}
18976
+ },
18977
+ audio: {
18978
+ accept: "audio/*",
18979
+ ...props.uploadOptions.audio || {}
18980
+ },
18981
+ file: {
18982
+ accept: `${DOC_ACCEPTS},${EXCEL_ACCEPTS},${PPT_ACCEPTS},${PDF_ACCEPTS},${TXT_ACCEPTS},${HTML_ACCEPTS}`,
18983
+ ...props.uploadOptions.file || {}
18984
+ }
18985
+ });
18986
+ const dynamicMax = computed(() => {
18987
+ const image = currentValue.value.image || [];
18988
+ const video = currentValue.value.video || [];
18989
+ const audio = currentValue.value.audio || [];
18990
+ const file = currentValue.value.file || [];
18991
+ const imageCount = image.length || 0;
18992
+ const videoCount = video.length || 0;
18993
+ const audioCount = audio.length || 0;
18994
+ const fileCount = file.length || 0;
18995
+ if (typeof props.max === "number") {
18996
+ const curNum = imageCount + videoCount + audioCount + fileCount;
18997
+ const leftNum = props.max - curNum;
18998
+ return {
18999
+ image: leftNum,
19000
+ video: leftNum,
19001
+ audio: leftNum,
19002
+ file: leftNum
19003
+ };
19004
+ } else if (typeof props.max === "object") {
19005
+ const {
19006
+ image: $image,
19007
+ video: $video,
19008
+ audio: $audio,
19009
+ file: $file
19010
+ } = props.max;
19011
+ const max = {};
19012
+ $image && (max.image = $image - imageCount);
19013
+ $video && (max.video = $video - videoCount);
19014
+ $audio && (max.audio = $audio - audioCount);
19015
+ $file && (max.file = $file - fileCount);
19016
+ return max;
19017
+ }
19018
+ return {};
19019
+ });
19020
+ const sync = () => {
19021
+ let v = props.picker.reduce((pre, cur) => pre.concat(currentValue.value[cur] || []), []).filter((i) => !i.errorFlag).map((i) => {
19022
+ if (props.output === "string") return i[props.keyValue.value];
19023
+ if (typeof props.output === "function") return props.output(i) || i;
19024
+ return i;
19025
+ });
19026
+ if (allowKeepString.value) {
19027
+ v = v.map((i) => i[props.keyValue.value] || i).join(",");
19028
+ } else if (allowKeepObject.value) {
19029
+ v = v[0] || null;
19030
+ }
19031
+ emit("update:modelValue", v);
19032
+ emit("change", v);
19033
+ formItem.change?.(v);
19034
+ };
19035
+ const handleFileBefore = async (vFile, fileList, type) => {
19036
+ if (props?.compressOptions?.compress && type === "image") ;
19037
+ const onFileBefore = instance.vnode.props?.onFileBefore || (() => {
19038
+ });
19039
+ return await onFileBefore(vFile, fileList, type) || vFile;
19040
+ };
19041
+ const handleFileStart = (vFile, type) => {
19042
+ currentValue.value[type].push(vFile);
19043
+ emit("file-start", vFile, type);
19044
+ };
19045
+ const handleFileProgress = (e, vFile, type) => {
19046
+ if (parseInt(e.percent, 10) <= 100) {
19047
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19048
+ if (vFile.uploadId === item.uploadId) {
19049
+ return {
19050
+ ...item,
19051
+ percent: e.percent
19052
+ };
19053
+ }
19054
+ return item;
19055
+ });
19056
+ }
19057
+ };
19058
+ const handleFileSuccess = (response, vFile, cycle, type) => {
19059
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19060
+ if (item.uploadId === vFile.uploadId) {
19061
+ return {
19062
+ type,
19063
+ [props.keyValue.label]: vFile.name,
19064
+ // 外部需要满足response中带source
19065
+ [props.keyValue.value]: response.source
19066
+ };
19067
+ }
19068
+ return item;
19069
+ });
19070
+ emit("file-success", response, vFile, cycle, type);
19071
+ };
19072
+ const handleError = (err, type) => {
19073
+ props.showMessage && err.message && Message.error(err.message);
19074
+ emit("error", err, type);
19075
+ };
19076
+ const handleFileError = (response, vFile, cycle, type) => {
19077
+ currentValue.value[type] = currentValue.value[type].map((item) => {
19078
+ if (item.uploadId === vFile.uploadId) {
19079
+ return {
19080
+ ...item,
19081
+ ...response,
19082
+ // 文件基础信息
19083
+ type,
19084
+ [props.keyValue.label]: vFile.name,
19085
+ errorFlag: (/* @__PURE__ */ new Date()).getTime()
19086
+ };
19087
+ }
19088
+ return item;
19089
+ });
19090
+ emit("file-error", response, vFile, cycle, type);
19091
+ };
19092
+ const handleFileComplete = (response, type) => {
19093
+ sync();
19094
+ emit("complete", response, type);
19095
+ };
19096
+ const handleDelete = async (index, type) => {
19097
+ const onRemoveBefore = instance.vnode.props?.onRemoveBefore || (() => {
19098
+ });
19099
+ await onRemoveBefore(index, type);
19100
+ const target = currentValue.value[type];
19101
+ const item = target[index];
19102
+ if (!item) {
19103
+ console.error("【vc-upload-picker】: 没有找到要删除的元素");
19104
+ return;
19105
+ }
19106
+ if (item.errorFlag) {
19107
+ currentValue.value[type] = target.filter(
19108
+ (it) => it.uploadId != item.uploadId
19109
+ );
19110
+ return;
19111
+ }
19112
+ target.splice(index, 1);
19113
+ sync();
19114
+ };
19115
+ const parseModelValue = (v) => {
19116
+ const initialData = { image: [], video: [], audio: [], file: [] };
19117
+ if (allowKeepString.value) {
19118
+ v = (props.max === 1 ? [v] : v.split(",")).filter((i) => !!i);
19119
+ } else if (allowKeepObject.value) {
19120
+ v = [v].filter((i) => i && !!i[props.keyValue.value]);
19121
+ }
19122
+ if (!Array.isArray(v) || !v.length) return initialData;
19123
+ return v.reduce((pre, cur) => {
19124
+ const value = cur[props.keyValue.value] || (typeof cur === "object" ? "" : cur);
19125
+ const label = cur[props.keyValue.label] || value.replace(/^.*\/([^/]+)$/, "$1");
19126
+ const type = cur.type || (props.picker.length === 1 ? props.picker[0] : recognizer(value));
19127
+ switch (type) {
19128
+ case "image":
19129
+ case "video":
19130
+ case "audio":
19131
+ case "file":
19132
+ pre[type].push({
19133
+ // 文件类型
19134
+ type,
19135
+ // 文件名
19136
+ [props.keyValue.label]: label,
19137
+ // 源文件地址
19138
+ [props.keyValue.value]: value,
19139
+ // 上传进度
19140
+ percent: null,
19141
+ // 错误标记
19142
+ errorFlag: false
19143
+ });
19144
+ return pre;
19145
+ default:
19146
+ return pre;
19147
+ }
19148
+ }, initialData);
19149
+ };
19150
+ watch(
19151
+ () => props.modelValue,
19152
+ (v) => {
19153
+ currentValue.value = parseModelValue(v);
19154
+ },
19155
+ { immediate: true }
19156
+ );
19157
+ expose();
19158
+ return {
19159
+ currentValue,
19160
+ currentUploadOptions,
19161
+ dynamicMax,
19162
+ handleDelete,
19163
+ handleFileBefore,
19164
+ handleFileStart,
19165
+ handleFileProgress,
19166
+ handleFileSuccess,
19167
+ handleFileError,
19168
+ handleError,
19169
+ handleFileComplete
19170
+ };
18725
19171
  };
18726
19172
 
18727
19173
  /** @jsxImportSource vue */
@@ -18730,17 +19176,115 @@ const COMPONENT_NAME = 'vc-upload-picker';
18730
19176
  const UploadPicker = /* @__PURE__ */ defineComponent({
18731
19177
  name: COMPONENT_NAME,
18732
19178
  props: props,
19179
+ emits: ['update:modelValue', 'file-success', 'file-start', 'success', 'error', 'complete', 'change', 'remove-before'],
18733
19180
  setup(props, {
18734
- slots
19181
+ slots,
19182
+ expose
18735
19183
  }) {
19184
+ const instance = getCurrentInstance();
19185
+ const currentPicker = computed(() => {
19186
+ return props.picker.reduce((pre, cur) => {
19187
+ switch (cur) {
19188
+ case 'image':
19189
+ pre.push({
19190
+ type: cur,
19191
+ item: 'div'
19192
+ // item: ImageItem
19193
+ });
19194
+ return pre;
19195
+ case 'video':
19196
+ pre.push({
19197
+ type: cur,
19198
+ item: 'div'
19199
+ // item: VideoItem
19200
+ });
19201
+ return pre;
19202
+ case 'audio':
19203
+ pre.push({
19204
+ type: cur,
19205
+ item: 'div'
19206
+ // item: AudioItem
19207
+ });
19208
+ return pre;
19209
+ case 'file':
19210
+ pre.push({
19211
+ type: cur,
19212
+ item: 'div'
19213
+ // item: FileItem
19214
+ });
19215
+ return pre;
19216
+ default:
19217
+ return pre;
19218
+ }
19219
+ }, []);
19220
+ });
19221
+ const handleClick = (e, type) => {
19222
+ const options = VcInstance.options.UploadPicker || {};
19223
+ if (typeof props.gallery === 'function' || props.gallery && options.gallery) {
19224
+ const fn = typeof props.gallery === 'function' ? props.gallery : options.gallery;
19225
+
19226
+ // 阻止原生事件,如video, file不走gallery, 可以跳过;
19227
+ fn(instance, type) && e.stopPropagation();
19228
+ }
19229
+ };
19230
+ const base = usePicker(expose);
18736
19231
  return () => {
18737
19232
  return createVNode("div", {
18738
19233
  "class": "vc-upload-picker"
18739
- }, [slots?.default?.()]);
19234
+ }, [currentPicker.value.map((picker, $index) => {
19235
+ return createVNode(Fragment$1, {
19236
+ "key": `${picker}-${$index}`
19237
+ }, [base.currentValue.value[picker.type].map((item, index) => {
19238
+ const Item = picker.item;
19239
+ return createVNode(Item, {
19240
+ "key": typeof item === 'object' ? item.uid : item,
19241
+ "it": item,
19242
+ "disabled": props.disabled,
19243
+ "image-preview-options": props.imagePreviewOptions,
19244
+ "imageClass": props.imageClass,
19245
+ "videoClass": props.videoClass,
19246
+ "audioClass": props.audioClass,
19247
+ "fileClass": props.fileClass,
19248
+ "index": index,
19249
+ "data": item,
19250
+ "class": "vc-upload-picker__item",
19251
+ "onDelete": () => base.handleDelete(index, picker.type)
19252
+ }, {
19253
+ default: scopeData => {
19254
+ return slots.default ? slots.default({
19255
+ it: scopeData?.it,
19256
+ current: scopeData?.current,
19257
+ index,
19258
+ name: picker.type
19259
+ }) : scopeData;
19260
+ }
19261
+ });
19262
+ }), withDirectives(createVNode(Upload, mergeProps(base.currentUploadOptions.value[picker.type], {
19263
+ "max": base.dynamicMax[picker.type],
19264
+ "class": "vc-upload-picker__upload",
19265
+ "onFileBefore": (vFile, fileList) => base.handleFileBefore(vFile, fileList, picker.type),
19266
+ "onFileStart": vFile => base.handleFileStart(vFile, picker.type),
19267
+ "onFileProgress": (e, vFile) => base.handleFileProgress(e, vFile, picker.type),
19268
+ "onFileSuccess": (response, vFile, cycle) => base.handleFileSuccess(response, vFile, cycle, picker.type),
19269
+ "onFileError": (response, vFile, cycle) => base.handleFileError(response, vFile, cycle, picker.type),
19270
+ "onError": e => base.handleError(e, picker.type),
19271
+ "onComplete": response => base.handleFileComplete(response, picker.type)
19272
+ }), {
19273
+ default: () => [slots?.[`${picker.type}-upload`] ? slots[`${picker.type}-upload`]?.() : createVNode("div", {
19274
+ "class": [props.boxClass, 'vc-upload-picker__box'],
19275
+ "onClick": e => handleClick(e, picker.type)
19276
+ }, [createVNode(Icon, {
19277
+ "type": "mini-plus",
19278
+ "class": "vc-upload-picker__plus-icon"
19279
+ }, null), createVNode("span", {
19280
+ "style": "margin-top: 8px"
19281
+ }, [createTextVNode("\u4E0A\u4F20")])])]
19282
+ }), [[vShow, !props.disabled && base.dynamicMax.value[picker.type] >= 1]])]);
19283
+ })]);
18740
19284
  };
18741
19285
  }
18742
19286
  });
18743
19287
 
18744
19288
  const MUploadPicker = UploadPicker;
18745
19289
 
18746
- export { ActionSheet, Alert, Artboard, Button, ButtonGroup, Calendar, Card, Carousel, Cascader, Chart, Checkbox, CheckboxGroup, Clipboard, Collapse, CollapseItem, ColorPicker, Countdown, Customer, DatePicker, Debounce, Divider, Drawer, DrawerView, Dropdown, Editor, Expand$1 as Expand, Form, FormItem, Fragment, HTMLToImage, Icon, IconManager, Image, ImageCrop, ImagePreview, ImageProcessing, Input, InputNumber, InputSearch, MList as List, MListItem as ListItem, MActionSheet, MAlert, MArtboard, MButton, MButtonGroup, MCalendar, MCard, MCarousel, MCascader, MChart, MCheckbox, MCheckboxGroup, MClipboard, MCollapse, MCollapseItem, MColorPicker, MCountdown, MCustomer, MDatePicker, Debounce as MDebounce, MDivider, MDrawer, MDrawerView, MDropdown, MEditor, MExpand, MForm, MFormItem, MFragment, MHTMLToImage, MIcon, MImage, MImageCrop, MImagePreview, MImageProcessing, MInput, MInputNumber, MInputSearch, MList, MListItem, MMarquee, MMessage, modal as MModal, MModalView, MNotice, MOption, MPagination, MPicker, MPopconfirm, MPopover, MPopup, MPortal, MPrint, MProgress, MRadio, MRadioGroup, MRate, MRecycleList, MResizer, MScroller, MSelect, MSlider, MSortList, MSpin, MSteps, MSwitch, MTable, MTableColumn, MTabs, MTabsPane, MTag, MText, MTextarea, MTimePicker, MTimeline, MToast, MToastView, MTouch, MTransition, MTransitionCollapse, MTransitionFade, MTransitionScale, MTransitionSlide, MTransitionZoom, MTree, MUpload, MUploadPicker, Marquee, Message, MessageView, Modal, ModalView, Notice, NoticeView, Option$1 as Option, Pagination, Picker, Popconfirm, Popover, Popup, Portal, PortalView, Print, Progress, Radio, RadioGroup, Rate, RecycleList, Resizer, Scroller, ScrollerWheel, Select, Slider, SortList, Spin, Steps, Switch, Table, TableColumn, Tabs, TabsPane, Tag, Text, Textarea, Theme, ThemeImage, ThemeText, ThemeView, TimePicker, Timeline, Toast, ToastView, Touch, Transition, TransitionCollapse, TransitionFade, TransitionScale, TransitionSlide, TransitionZoom, Tree, Upload, UploadPicker, VcError, VcInstance };
19290
+ export { ActionSheet, Alert, Artboard, Button, ButtonGroup, Calendar, Card, Carousel, Cascader, Chart, Checkbox, CheckboxGroup, Clipboard, Collapse, CollapseItem, ColorPicker, Countdown, Customer, DatePicker, Debounce, Divider, Drawer, DrawerView, Dropdown, Editor, Expand$1 as Expand, Form, FormItem, Fragment, HTMLToImage, Icon, IconManager, Image$1 as Image, ImageCrop, ImagePreview, ImageProcessing, Input, InputNumber, InputSearch, MList as List, MListItem as ListItem, MActionSheet, MAlert, MArtboard, MButton, MButtonGroup, MCalendar, MCard, MCarousel, MCascader, MChart, MCheckbox, MCheckboxGroup, MClipboard, MCollapse, MCollapseItem, MColorPicker, MCountdown, MCustomer, MDatePicker, Debounce as MDebounce, MDivider, MDrawer, MDrawerView, MDropdown, MEditor, MExpand, MForm, MFormItem, MFragment, MHTMLToImage, MIcon, MImage, MImageCrop, MImagePreview, MImageProcessing, MInput, MInputNumber, MInputSearch, MList, MListItem, MMarquee, MMessage, modal as MModal, MModalView, MNotice, MOption, MPagination, MPicker, MPopconfirm, MPopover, MPopup, MPortal, MPrint, MProgress, MRadio, MRadioGroup, MRate, MRecycleList, MResizer, MScroller, MSelect, MSlider, MSortList, MSpin, MSteps, MSwitch, MTable, MTableColumn, MTabs, MTabsPane, MTag, MText, MTextarea, MTimePicker, MTimeline, MToast, MToastView, MTouch, MTransition, MTransitionCollapse, MTransitionFade, MTransitionScale, MTransitionSlide, MTransitionZoom, MTree, MUpload, MUploadPicker, Marquee, Message, MessageView, Modal, ModalView, Notice, NoticeView, Option$1 as Option, Pagination, Picker, Popconfirm, Popover, Popup, Portal, PortalView, Print, Progress, Radio, RadioGroup, Rate, RecycleList, Resizer, Scroller, ScrollerWheel, Select, Slider, SortList, Spin, Steps, Switch, Table, TableColumn, Tabs, TabsPane, Tag, Text, Textarea, Theme, ThemeImage, ThemeText, ThemeView, TimePicker, Timeline, Toast, ToastView, Touch, Transition, TransitionCollapse, TransitionFade, TransitionScale, TransitionSlide, TransitionZoom, Tree, Upload, UploadPicker, VcError, VcInstance };