fantasia-ui 0.2.58 → 0.2.59

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.
@@ -6696,16 +6696,16 @@ to {
6696
6696
  color: #94a3b8;
6697
6697
  font-weight: 700;
6698
6698
  background: rgba(248, 250, 252, 0.5);
6699
- }.f-upload[data-v-6bdc061f] {
6699
+ }.f-upload[data-v-04ecda2b] {
6700
6700
  display: flex;
6701
6701
  flex-direction: column;
6702
6702
  gap: 0.5rem;
6703
6703
  }
6704
- .f-upload__trigger[data-v-6bdc061f] {
6704
+ .f-upload__trigger[data-v-04ecda2b] {
6705
6705
  display: inline-block;
6706
6706
  cursor: pointer;
6707
6707
  }
6708
- .f-upload__zone[data-v-6bdc061f] {
6708
+ .f-upload__zone[data-v-04ecda2b] {
6709
6709
  height: 11rem;
6710
6710
  width: 100%;
6711
6711
  border: 2px dashed #e2e8f0;
@@ -6718,30 +6718,35 @@ to {
6718
6718
  text-align: center;
6719
6719
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
6720
6720
  }
6721
- .f-upload__zone[data-v-6bdc061f]:hover {
6721
+ .f-upload__zone[data-v-04ecda2b]:hover {
6722
6722
  border-color: #818cf8;
6723
6723
  background: rgba(248, 250, 252, 0.3);
6724
6724
  }
6725
- .f-upload--drag .f-upload__zone[data-v-6bdc061f] {
6725
+ .f-upload--drag .f-upload__zone[data-v-04ecda2b] {
6726
6726
  border-color: #6366f1;
6727
6727
  background: rgba(238, 242, 255, 0.2);
6728
6728
  }
6729
- .f-upload__input[data-v-6bdc061f] {
6729
+ .f-upload__input[data-v-04ecda2b] {
6730
6730
  display: none;
6731
6731
  }
6732
- .f-upload__title[data-v-6bdc061f] {
6732
+ .f-upload__title[data-v-04ecda2b] {
6733
6733
  font-size: 0.75rem;
6734
6734
  font-weight: 700;
6735
6735
  color: #334155;
6736
6736
  }
6737
- .f-upload__hint[data-v-6bdc061f] {
6737
+ .f-upload__hint[data-v-04ecda2b] {
6738
6738
  font-size: 0.625rem;
6739
6739
  font-weight: 700;
6740
6740
  color: #94a3b8;
6741
6741
  margin-top: 0.25rem;
6742
6742
  line-height: 1.4;
6743
6743
  }
6744
- .f-upload__file-item[data-v-6bdc061f] {
6744
+ .f-upload__list[data-v-04ecda2b] {
6745
+ display: flex;
6746
+ flex-direction: column;
6747
+ gap: 0.4rem;
6748
+ }
6749
+ .f-upload__file-item[data-v-04ecda2b] {
6745
6750
  display: flex;
6746
6751
  align-items: center;
6747
6752
  gap: 0.5rem;
@@ -6752,18 +6757,30 @@ to {
6752
6757
  font-size: 0.75rem;
6753
6758
  color: #4338ca;
6754
6759
  }
6755
- .f-upload__file-info[data-v-6bdc061f] {
6760
+ .f-upload__file-item--error[data-v-04ecda2b] {
6761
+ background: rgba(254, 226, 226, 0.5);
6762
+ border-color: #fecaca;
6763
+ color: #b91c1c;
6764
+ }
6765
+ .f-upload__file-item--success[data-v-04ecda2b] {
6766
+ border-color: #bbf7d0;
6767
+ }
6768
+ .f-upload__file-info[data-v-04ecda2b] {
6756
6769
  display: flex;
6757
6770
  flex-direction: column;
6758
6771
  gap: 0.25rem;
6759
6772
  flex: 1;
6760
6773
  min-width: 0;
6761
6774
  }
6762
- .f-upload__file-name[data-v-6bdc061f] {
6775
+ .f-upload__file-name[data-v-04ecda2b] {
6763
6776
  font-weight: 600;
6764
6777
  overflow: hidden;
6765
6778
  text-overflow: ellipsis;
6766
6779
  white-space: nowrap;
6780
+ }
6781
+ .f-upload__file-error[data-v-04ecda2b] {
6782
+ font-size: 0.6875rem;
6783
+ color: #dc2626;
6767
6784
  }.f-button-group[data-v-62561657] {
6768
6785
  display: inline-flex;
6769
6786
  vertical-align: middle;
@@ -38,7 +38,7 @@ var __async = (__this, __arguments, generator) => {
38
38
  });
39
39
  };
40
40
  import { defineComponent, computed, createElementBlock, openBlock, normalizeStyle, unref, renderSlot, normalizeClass, createCommentVNode, Fragment, renderList, createElementVNode, toDisplayString, ref, watch, createVNode, withDirectives, Transition, withCtx, vShow, mergeModels, useModel, inject, createBlock, resolveDynamicComponent, nextTick, onUnmounted, Teleport, withModifiers, withKeys, isRef, vModelText, createTextVNode, TransitionGroup, useSlots, provide, onMounted, readonly, onBeforeUnmount, reactive, toRefs, createStaticVNode, h, render } from "vue";
41
- import { CloseCircleOutline, EyeOutline, EyeOffOutline, StarOutline, StarHalf, Star, ChevronDownOutline, ChevronBackOutline, EllipsisHorizontal, ChevronForwardOutline, CloseCircle, Close, Person, InformationCircleOutline, WarningOutline, AlertCircleOutline, CheckmarkCircleOutline, CloudUpload, DocumentOutline, RemoveOutline, AddOutline, ChevronUpOutline, InformationCircle, AlertCircle, CheckmarkCircle, CloseOutline, Warning } from "@vicons/ionicons5";
41
+ import { CloseCircleOutline, EyeOutline, EyeOffOutline, StarOutline, StarHalf, Star, ChevronDownOutline, ChevronBackOutline, EllipsisHorizontal, ChevronForwardOutline, CloseCircle, Close, Person, InformationCircleOutline, WarningOutline, AlertCircleOutline, CheckmarkCircleOutline, CloudUpload, CheckmarkCircle, AlertCircle, DocumentOutline, Reload, RemoveOutline, AddOutline, ChevronUpOutline, InformationCircle, CloseOutline, Warning } from "@vicons/ionicons5";
42
42
  import { useEventListener } from "@vueuse/core";
43
43
  const _sfc_main$R = /* @__PURE__ */ defineComponent({
44
44
  __name: "FIcon",
@@ -721,7 +721,7 @@ const _hoisted_7$5 = {
721
721
  key: 0,
722
722
  class: "page-btn ellipsis"
723
723
  };
724
- const _hoisted_8$3 = ["onClick"];
724
+ const _hoisted_8$4 = ["onClick"];
725
725
  const _hoisted_9$3 = {
726
726
  key: 5,
727
727
  class: "f-pagination__jump"
@@ -817,7 +817,7 @@ const _sfc_main$K = /* @__PURE__ */ defineComponent({
817
817
  key: 1,
818
818
  class: normalizeClass(["page-btn", { active: currentPage.value === page }]),
819
819
  onClick: ($event) => go(page)
820
- }, toDisplayString(page), 11, _hoisted_8$3))
820
+ }, toDisplayString(page), 11, _hoisted_8$4))
821
821
  ], 64);
822
822
  }), 128))
823
823
  ])) : item === "next" ? (openBlock(), createElementBlock("button", {
@@ -881,7 +881,7 @@ const _hoisted_7$4 = {
881
881
  key: 0,
882
882
  class: "f-card__header-extra"
883
883
  };
884
- const _hoisted_8$2 = {
884
+ const _hoisted_8$3 = {
885
885
  key: 1,
886
886
  class: "f-card__cover"
887
887
  };
@@ -928,7 +928,7 @@ const _sfc_main$J = /* @__PURE__ */ defineComponent({
928
928
  renderSlot(_ctx.$slots, "header-extra", {}, void 0, true)
929
929
  ])) : createCommentVNode("", true)
930
930
  ])) : createCommentVNode("", true),
931
- _ctx.$slots.cover ? (openBlock(), createElementBlock("div", _hoisted_8$2, [
931
+ _ctx.$slots.cover ? (openBlock(), createElementBlock("div", _hoisted_8$3, [
932
932
  renderSlot(_ctx.$slots, "cover", {}, void 0, true)
933
933
  ])) : createCommentVNode("", true),
934
934
  createElementVNode("div", _hoisted_9$2, [
@@ -2382,7 +2382,7 @@ const _hoisted_4$a = ["onClick"];
2382
2382
  const _hoisted_5$7 = { class: "f-transfer__actions" };
2383
2383
  const _hoisted_6$6 = ["disabled"];
2384
2384
  const _hoisted_7$3 = ["disabled"];
2385
- const _hoisted_8$1 = { class: "f-transfer__panel" };
2385
+ const _hoisted_8$2 = { class: "f-transfer__panel" };
2386
2386
  const _hoisted_9$1 = { class: "f-transfer__header" };
2387
2387
  const _hoisted_10$1 = { class: "f-transfer__list" };
2388
2388
  const _hoisted_11$1 = ["onClick"];
@@ -2468,7 +2468,7 @@ const _sfc_main$k = /* @__PURE__ */ defineComponent({
2468
2468
  onClick: _cache[1] || (_cache[1] = ($event) => moveSelected("left"))
2469
2469
  }, " ← ", 8, _hoisted_7$3)
2470
2470
  ]),
2471
- createElementVNode("div", _hoisted_8$1, [
2471
+ createElementVNode("div", _hoisted_8$2, [
2472
2472
  createElementVNode("div", _hoisted_9$1, toDisplayString(_ctx.rightTitle), 1),
2473
2473
  createElementVNode("ul", _hoisted_10$1, [
2474
2474
  (openBlock(true), createElementBlock(Fragment, null, renderList(unref(rightList), (item) => {
@@ -2805,7 +2805,7 @@ const _hoisted_6$4 = {
2805
2805
  class: "f-table__th f-table__th--index"
2806
2806
  };
2807
2807
  const _hoisted_7$2 = { class: "f-table__th-inner" };
2808
- const _hoisted_8 = { class: "f-table__body" };
2808
+ const _hoisted_8$1 = { class: "f-table__body" };
2809
2809
  const _hoisted_9 = {
2810
2810
  key: 0,
2811
2811
  class: "f-table__spacer"
@@ -2928,7 +2928,7 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
2928
2928
  }), 128))
2929
2929
  ])
2930
2930
  ]),
2931
- createElementVNode("tbody", _hoisted_8, [
2931
+ createElementVNode("tbody", _hoisted_8$1, [
2932
2932
  unref(offsetTop) > 0 ? (openBlock(), createElementBlock("tr", _hoisted_9, [
2933
2933
  createElementVNode("td", {
2934
2934
  colspan: unref(colSpan),
@@ -2995,34 +2995,96 @@ const _sfc_main$h = /* @__PURE__ */ defineComponent({
2995
2995
  });
2996
2996
  const FTable = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-01724962"]]);
2997
2997
  const _hoisted_1$c = { class: "f-upload" };
2998
- const _hoisted_2$a = ["accept"];
2998
+ const _hoisted_2$a = ["accept", "multiple"];
2999
2999
  const _hoisted_3$a = { class: "f-upload__zone" };
3000
3000
  const _hoisted_4$7 = { class: "f-upload__hint" };
3001
3001
  const _hoisted_5$4 = {
3002
3002
  key: 0,
3003
- class: "f-upload__file-item"
3003
+ class: "f-upload__list"
3004
3004
  };
3005
3005
  const _hoisted_6$3 = { class: "f-upload__file-info" };
3006
3006
  const _hoisted_7$1 = { class: "f-upload__file-name" };
3007
+ const _hoisted_8 = {
3008
+ key: 1,
3009
+ class: "f-upload__file-error"
3010
+ };
3007
3011
  const _sfc_main$g = /* @__PURE__ */ defineComponent({
3008
3012
  __name: "FUpload",
3009
3013
  props: /* @__PURE__ */ mergeModels({
3010
3014
  accept: {},
3015
+ hint: { default: "支持 .xlsx .xls .json .txt" },
3011
3016
  limit: {},
3012
- progress: {},
3013
- hint: { default: "支持 .xlsx .xls .json .txt" }
3017
+ autoUpload: { type: Boolean, default: true },
3018
+ beforeUpload: {},
3019
+ httpRequest: {}
3014
3020
  }, {
3015
- "file": { default: "" },
3016
- "fileModifiers": {}
3021
+ "fileList": { default: () => [] },
3022
+ "fileListModifiers": {}
3017
3023
  }),
3018
- emits: /* @__PURE__ */ mergeModels(["change", "exceed"], ["update:file"]),
3019
- setup(__props, { emit: __emit }) {
3020
- const file = useModel(__props, "file");
3024
+ emits: /* @__PURE__ */ mergeModels(["change", "progress", "success", "error", "remove", "exceed"], ["update:fileList"]),
3025
+ setup(__props, { expose: __expose, emit: __emit }) {
3026
+ const fileList = useModel(__props, "fileList");
3027
+ const props = __props;
3021
3028
  const emit = __emit;
3022
3029
  const dragActive = ref(false);
3023
- const isCloseHover = ref(false);
3024
- const clearFile = () => {
3025
- file.value = "";
3030
+ const hoveredUid = ref(null);
3031
+ const genUid = () => `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
3032
+ const findItem = (uid) => fileList.value.find((f) => f.uid === uid);
3033
+ const updateItem = (uid, patch) => {
3034
+ const item = findItem(uid);
3035
+ if (item) Object.assign(item, patch);
3036
+ return item;
3037
+ };
3038
+ const submitUpload = (item) => __async(null, null, function* () {
3039
+ if (!props.httpRequest) return;
3040
+ updateItem(item.uid, { status: "uploading", percentage: 0 });
3041
+ try {
3042
+ const response = yield props.httpRequest(item.raw, (percent) => {
3043
+ const current = updateItem(item.uid, { percentage: percent });
3044
+ if (current) emit("progress", current);
3045
+ });
3046
+ const done = updateItem(item.uid, { status: "success", percentage: 100, response });
3047
+ if (done) emit("success", done, response);
3048
+ } catch (err) {
3049
+ const failed = updateItem(item.uid, { status: "error", errorMessage: (err == null ? void 0 : err.message) || "上传失败" });
3050
+ if (failed) emit("error", failed, err);
3051
+ }
3052
+ });
3053
+ const addFiles = (files) => __async(null, null, function* () {
3054
+ if (!files.length) return;
3055
+ const remaining = props.limit != null ? props.limit - fileList.value.length : Infinity;
3056
+ if (remaining <= 0) {
3057
+ emit("exceed", files);
3058
+ return;
3059
+ }
3060
+ const accepted = files.slice(0, remaining);
3061
+ const rejected = files.slice(remaining);
3062
+ if (rejected.length) emit("exceed", rejected);
3063
+ for (const raw of accepted) {
3064
+ if (props.beforeUpload) {
3065
+ const ok = yield props.beforeUpload(raw);
3066
+ if (!ok) continue;
3067
+ }
3068
+ const item = {
3069
+ uid: genUid(),
3070
+ name: raw.name,
3071
+ status: "ready",
3072
+ percentage: 0,
3073
+ raw
3074
+ };
3075
+ fileList.value = [...fileList.value, item];
3076
+ emit("change", item);
3077
+ if (props.autoUpload) submitUpload(item);
3078
+ }
3079
+ });
3080
+ const retryUpload = (uid) => {
3081
+ const item = findItem(uid);
3082
+ if (item) submitUpload(item);
3083
+ };
3084
+ const removeFile = (uid) => {
3085
+ const item = findItem(uid);
3086
+ fileList.value = fileList.value.filter((f) => f.uid !== uid);
3087
+ if (item) emit("remove", item);
3026
3088
  };
3027
3089
  const onDragOver = (e) => {
3028
3090
  e.preventDefault();
@@ -3036,18 +3098,17 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
3036
3098
  var _a, _b;
3037
3099
  e.preventDefault();
3038
3100
  dragActive.value = false;
3039
- const f = (_b = (_a = e.dataTransfer) == null ? void 0 : _a.files) == null ? void 0 : _b[0];
3040
- if (!f) return;
3041
- emit("change", f);
3101
+ const files = Array.from((_b = (_a = e.dataTransfer) == null ? void 0 : _a.files) != null ? _b : []);
3102
+ addFiles(files);
3042
3103
  };
3043
3104
  const onFileChange = (e) => {
3044
3105
  var _a;
3045
3106
  const input = e.target;
3046
- const f = (_a = input.files) == null ? void 0 : _a[0];
3047
- if (!f) return;
3048
- emit("change", f);
3107
+ const files = Array.from((_a = input.files) != null ? _a : []);
3049
3108
  input.value = "";
3109
+ addFiles(files);
3050
3110
  };
3111
+ __expose({ retryUpload, removeFile, submitAll: () => fileList.value.filter((f) => f.status === "ready").forEach(submitUpload) });
3051
3112
  return (_ctx, _cache) => {
3052
3113
  return openBlock(), createElementBlock("div", _hoisted_1$c, [
3053
3114
  createElementVNode("label", {
@@ -3059,6 +3120,7 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
3059
3120
  createElementVNode("input", {
3060
3121
  type: "file",
3061
3122
  accept: _ctx.accept,
3123
+ multiple: _ctx.limit == null || _ctx.limit > 1,
3062
3124
  class: "f-upload__input",
3063
3125
  onChange: onFileChange
3064
3126
  }, null, 40, _hoisted_2$a),
@@ -3070,43 +3132,60 @@ const _sfc_main$g = /* @__PURE__ */ defineComponent({
3070
3132
  ]),
3071
3133
  _: 1
3072
3134
  }),
3073
- _cache[2] || (_cache[2] = createElementVNode("span", { class: "f-upload__title" }, "点击或拖拽文件至此处", -1)),
3135
+ _cache[1] || (_cache[1] = createElementVNode("span", { class: "f-upload__title" }, "点击或拖拽文件至此处", -1)),
3074
3136
  createElementVNode("span", _hoisted_4$7, toDisplayString(_ctx.hint), 1)
3075
3137
  ])
3076
3138
  ], true)
3077
3139
  ], 34),
3078
- file.value ? (openBlock(), createElementBlock("div", _hoisted_5$4, [
3079
- createVNode(FIcon, null, {
3080
- default: withCtx(() => [
3081
- createVNode(unref(DocumentOutline))
3082
- ]),
3083
- _: 1
3084
- }),
3085
- createElementVNode("div", _hoisted_6$3, [
3086
- createElementVNode("span", _hoisted_7$1, toDisplayString(file.value), 1),
3087
- _ctx.progress != null ? (openBlock(), createBlock(FProgress, {
3088
- key: 0,
3089
- percent: _ctx.progress,
3090
- "show-text": ""
3091
- }, null, 8, ["percent"])) : createCommentVNode("", true)
3092
- ]),
3093
- createVNode(FIcon, {
3094
- onClick: withModifiers(clearFile, ["stop"]),
3095
- onMouseenter: _cache[0] || (_cache[0] = ($event) => isCloseHover.value = true),
3096
- onMouseleave: _cache[1] || (_cache[1] = ($event) => isCloseHover.value = false),
3097
- style: { "cursor": "pointer", "transition": "color 0.2s" }
3098
- }, {
3099
- default: withCtx(() => [
3100
- (openBlock(), createBlock(resolveDynamicComponent(unref(isCloseHover) ? unref(CloseCircle) : unref(Close))))
3101
- ]),
3102
- _: 1
3103
- })
3140
+ fileList.value.length ? (openBlock(), createElementBlock("div", _hoisted_5$4, [
3141
+ (openBlock(true), createElementBlock(Fragment, null, renderList(fileList.value, (item) => {
3142
+ return openBlock(), createElementBlock("div", {
3143
+ key: item.uid,
3144
+ class: normalizeClass(["f-upload__file-item", `f-upload__file-item--${item.status}`])
3145
+ }, [
3146
+ createVNode(FIcon, null, {
3147
+ default: withCtx(() => [
3148
+ (openBlock(), createBlock(resolveDynamicComponent(item.status === "success" ? unref(CheckmarkCircle) : item.status === "error" ? unref(AlertCircle) : unref(DocumentOutline))))
3149
+ ]),
3150
+ _: 2
3151
+ }, 1024),
3152
+ createElementVNode("div", _hoisted_6$3, [
3153
+ createElementVNode("span", _hoisted_7$1, toDisplayString(item.name), 1),
3154
+ item.status === "uploading" ? (openBlock(), createBlock(FProgress, {
3155
+ key: 0,
3156
+ percent: item.percentage,
3157
+ "show-text": ""
3158
+ }, null, 8, ["percent"])) : item.status === "error" ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(item.errorMessage), 1)) : createCommentVNode("", true)
3159
+ ]),
3160
+ item.status === "error" ? (openBlock(), createBlock(FIcon, {
3161
+ key: 0,
3162
+ onClick: withModifiers(($event) => retryUpload(item.uid), ["stop"]),
3163
+ style: { "cursor": "pointer" }
3164
+ }, {
3165
+ default: withCtx(() => [
3166
+ createVNode(unref(Reload))
3167
+ ]),
3168
+ _: 2
3169
+ }, 1032, ["onClick"])) : createCommentVNode("", true),
3170
+ createVNode(FIcon, {
3171
+ onClick: withModifiers(($event) => removeFile(item.uid), ["stop"]),
3172
+ onMouseenter: ($event) => hoveredUid.value = item.uid,
3173
+ onMouseleave: _cache[0] || (_cache[0] = ($event) => hoveredUid.value = null),
3174
+ style: { "cursor": "pointer", "transition": "color 0.2s" }
3175
+ }, {
3176
+ default: withCtx(() => [
3177
+ (openBlock(), createBlock(resolveDynamicComponent(unref(hoveredUid) === item.uid ? unref(CloseCircle) : unref(Close))))
3178
+ ]),
3179
+ _: 2
3180
+ }, 1032, ["onClick", "onMouseenter"])
3181
+ ], 2);
3182
+ }), 128))
3104
3183
  ])) : createCommentVNode("", true)
3105
3184
  ]);
3106
3185
  };
3107
3186
  }
3108
3187
  });
3109
- const FUpload = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-6bdc061f"]]);
3188
+ const FUpload = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-04ecda2b"]]);
3110
3189
  const _sfc_main$f = /* @__PURE__ */ defineComponent({
3111
3190
  __name: "FButtonGroup",
3112
3191
  props: {
@@ -722,7 +722,7 @@ var __async = (__this, __arguments, generator) => {
722
722
  key: 0,
723
723
  class: "page-btn ellipsis"
724
724
  };
725
- const _hoisted_8$3 = ["onClick"];
725
+ const _hoisted_8$4 = ["onClick"];
726
726
  const _hoisted_9$3 = {
727
727
  key: 5,
728
728
  class: "f-pagination__jump"
@@ -818,7 +818,7 @@ var __async = (__this, __arguments, generator) => {
818
818
  key: 1,
819
819
  class: vue.normalizeClass(["page-btn", { active: currentPage.value === page }]),
820
820
  onClick: ($event) => go(page)
821
- }, vue.toDisplayString(page), 11, _hoisted_8$3))
821
+ }, vue.toDisplayString(page), 11, _hoisted_8$4))
822
822
  ], 64);
823
823
  }), 128))
824
824
  ])) : item === "next" ? (vue.openBlock(), vue.createElementBlock("button", {
@@ -882,7 +882,7 @@ var __async = (__this, __arguments, generator) => {
882
882
  key: 0,
883
883
  class: "f-card__header-extra"
884
884
  };
885
- const _hoisted_8$2 = {
885
+ const _hoisted_8$3 = {
886
886
  key: 1,
887
887
  class: "f-card__cover"
888
888
  };
@@ -929,7 +929,7 @@ var __async = (__this, __arguments, generator) => {
929
929
  vue.renderSlot(_ctx.$slots, "header-extra", {}, void 0, true)
930
930
  ])) : vue.createCommentVNode("", true)
931
931
  ])) : vue.createCommentVNode("", true),
932
- _ctx.$slots.cover ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8$2, [
932
+ _ctx.$slots.cover ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_8$3, [
933
933
  vue.renderSlot(_ctx.$slots, "cover", {}, void 0, true)
934
934
  ])) : vue.createCommentVNode("", true),
935
935
  vue.createElementVNode("div", _hoisted_9$2, [
@@ -2383,7 +2383,7 @@ var __async = (__this, __arguments, generator) => {
2383
2383
  const _hoisted_5$7 = { class: "f-transfer__actions" };
2384
2384
  const _hoisted_6$6 = ["disabled"];
2385
2385
  const _hoisted_7$3 = ["disabled"];
2386
- const _hoisted_8$1 = { class: "f-transfer__panel" };
2386
+ const _hoisted_8$2 = { class: "f-transfer__panel" };
2387
2387
  const _hoisted_9$1 = { class: "f-transfer__header" };
2388
2388
  const _hoisted_10$1 = { class: "f-transfer__list" };
2389
2389
  const _hoisted_11$1 = ["onClick"];
@@ -2469,7 +2469,7 @@ var __async = (__this, __arguments, generator) => {
2469
2469
  onClick: _cache[1] || (_cache[1] = ($event) => moveSelected("left"))
2470
2470
  }, " ← ", 8, _hoisted_7$3)
2471
2471
  ]),
2472
- vue.createElementVNode("div", _hoisted_8$1, [
2472
+ vue.createElementVNode("div", _hoisted_8$2, [
2473
2473
  vue.createElementVNode("div", _hoisted_9$1, vue.toDisplayString(_ctx.rightTitle), 1),
2474
2474
  vue.createElementVNode("ul", _hoisted_10$1, [
2475
2475
  (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(vue.unref(rightList), (item) => {
@@ -2806,7 +2806,7 @@ var __async = (__this, __arguments, generator) => {
2806
2806
  class: "f-table__th f-table__th--index"
2807
2807
  };
2808
2808
  const _hoisted_7$2 = { class: "f-table__th-inner" };
2809
- const _hoisted_8 = { class: "f-table__body" };
2809
+ const _hoisted_8$1 = { class: "f-table__body" };
2810
2810
  const _hoisted_9 = {
2811
2811
  key: 0,
2812
2812
  class: "f-table__spacer"
@@ -2929,7 +2929,7 @@ var __async = (__this, __arguments, generator) => {
2929
2929
  }), 128))
2930
2930
  ])
2931
2931
  ]),
2932
- vue.createElementVNode("tbody", _hoisted_8, [
2932
+ vue.createElementVNode("tbody", _hoisted_8$1, [
2933
2933
  vue.unref(offsetTop) > 0 ? (vue.openBlock(), vue.createElementBlock("tr", _hoisted_9, [
2934
2934
  vue.createElementVNode("td", {
2935
2935
  colspan: vue.unref(colSpan),
@@ -2996,34 +2996,96 @@ var __async = (__this, __arguments, generator) => {
2996
2996
  });
2997
2997
  const FTable = /* @__PURE__ */ _export_sfc(_sfc_main$h, [["__scopeId", "data-v-01724962"]]);
2998
2998
  const _hoisted_1$c = { class: "f-upload" };
2999
- const _hoisted_2$a = ["accept"];
2999
+ const _hoisted_2$a = ["accept", "multiple"];
3000
3000
  const _hoisted_3$a = { class: "f-upload__zone" };
3001
3001
  const _hoisted_4$7 = { class: "f-upload__hint" };
3002
3002
  const _hoisted_5$4 = {
3003
3003
  key: 0,
3004
- class: "f-upload__file-item"
3004
+ class: "f-upload__list"
3005
3005
  };
3006
3006
  const _hoisted_6$3 = { class: "f-upload__file-info" };
3007
3007
  const _hoisted_7$1 = { class: "f-upload__file-name" };
3008
+ const _hoisted_8 = {
3009
+ key: 1,
3010
+ class: "f-upload__file-error"
3011
+ };
3008
3012
  const _sfc_main$g = /* @__PURE__ */ vue.defineComponent({
3009
3013
  __name: "FUpload",
3010
3014
  props: /* @__PURE__ */ vue.mergeModels({
3011
3015
  accept: {},
3016
+ hint: { default: "支持 .xlsx .xls .json .txt" },
3012
3017
  limit: {},
3013
- progress: {},
3014
- hint: { default: "支持 .xlsx .xls .json .txt" }
3018
+ autoUpload: { type: Boolean, default: true },
3019
+ beforeUpload: {},
3020
+ httpRequest: {}
3015
3021
  }, {
3016
- "file": { default: "" },
3017
- "fileModifiers": {}
3022
+ "fileList": { default: () => [] },
3023
+ "fileListModifiers": {}
3018
3024
  }),
3019
- emits: /* @__PURE__ */ vue.mergeModels(["change", "exceed"], ["update:file"]),
3020
- setup(__props, { emit: __emit }) {
3021
- const file = vue.useModel(__props, "file");
3025
+ emits: /* @__PURE__ */ vue.mergeModels(["change", "progress", "success", "error", "remove", "exceed"], ["update:fileList"]),
3026
+ setup(__props, { expose: __expose, emit: __emit }) {
3027
+ const fileList = vue.useModel(__props, "fileList");
3028
+ const props = __props;
3022
3029
  const emit = __emit;
3023
3030
  const dragActive = vue.ref(false);
3024
- const isCloseHover = vue.ref(false);
3025
- const clearFile = () => {
3026
- file.value = "";
3031
+ const hoveredUid = vue.ref(null);
3032
+ const genUid = () => `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
3033
+ const findItem = (uid) => fileList.value.find((f) => f.uid === uid);
3034
+ const updateItem = (uid, patch) => {
3035
+ const item = findItem(uid);
3036
+ if (item) Object.assign(item, patch);
3037
+ return item;
3038
+ };
3039
+ const submitUpload = (item) => __async(null, null, function* () {
3040
+ if (!props.httpRequest) return;
3041
+ updateItem(item.uid, { status: "uploading", percentage: 0 });
3042
+ try {
3043
+ const response = yield props.httpRequest(item.raw, (percent) => {
3044
+ const current = updateItem(item.uid, { percentage: percent });
3045
+ if (current) emit("progress", current);
3046
+ });
3047
+ const done = updateItem(item.uid, { status: "success", percentage: 100, response });
3048
+ if (done) emit("success", done, response);
3049
+ } catch (err) {
3050
+ const failed = updateItem(item.uid, { status: "error", errorMessage: (err == null ? void 0 : err.message) || "上传失败" });
3051
+ if (failed) emit("error", failed, err);
3052
+ }
3053
+ });
3054
+ const addFiles = (files) => __async(null, null, function* () {
3055
+ if (!files.length) return;
3056
+ const remaining = props.limit != null ? props.limit - fileList.value.length : Infinity;
3057
+ if (remaining <= 0) {
3058
+ emit("exceed", files);
3059
+ return;
3060
+ }
3061
+ const accepted = files.slice(0, remaining);
3062
+ const rejected = files.slice(remaining);
3063
+ if (rejected.length) emit("exceed", rejected);
3064
+ for (const raw of accepted) {
3065
+ if (props.beforeUpload) {
3066
+ const ok = yield props.beforeUpload(raw);
3067
+ if (!ok) continue;
3068
+ }
3069
+ const item = {
3070
+ uid: genUid(),
3071
+ name: raw.name,
3072
+ status: "ready",
3073
+ percentage: 0,
3074
+ raw
3075
+ };
3076
+ fileList.value = [...fileList.value, item];
3077
+ emit("change", item);
3078
+ if (props.autoUpload) submitUpload(item);
3079
+ }
3080
+ });
3081
+ const retryUpload = (uid) => {
3082
+ const item = findItem(uid);
3083
+ if (item) submitUpload(item);
3084
+ };
3085
+ const removeFile = (uid) => {
3086
+ const item = findItem(uid);
3087
+ fileList.value = fileList.value.filter((f) => f.uid !== uid);
3088
+ if (item) emit("remove", item);
3027
3089
  };
3028
3090
  const onDragOver = (e) => {
3029
3091
  e.preventDefault();
@@ -3037,18 +3099,17 @@ var __async = (__this, __arguments, generator) => {
3037
3099
  var _a, _b;
3038
3100
  e.preventDefault();
3039
3101
  dragActive.value = false;
3040
- const f = (_b = (_a = e.dataTransfer) == null ? void 0 : _a.files) == null ? void 0 : _b[0];
3041
- if (!f) return;
3042
- emit("change", f);
3102
+ const files = Array.from((_b = (_a = e.dataTransfer) == null ? void 0 : _a.files) != null ? _b : []);
3103
+ addFiles(files);
3043
3104
  };
3044
3105
  const onFileChange = (e) => {
3045
3106
  var _a;
3046
3107
  const input = e.target;
3047
- const f = (_a = input.files) == null ? void 0 : _a[0];
3048
- if (!f) return;
3049
- emit("change", f);
3108
+ const files = Array.from((_a = input.files) != null ? _a : []);
3050
3109
  input.value = "";
3110
+ addFiles(files);
3051
3111
  };
3112
+ __expose({ retryUpload, removeFile, submitAll: () => fileList.value.filter((f) => f.status === "ready").forEach(submitUpload) });
3052
3113
  return (_ctx, _cache) => {
3053
3114
  return vue.openBlock(), vue.createElementBlock("div", _hoisted_1$c, [
3054
3115
  vue.createElementVNode("label", {
@@ -3060,6 +3121,7 @@ var __async = (__this, __arguments, generator) => {
3060
3121
  vue.createElementVNode("input", {
3061
3122
  type: "file",
3062
3123
  accept: _ctx.accept,
3124
+ multiple: _ctx.limit == null || _ctx.limit > 1,
3063
3125
  class: "f-upload__input",
3064
3126
  onChange: onFileChange
3065
3127
  }, null, 40, _hoisted_2$a),
@@ -3071,43 +3133,60 @@ var __async = (__this, __arguments, generator) => {
3071
3133
  ]),
3072
3134
  _: 1
3073
3135
  }),
3074
- _cache[2] || (_cache[2] = vue.createElementVNode("span", { class: "f-upload__title" }, "点击或拖拽文件至此处", -1)),
3136
+ _cache[1] || (_cache[1] = vue.createElementVNode("span", { class: "f-upload__title" }, "点击或拖拽文件至此处", -1)),
3075
3137
  vue.createElementVNode("span", _hoisted_4$7, vue.toDisplayString(_ctx.hint), 1)
3076
3138
  ])
3077
3139
  ], true)
3078
3140
  ], 34),
3079
- file.value ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$4, [
3080
- vue.createVNode(FIcon, null, {
3081
- default: vue.withCtx(() => [
3082
- vue.createVNode(vue.unref(ionicons5.DocumentOutline))
3083
- ]),
3084
- _: 1
3085
- }),
3086
- vue.createElementVNode("div", _hoisted_6$3, [
3087
- vue.createElementVNode("span", _hoisted_7$1, vue.toDisplayString(file.value), 1),
3088
- _ctx.progress != null ? (vue.openBlock(), vue.createBlock(FProgress, {
3089
- key: 0,
3090
- percent: _ctx.progress,
3091
- "show-text": ""
3092
- }, null, 8, ["percent"])) : vue.createCommentVNode("", true)
3093
- ]),
3094
- vue.createVNode(FIcon, {
3095
- onClick: vue.withModifiers(clearFile, ["stop"]),
3096
- onMouseenter: _cache[0] || (_cache[0] = ($event) => isCloseHover.value = true),
3097
- onMouseleave: _cache[1] || (_cache[1] = ($event) => isCloseHover.value = false),
3098
- style: { "cursor": "pointer", "transition": "color 0.2s" }
3099
- }, {
3100
- default: vue.withCtx(() => [
3101
- (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(vue.unref(isCloseHover) ? vue.unref(ionicons5.CloseCircle) : vue.unref(ionicons5.Close))))
3102
- ]),
3103
- _: 1
3104
- })
3141
+ fileList.value.length ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_5$4, [
3142
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(fileList.value, (item) => {
3143
+ return vue.openBlock(), vue.createElementBlock("div", {
3144
+ key: item.uid,
3145
+ class: vue.normalizeClass(["f-upload__file-item", `f-upload__file-item--${item.status}`])
3146
+ }, [
3147
+ vue.createVNode(FIcon, null, {
3148
+ default: vue.withCtx(() => [
3149
+ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(item.status === "success" ? vue.unref(ionicons5.CheckmarkCircle) : item.status === "error" ? vue.unref(ionicons5.AlertCircle) : vue.unref(ionicons5.DocumentOutline))))
3150
+ ]),
3151
+ _: 2
3152
+ }, 1024),
3153
+ vue.createElementVNode("div", _hoisted_6$3, [
3154
+ vue.createElementVNode("span", _hoisted_7$1, vue.toDisplayString(item.name), 1),
3155
+ item.status === "uploading" ? (vue.openBlock(), vue.createBlock(FProgress, {
3156
+ key: 0,
3157
+ percent: item.percentage,
3158
+ "show-text": ""
3159
+ }, null, 8, ["percent"])) : item.status === "error" ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_8, vue.toDisplayString(item.errorMessage), 1)) : vue.createCommentVNode("", true)
3160
+ ]),
3161
+ item.status === "error" ? (vue.openBlock(), vue.createBlock(FIcon, {
3162
+ key: 0,
3163
+ onClick: vue.withModifiers(($event) => retryUpload(item.uid), ["stop"]),
3164
+ style: { "cursor": "pointer" }
3165
+ }, {
3166
+ default: vue.withCtx(() => [
3167
+ vue.createVNode(vue.unref(ionicons5.Reload))
3168
+ ]),
3169
+ _: 2
3170
+ }, 1032, ["onClick"])) : vue.createCommentVNode("", true),
3171
+ vue.createVNode(FIcon, {
3172
+ onClick: vue.withModifiers(($event) => removeFile(item.uid), ["stop"]),
3173
+ onMouseenter: ($event) => hoveredUid.value = item.uid,
3174
+ onMouseleave: _cache[0] || (_cache[0] = ($event) => hoveredUid.value = null),
3175
+ style: { "cursor": "pointer", "transition": "color 0.2s" }
3176
+ }, {
3177
+ default: vue.withCtx(() => [
3178
+ (vue.openBlock(), vue.createBlock(vue.resolveDynamicComponent(vue.unref(hoveredUid) === item.uid ? vue.unref(ionicons5.CloseCircle) : vue.unref(ionicons5.Close))))
3179
+ ]),
3180
+ _: 2
3181
+ }, 1032, ["onClick", "onMouseenter"])
3182
+ ], 2);
3183
+ }), 128))
3105
3184
  ])) : vue.createCommentVNode("", true)
3106
3185
  ]);
3107
3186
  };
3108
3187
  }
3109
3188
  });
3110
- const FUpload = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-6bdc061f"]]);
3189
+ const FUpload = /* @__PURE__ */ _export_sfc(_sfc_main$g, [["__scopeId", "data-v-04ecda2b"]]);
3111
3190
  const _sfc_main$f = /* @__PURE__ */ vue.defineComponent({
3112
3191
  __name: "FButtonGroup",
3113
3192
  props: {
@@ -1,26 +1,35 @@
1
- type __VLS_Props = {
2
- accept?: string;
3
- limit?: number;
4
- progress?: number | null;
5
- hint?: string;
6
- };
1
+ import type { FUploadProps, UploadFileItem } from '../../types';
2
+ type __VLS_Props = FUploadProps;
7
3
  type __VLS_PublicProps = __VLS_Props & {
8
- 'file'?: string;
4
+ 'fileList'?: UploadFileItem[];
9
5
  };
10
6
  declare var __VLS_1: {};
11
7
  type __VLS_Slots = {} & {
12
8
  default?: (props: typeof __VLS_1) => any;
13
9
  };
14
- declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
- change: (file: File) => any;
16
- exceed: (file: File) => any;
17
- "update:file": (value: string) => any;
10
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps, {
11
+ retryUpload: (uid: string) => void;
12
+ removeFile: (uid: string) => void;
13
+ submitAll: () => void;
14
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ success: (file: UploadFileItem, response: string) => any;
16
+ error: (file: UploadFileItem, error: unknown) => any;
17
+ progress: (file: UploadFileItem) => any;
18
+ change: (file: UploadFileItem) => any;
19
+ remove: (file: UploadFileItem) => any;
20
+ exceed: (files: File[]) => any;
21
+ "update:fileList": (value: UploadFileItem[]) => any;
18
22
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
19
- onChange?: (file: File) => any;
20
- onExceed?: (file: File) => any;
21
- "onUpdate:file"?: (value: string) => any;
23
+ onSuccess?: (file: UploadFileItem, response: string) => any;
24
+ onError?: (file: UploadFileItem, error: unknown) => any;
25
+ onProgress?: (file: UploadFileItem) => any;
26
+ onChange?: (file: UploadFileItem) => any;
27
+ onRemove?: (file: UploadFileItem) => any;
28
+ onExceed?: (files: File[]) => any;
29
+ "onUpdate:fileList"?: (value: UploadFileItem[]) => any;
22
30
  }>, {
23
31
  hint: string;
32
+ autoUpload: boolean;
24
33
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
25
34
  declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
26
35
  export default _default;
@@ -4,6 +4,4 @@ declare const install: Plugin;
4
4
  export default install;
5
5
  export * from './components';
6
6
  export * from './composables';
7
- export type { FDialogFn, FDialogOptions } from './types/components';
8
- export type { FMessageFn, FMessageOptions, FMessageInstance } from './types/components';
9
- export type { FNotificationFn, FNotificationOptions, FNotificationInstance } from './types/components';
7
+ export type { FDialogFn, FDialogOptions, FMessageFn, FMessageOptions, FMessageInstance, FNotificationFn, FNotificationOptions, FNotificationInstance, UploadFileItem } from './types/components';
@@ -1,11 +1,28 @@
1
- type imageType = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/jpg' | 'image/bmp' | 'image/gif' | 'image/svg+xml' | 'image/svg';
2
- type fileType = imageType;
3
- type fileTypes = 'image';
4
- type fileTypesMap = {
5
- [key in fileTypes]: fileType[];
6
- };
7
- interface SheetData {
8
- sheetName: string;
9
- data: any[];
1
+ interface FUploadProps {
2
+ accept?: string;
3
+ hint?: string;
4
+ /** 最多允许存在的文件数(数量限制,不是字节大小) */
5
+ limit?: number;
6
+ /** 选中后是否立即自动上传,默认 true */
7
+ autoUpload?: boolean;
8
+ /**
9
+ * 校验/拦截钩子:返回 false 或 reject 则该文件不会进入列表也不会上传。
10
+ * 用来做文件大小、类型等前置校验,替代过去分散在各业务组件里的校验代码。
11
+ */
12
+ beforeUpload?: (file: File) => boolean | Promise<boolean>;
13
+ /**
14
+ * 实际上传逻辑,由调用方注入——组件本身不关心用的是 fetch/axios/dispatcher,
15
+ * 只负责在合适的时机调用它,并把 onProgress 回调交给它在上传过程中调用。
16
+ */
17
+ httpRequest?: (file: File, onProgress: (percent: number) => void) => Promise<string>;
10
18
  }
11
- export type { fileTypesMap, SheetData, imageType };
19
+ interface UploadFileItem {
20
+ uid: string;
21
+ name: string;
22
+ status: 'ready' | 'uploading' | 'success' | 'error';
23
+ percentage: number;
24
+ raw: File;
25
+ response?: string;
26
+ errorMessage?: string;
27
+ }
28
+ export type { FUploadProps, UploadFileItem };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fantasia-ui",
3
3
  "private": false,
4
- "version": "0.2.58",
4
+ "version": "0.2.59",
5
5
  "type": "module",
6
6
  "main": "dist/fantasia-ui.umd.js",
7
7
  "module": "dist/fantasia-ui.es.js",