qidian-vue-ui 1.3.4 → 1.4.1

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.
@@ -1,4 +1,18 @@
1
- .qd-file-viewer[data-v-b3189b53] {
1
+ .qd-file-viewer[data-v-d9e97271],
2
+ .qd-file-viewer__content[data-v-d9e97271] {
2
3
  width: 100%;
3
4
  height: 100%;
4
5
  }
6
+ .qd-file-viewer--image[data-v-d9e97271] {
7
+ transform: translate3d(0, 0, 0);
8
+ }
9
+ .qd-file-viewer--image[data-v-d9e97271] .t-image-viewer__trigger {
10
+ display: none;
11
+ }
12
+ .qd-file-viewer--image[data-v-d9e97271] .t-image-viewer__modal-box,
13
+ .qd-file-viewer--image[data-v-d9e97271] .t-image-viewer-preview-image {
14
+ animation: none;
15
+ }
16
+ .qd-file-viewer--image[data-v-d9e97271] .t-image-viewer__modal-mask {
17
+ transition: none;
18
+ }
@@ -2,7 +2,7 @@ import './index.css'
2
2
  import _sfc_main from "./index.vue2.mjs";
3
3
  /* empty css */
4
4
  import _export_sfc from "../../_virtual/_plugin-vue_export-helper.mjs";
5
- const QdFileViewer = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-b3189b53"]]);
5
+ const QdFileViewer = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d9e97271"]]);
6
6
  export {
7
7
  QdFileViewer as default
8
8
  };
@@ -1,7 +1,10 @@
1
1
  import './index.css'
2
- import { defineComponent, computed, createBlock, openBlock, unref } from "vue";
2
+ import { defineComponent, ref, computed, watch, nextTick, createElementBlock, openBlock, normalizeClass, createBlock, unref } from "vue";
3
3
  import { allRenderers } from "@file-viewer/preset-all";
4
4
  import { FileViewer } from "@file-viewer/vue3";
5
+ import { ImageViewer } from "tdesign-vue-next";
6
+ import { isImage } from "../../utils/tools.mjs";
7
+ import { useConfig } from "../config-provider/useConfig.mjs";
5
8
  import { useTheme } from "../../hooks/useTheme.mjs";
6
9
  import QdOfficePdf from "../office/pdf/index.vue.mjs";
7
10
  import merge from "../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/merge.mjs";
@@ -11,11 +14,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
11
14
  props: {
12
15
  src: {},
13
16
  file: {},
14
- options: {}
17
+ options: {},
18
+ attach: {}
15
19
  },
16
20
  setup(__props) {
17
21
  const props = __props;
18
22
  const tdTheme = useTheme();
23
+ const { t } = useConfig("wrapper");
24
+ const rootRef = ref();
25
+ const imageViewerVisible = ref(false);
19
26
  const isPdf = computed(() => {
20
27
  const { file, src } = props;
21
28
  if (file) {
@@ -28,36 +35,85 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
28
35
  }
29
36
  return false;
30
37
  });
38
+ const fileType = computed(() => {
39
+ const { file, src } = props;
40
+ if (file && "type" in file && file.type) return file.type;
41
+ if (file && "name" in file && file.name) return file.name;
42
+ return src || "";
43
+ });
44
+ const isImageFile = computed(() => isImage(fileType.value));
45
+ const imageViewerSource = computed(() => {
46
+ const { file, src } = props;
47
+ if (typeof File !== "undefined" && file instanceof File) return file;
48
+ if (src) return src;
49
+ return void 0;
50
+ });
51
+ const imageViewerImages = computed(
52
+ () => imageViewerSource.value ? [imageViewerSource.value] : []
53
+ );
54
+ const useImageViewer = computed(() => isImageFile.value && !!imageViewerSource.value);
55
+ const resolvedAttach = computed(() => {
56
+ if (rootRef.value) return () => rootRef.value;
57
+ return "body";
58
+ });
59
+ watch(
60
+ useImageViewer,
61
+ (val) => {
62
+ if (!val) {
63
+ imageViewerVisible.value = false;
64
+ return;
65
+ }
66
+ nextTick(() => {
67
+ imageViewerVisible.value = true;
68
+ });
69
+ },
70
+ { immediate: true }
71
+ );
31
72
  return (_ctx, _cache) => {
32
- return isPdf.value ? (openBlock(), createBlock(unref(QdOfficePdf), {
33
- key: 0,
34
- class: "qd-file-viewer",
35
- src: __props.src,
36
- file: __props.file
37
- }, null, 8, ["src", "file"])) : (openBlock(), createBlock(unref(FileViewer), {
38
- key: 1,
39
- class: "qd-file-viewer",
40
- file: __props.file,
41
- url: __props.src,
42
- options: unref(merge)(
43
- {
44
- theme: unref(tdTheme),
45
- toolbar: {
46
- download: false,
47
- exportHtml: false,
48
- print: false
73
+ return openBlock(), createElementBlock("div", {
74
+ ref_key: "rootRef",
75
+ ref: rootRef,
76
+ class: normalizeClass(["qd-file-viewer", useImageViewer.value ? "qd-file-viewer--image" : ""])
77
+ }, [
78
+ useImageViewer.value ? (openBlock(), createBlock(unref(ImageViewer), {
79
+ key: 0,
80
+ visible: imageViewerVisible.value,
81
+ "onUpdate:visible": _cache[0] || (_cache[0] = ($event) => imageViewerVisible.value = $event),
82
+ images: imageViewerImages.value,
83
+ "close-btn": false,
84
+ "close-on-esc-keydown": false,
85
+ "close-on-overlay": false,
86
+ attach: resolvedAttach.value
87
+ }, null, 8, ["visible", "images", "attach"])) : isPdf.value ? (openBlock(), createBlock(unref(QdOfficePdf), {
88
+ key: 1,
89
+ class: "qd-file-viewer__content",
90
+ src: __props.src,
91
+ file: __props.file
92
+ }, null, 8, ["src", "file"])) : (openBlock(), createBlock(unref(FileViewer), {
93
+ key: 2,
94
+ class: "qd-file-viewer__content",
95
+ file: __props.file,
96
+ url: __props.src,
97
+ options: unref(merge)(
98
+ {
99
+ theme: unref(tdTheme),
100
+ toolbar: {
101
+ download: false,
102
+ exportHtml: false,
103
+ print: false
104
+ },
105
+ pdf: {
106
+ defaultNavigationVisible: false
107
+ }
49
108
  },
50
- pdf: {
51
- defaultNavigationVisible: false
109
+ __props.options,
110
+ {
111
+ builtinRenderers: "none",
112
+ renderers: unref(allRenderers)
52
113
  }
53
- },
54
- __props.options,
55
- {
56
- builtinRenderers: "none",
57
- renderers: unref(allRenderers)
58
- }
59
- )
60
- }, null, 8, ["file", "url", "options"]));
114
+ )
115
+ }, null, 8, ["file", "url", "options"]))
116
+ ], 2);
61
117
  };
62
118
  }
63
119
  });
@@ -1,7 +1,9 @@
1
1
  import type { FileRef, FileViewerOptions } from '@file-viewer/vue3';
2
+ import type { QdDialogProps } from '../dialog';
2
3
  export type QdFileViewerFile = FileRef;
3
4
  export interface QdFileViewerProps {
4
5
  src?: string;
5
6
  file?: QdFileViewerFile;
6
7
  options?: Omit<FileViewerOptions, 'builtinRenderers' | 'renderers'>;
8
+ attach?: QdDialogProps['attach'];
7
9
  }
@@ -6,6 +6,7 @@ declare const _default: import("vue").DefineComponent<QdMultiFileViewerProps, {
6
6
  show: typeof show;
7
7
  hide: typeof hide;
8
8
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<QdMultiFileViewerProps> & Readonly<{}>, {
9
- wrapper: QdDialogProps | false;
9
+ wrapper: Omit<QdDialogProps, "attach" | "destroyOnClose"> | false;
10
+ fileViewerOnly: boolean;
10
11
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
11
12
  export default _default;
@@ -1,11 +1,11 @@
1
1
  import './index.css'
2
2
  import { defineComponent, mergeDefaults, ref, computed, watch, createBlock, openBlock, resolveDynamicComponent, mergeProps, withCtx, createElementBlock, Fragment, createVNode, createElementVNode, unref, renderList, createTextVNode, toDisplayString, createCommentVNode, nextTick } from "vue";
3
3
  import { Menu, MenuItem, Empty, ImageViewer } from "tdesign-vue-next";
4
+ import { isImage } from "../../utils/tools.mjs";
4
5
  import { useConfig } from "../config-provider/useConfig.mjs";
5
6
  import _sfc_main$1 from "../dialog/index.vue.mjs";
6
7
  /* empty css */
7
8
  import QdFileViewer from "../file-viewer/index.vue.mjs";
8
- import { IMAGE_REGEXP } from "../upload/common.mjs";
9
9
  import { qdMultiFileViewerWrapperProps, qdMultiFileViewerProps } from "./props.mjs";
10
10
  import merge from "../../node_modules/.pnpm/lodash-es@4.17.21/node_modules/lodash-es/merge.mjs";
11
11
  const _hoisted_1 = {
@@ -20,6 +20,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
20
20
  index: {},
21
21
  options: {},
22
22
  files: {},
23
+ attach: {},
24
+ fileViewerOnly: { type: Boolean },
23
25
  wrapper: { type: [Object, Boolean] }
24
26
  }, qdMultiFileViewerProps),
25
27
  setup(__props, { expose: __expose }) {
@@ -55,22 +57,25 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
55
57
  });
56
58
  const allFilesAreImages = computed(() => {
57
59
  const files = mergedProps.value.files;
58
- return !!files?.length && files.every((item) => isImage(item) && !!resolveImageViewerSource(item));
60
+ return !!files?.length && files.every((item) => isImage(resolveFileType(item)) && !!resolveImageViewerSource(item));
59
61
  });
62
+ const fileViewerOnly = computed(
63
+ () => mergedProps.value.wrapper === false || mergedProps.value.fileViewerOnly === true
64
+ );
65
+ const useImageViewer = computed(() => allFilesAreImages.value && !fileViewerOnly.value);
60
66
  const imageViewerImages = computed(
61
67
  () => (mergedProps.value.files ?? []).map((item) => resolveImageViewerSource(item) ?? "")
62
68
  );
63
- const imageViewerAttach = computed(() => {
64
- const wrapper = mergedProps.value.wrapper === false ? void 0 : mergedProps.value.wrapper;
65
- return wrapper?.attach ?? wrapperT("attach") ?? "body";
66
- });
67
- const isDialog = computed(() => mergedProps.value.wrapper !== false && !allFilesAreImages.value);
69
+ const resolvedAttach = computed(() => mergedProps.value.attach ?? wrapperT("attach") ?? "body");
70
+ const isDialog = computed(() => mergedProps.value.wrapper !== false && !useImageViewer.value);
68
71
  const dialogProps = computed(() => {
69
72
  const wrapper = mergedProps.value.wrapper === false ? void 0 : mergedProps.value.wrapper;
70
73
  return {
71
74
  ...qdMultiFileViewerWrapperProps,
72
75
  ...wrapper,
73
76
  header: wrapper?.header ?? activeFileName.value,
77
+ attach: resolvedAttach.value,
78
+ destroyOnClose: true,
74
79
  cancelBtn: wrapper?.cancelBtn === void 0 ? t("closeText") : typeof wrapper.cancelBtn === "object" && wrapper.cancelBtn !== null ? { ...wrapper.cancelBtn, content: t("closeText") } : wrapper.cancelBtn
75
80
  };
76
81
  });
@@ -82,9 +87,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
82
87
  if (file && "name" in file && file.name) return file.name;
83
88
  return item.src || item.name || "";
84
89
  }
85
- function isImage(item) {
86
- return IMAGE_REGEXP.test(resolveFileType(item));
87
- }
88
90
  function resolveImageViewerSource(item) {
89
91
  if (typeof File !== "undefined" && item.file instanceof File) return item.file;
90
92
  if (item.src) return item.src;
@@ -117,9 +119,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
117
119
  function show(override) {
118
120
  runtimeOverrides.value = override ?? {};
119
121
  activeIndex.value = normalizeIndex(mergedProps.value.index, mergedProps.value.files);
120
- if (allFilesAreImages.value) {
122
+ if (useImageViewer.value) {
121
123
  imageViewerIndex.value = activeIndex.value;
122
- imageViewerVisible.value = true;
124
+ imageViewerVisible.value = false;
125
+ nextTick(() => {
126
+ imageViewerVisible.value = true;
127
+ });
123
128
  return;
124
129
  }
125
130
  if (!isDialog.value) return;
@@ -139,12 +144,12 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
139
144
  __expose({ show, hide });
140
145
  return (_ctx, _cache) => {
141
146
  return openBlock(), createBlock(resolveDynamicComponent(wrapperTag.value), mergeProps(wrapperBindings.value, {
142
- class: ["qd-multi-file-viewer", allFilesAreImages.value ? "qd-multi-file-viewer--image" : ""],
147
+ class: ["qd-multi-file-viewer", useImageViewer.value ? "qd-multi-file-viewer--image" : ""],
143
148
  ref_key: "dialogRef",
144
149
  ref: dialogRef
145
150
  }), {
146
151
  default: withCtx(() => [
147
- !allFilesAreImages.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
152
+ !useImageViewer.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
148
153
  mergedProps.value.files?.length ? (openBlock(), createElementBlock("div", _hoisted_1, [
149
154
  createVNode(unref(Menu), {
150
155
  value: activeIndex.value,
@@ -180,7 +185,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
180
185
  index: imageViewerIndex.value,
181
186
  "onUpdate:index": _cache[1] || (_cache[1] = ($event) => imageViewerIndex.value = $event),
182
187
  images: imageViewerImages.value,
183
- attach: imageViewerAttach.value
188
+ attach: resolvedAttach.value
184
189
  }, null, 8, ["visible", "index", "images", "attach"]))
185
190
  ]),
186
191
  _: 1
@@ -11,4 +11,5 @@ export declare const qdMultiFileViewerProps: {
11
11
  readonly confirmBtn: null;
12
12
  readonly footer: false;
13
13
  };
14
+ readonly fileViewerOnly: false;
14
15
  };
@@ -5,7 +5,8 @@ const qdMultiFileViewerWrapperProps = {
5
5
  footer: false
6
6
  };
7
7
  const qdMultiFileViewerProps = {
8
- wrapper: () => qdMultiFileViewerWrapperProps
8
+ wrapper: () => qdMultiFileViewerWrapperProps,
9
+ fileViewerOnly: false
9
10
  };
10
11
  export {
11
12
  qdMultiFileViewerProps,
@@ -10,7 +10,9 @@ export interface QdMultiFileViewerProps {
10
10
  index?: number;
11
11
  options?: QdFileViewerProps['options'];
12
12
  files?: QdMultiFileViewerItem[];
13
- wrapper?: QdDialogProps | false;
13
+ attach?: QdDialogProps['attach'];
14
+ fileViewerOnly?: boolean;
15
+ wrapper?: Omit<QdDialogProps, 'attach' | 'destroyOnClose'> | false;
14
16
  }
15
17
  export interface QdMultiFileViewerInstanceFunctions {
16
18
  show: (props?: Partial<QdMultiFileViewerProps>) => void;
@@ -1,10 +1,11 @@
1
+ import { isImage } from "../../../../utils/tools.mjs";
1
2
  function isImageUrl(url) {
2
- return /\.(jpg|jpeg|png|gif|webp|bmp)(\?.*)?$/i.test(url);
3
+ return isImage(url);
3
4
  }
4
5
  function isImageFile(file) {
5
6
  if (file.type.startsWith("image/")) return true;
6
7
  if (!(file instanceof File)) return false;
7
- return /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(file.name);
8
+ return isImage(file.name);
8
9
  }
9
10
  function loadImage(src) {
10
11
  return new Promise((resolve, reject) => {
@@ -1,25 +1,25 @@
1
- .qd-service-table[data-v-77723bee] {
1
+ .qd-service-table[data-v-c2177f89] {
2
2
  height: 100%;
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  }
6
- .qd-service-table[data-v-77723bee] > .t-table__content {
6
+ .qd-service-table[data-v-c2177f89] > .t-table__content {
7
7
  flex: 1;
8
8
  }
9
- .qd-service-table[data-v-77723bee] .t-table__tree-col:not(:has(.t-table__tree-op-icon)) {
9
+ .qd-service-table[data-v-c2177f89] .t-table__tree-col:not(:has(.t-table__tree-op-icon)) {
10
10
  margin-left: calc(var(--td-font-size-body-large) + var(--td-comp-margin-s));
11
11
  }
12
- .qd-service-table[data-v-77723bee] .t-table__tree-leaf-node .t-table__tree-op-icon:empty {
12
+ .qd-service-table[data-v-c2177f89] .t-table__tree-leaf-node .t-table__tree-op-icon:empty {
13
13
  width: calc(var(--td-font-size-body-large) + var(--td-comp-margin-s));
14
14
  }
15
- .qd-service-table[data-v-77723bee] .t-affix {
16
- transform: var(--v2846a290);
15
+ .qd-service-table[data-v-c2177f89] .t-affix {
16
+ transform: var(--v072441ea);
17
17
  }
18
- .qd-service-table--ps-x-fixed[data-v-77723bee] .ps__rail-x {
18
+ .qd-service-table--ps-x-fixed[data-v-c2177f89] .ps__rail-x {
19
19
  position: fixed;
20
- left: var(--v4a362512) !important;
21
- bottom: var(--v224f22e0) !important;
20
+ left: var(--ac91cc66) !important;
21
+ bottom: var(--v79d9a896) !important;
22
22
  }
23
- .qd-service-table--disable-horizontal-scroll[data-v-77723bee] colgroup > col {
23
+ .qd-service-table--disable-horizontal-scroll[data-v-c2177f89] colgroup > col {
24
24
  width: auto !important;
25
25
  }
@@ -2,7 +2,7 @@ import './index.css'
2
2
  import _sfc_main from "./index.vue2.mjs";
3
3
  /* empty css */
4
4
  import _export_sfc from "../../../_virtual/_plugin-vue_export-helper.mjs";
5
- const QdServiceTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-77723bee"]]);
5
+ const QdServiceTable = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-c2177f89"]]);
6
6
  export {
7
7
  QdServiceTable as default
8
8
  };
@@ -307,9 +307,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
307
307
  emit: __emit
308
308
  }) {
309
309
  useCssVars((_ctx) => ({
310
- "v2846a290": affixedHeaderTransform.value,
311
- "v4a362512": tableContentLeft.value,
312
- "v224f22e0": outsideBottom.value
310
+ "v072441ea": affixedHeaderTransform.value,
311
+ "ac91cc66": tableContentLeft.value,
312
+ "v79d9a896": outsideBottom.value
313
313
  }));
314
314
  const props = __props;
315
315
  const emit = __emit;
@@ -355,19 +355,24 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
355
355
  const tableContentVisible = ref(false);
356
356
  const isScrollSyncing = ref(false);
357
357
  const tableContentRect = reactive(useElementBounding(tableContentEl));
358
- const tableContentLeft = computed(() => `${tableContentRect.left}px`);
358
+ const tableContentLeft = computed(() => `${tableContentRect.left - affixedRailOffsetX.value}px`);
359
359
  const outsideScrollRect = reactive(useElementBounding(outsideScrollEl));
360
- const outsideBottom = computed(() => outsideScrollEl.value ? `${window.innerHeight - outsideScrollRect.bottom}px` : "0");
360
+ const affixBottomOffset = ref(0);
361
+ let probeRafId = 0;
362
+ let animatingAncestorCleanup = null;
363
+ const outsideBottom = computed(() => {
364
+ if (!outsideScrollEl.value) return "0";
365
+ return `${Math.max(0, window.innerHeight - outsideScrollRect.bottom + affixBottomOffset.value)}px`;
366
+ });
361
367
  const tableHeaderHalfVisible = computed(() => {
362
368
  if (!tableContentVisible.value) return false;
363
369
  return tableContentRect.top + tableHeaderH.value * 0.5 <= outsideScrollRect.bottom;
364
370
  });
365
371
  const tableContentFullVisible = computed(() => {
366
372
  if (!tableContentVisible.value) return false;
367
- const contentBottom = tableContentRect.bottom;
368
- const containerBottom = outsideScrollRect.bottom;
369
- return contentBottom <= containerBottom;
373
+ return tableContentRect.bottom <= outsideScrollRect.bottom - affixBottomOffset.value;
370
374
  });
375
+ const isPsXFixed = computed(() => tableContentVisible.value && tableHeaderHalfVisible.value && !tableContentFullVisible.value);
371
376
  const {
372
377
  ps: tablePs
373
378
  } = usePerfectScrollbar(tableContentEl, {
@@ -403,6 +408,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
403
408
  stop: outsideTableContentStop
404
409
  } = useMouseInElement(tableContentEl);
405
410
  const affixedHeaderTransform = ref("none");
411
+ const affixedRailOffsetX = ref(0);
406
412
  const affixBaseEl = computed(() => outsideScrollEl.value || tableRef.value?.$el);
407
413
  const reProps = computed(() => {
408
414
  const {
@@ -826,10 +832,15 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
826
832
  return keys;
827
833
  }, []);
828
834
  }
835
+ function refreshAffixedMetrics() {
836
+ tableContentRect.update();
837
+ outsideScrollRect.update();
838
+ affixedRailOffsetX.value = getTransformOffset(affixBaseEl.value).x;
839
+ }
829
840
  function onResize(entries) {
830
841
  const el = entries[0].target;
831
842
  outsideScrollEl.value = getScrollEl(el);
832
- outsideScrollRect.update();
843
+ refreshAffixedMetrics();
833
844
  const headerEl = el.querySelector(".t-table__header");
834
845
  tableHeaderEl.value = headerEl;
835
846
  let headerH = 0;
@@ -837,6 +848,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
837
848
  tableHeaderH.value = headerH;
838
849
  const contentEl = el.querySelector(".t-table__content");
839
850
  tableContentEl.value = contentEl;
851
+ probeBottomBlockHeight();
840
852
  }
841
853
  function isTransformAnimating(node) {
842
854
  return node.getAnimations().length > 0;
@@ -851,6 +863,23 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
851
863
  }
852
864
  return null;
853
865
  }
866
+ function watchAnimatingTransformAncestor() {
867
+ animatingAncestorCleanup?.();
868
+ const animatingNode = findAnimatingTransformAncestor(affixBaseEl.value);
869
+ if (!animatingNode) return;
870
+ const cleanup = () => {
871
+ animatingNode.removeEventListener("transitionend", onEnd);
872
+ animatingNode.removeEventListener("animationend", onEnd);
873
+ animatingAncestorCleanup = null;
874
+ };
875
+ const onEnd = () => {
876
+ cleanup();
877
+ refreshAffixedMetrics();
878
+ };
879
+ animatingNode.addEventListener("transitionend", onEnd);
880
+ animatingNode.addEventListener("animationend", onEnd);
881
+ animatingAncestorCleanup = cleanup;
882
+ }
854
883
  async function applyAffixedTransform() {
855
884
  const el = affixBaseEl.value;
856
885
  if (!el) return;
@@ -861,6 +890,33 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
861
890
  });
862
891
  affixedHeaderTransform.value = `translateY(-${y}px)`;
863
892
  }
893
+ function scheduleProbeBottomBlock() {
894
+ if (probeRafId) return;
895
+ probeRafId = requestAnimationFrame(() => {
896
+ probeRafId = 0;
897
+ probeBottomBlockHeight();
898
+ });
899
+ }
900
+ function probeBottomBlockHeight() {
901
+ const scrollEl = outsideScrollEl.value;
902
+ const tableEl = tableRef.value?.$el;
903
+ if (!scrollEl || !tableEl) return;
904
+ const sRect = scrollEl.getBoundingClientRect();
905
+ const stack = document.elementsFromPoint(sRect.left + sRect.width / 2, sRect.bottom - 1);
906
+ let blockHeight = 0;
907
+ for (const el of stack) {
908
+ if (tableEl.contains(el)) continue;
909
+ if (el === scrollEl || el.contains(scrollEl)) continue;
910
+ const {
911
+ position
912
+ } = getComputedStyle(el);
913
+ if (position !== "fixed" && position !== "sticky") continue;
914
+ const rect = el.getBoundingClientRect();
915
+ if (rect.top < sRect.top) continue;
916
+ blockHeight = Math.max(blockHeight, sRect.bottom - rect.top);
917
+ }
918
+ affixBottomOffset.value = blockHeight;
919
+ }
864
920
  watch(columnControllerVisible, (visible) => {
865
921
  const {
866
922
  columns
@@ -879,6 +935,31 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
879
935
  requestAnimationFrame(() => {
880
936
  tablePs.value?.update();
881
937
  });
938
+ watchAnimatingTransformAncestor();
939
+ nextTick(() => {
940
+ setTimeout(refreshAffixedMetrics, 320);
941
+ });
942
+ });
943
+ watch(isOutsideTableContent, (outside) => {
944
+ if (outside) return;
945
+ refreshAffixedMetrics();
946
+ });
947
+ watch(outsideScrollEl, (el, _old, onCleanup) => {
948
+ if (!el) return;
949
+ probeBottomBlockHeight();
950
+ const onScroll = () => scheduleProbeBottomBlock();
951
+ el.addEventListener("scroll", onScroll, {
952
+ passive: true
953
+ });
954
+ onCleanup(() => {
955
+ el.removeEventListener("scroll", onScroll);
956
+ if (probeRafId) {
957
+ cancelAnimationFrame(probeRafId);
958
+ probeRafId = 0;
959
+ }
960
+ });
961
+ }, {
962
+ immediate: true
882
963
  });
883
964
  onActivated(() => {
884
965
  setScrollElTo();
@@ -890,6 +971,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
890
971
  onBeforeUnmount(() => {
891
972
  tableContentStop();
892
973
  outsideTableContentStop();
974
+ if (probeRafId) cancelAnimationFrame(probeRafId);
975
+ animatingAncestorCleanup?.();
893
976
  });
894
977
  __expose(createExposeProxy({
895
978
  data,
@@ -917,7 +1000,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
917
1000
  return (_ctx, _cache) => {
918
1001
  return withDirectives((openBlock(), createBlock(resolveDynamicComponent(reProps.value.tableOptions.tree ? unref(EnhancedTable) : unref(Table)), mergeProps({
919
1002
  ref: "table",
920
- class: ["qd-service-table", __props.headerBg ? "" : "t-table--header-bg-inherit", unref(isOutsideTableContent) ? "" : "qd-service-table--inside-table-content", tableContentVisible.value && tableHeaderHalfVisible.value && !tableContentFullVisible.value ? "qd-service-table--ps-x-fixed" : "", __props.disableHorizontalScroll ? "qd-service-table--disable-horizontal-scroll" : ""],
1003
+ class: ["qd-service-table", __props.headerBg ? "" : "t-table--header-bg-inherit", unref(isOutsideTableContent) ? "" : "qd-service-table--inside-table-content", isPsXFixed.value ? "qd-service-table--ps-x-fixed" : "", __props.disableHorizontalScroll ? "qd-service-table--disable-horizontal-scroll" : ""],
921
1004
  "selected-row-keys": unref(selectedRowKeys),
922
1005
  "onUpdate:selectedRowKeys": _cache[0] || (_cache[0] = ($event) => isRef(selectedRowKeys) ? selectedRowKeys.value = $event : null),
923
1006
  "active-row-keys": unref(activeRowKeys),
@@ -1,5 +1,5 @@
1
+ import type { UploadFile } from 'tdesign-vue-next';
1
2
  import type { Slot } from 'vue';
2
3
  export declare function handleAbridgeName(inputName: string, leftCount?: number, rightCount?: number): string;
3
- export declare const IMAGE_REGEXP: RegExp;
4
- export declare function returnFileSize(number: number | undefined): string;
4
+ export declare function getFileType(file: UploadFile): string;
5
5
  export declare function hasSlotContent(slot?: Slot): boolean;
@@ -15,14 +15,8 @@ function handleAbridgeName(inputName, leftCount = 5, rightCount = 7) {
15
15
  }
16
16
  return name.replace(new RegExp(`^(.{${leftLength}})(.+)(.{${rightLength}})$`), "$1…$3");
17
17
  }
18
- const IMAGE_REGEXP = /(.png|.jpg|.jpeg|.jpe|.webp|.avif|.heic|.heif|.svg|.gif|.bmp)/i;
19
- function returnFileSize(number) {
20
- if (!number && number !== 0) return "";
21
- const SIZE_MAP = { KB: 1024, MB: 1048576, GB: 1073741824 };
22
- if (number < SIZE_MAP.KB) return `${number} Bytes`;
23
- if (number < SIZE_MAP.MB) return `${(number / SIZE_MAP.KB).toFixed(1)} KB`;
24
- if (number < SIZE_MAP.GB) return `${(number / SIZE_MAP.MB).toFixed(1)} MB`;
25
- return `${(number / SIZE_MAP.GB).toFixed(1)} GB`;
18
+ function getFileType(file) {
19
+ return file.raw?.type || file.type || file.name || "";
26
20
  }
27
21
  function hasSlotContent(slot) {
28
22
  if (!slot) return false;
@@ -40,8 +34,7 @@ function isValidSlotChild(children) {
40
34
  return true;
41
35
  }
42
36
  export {
43
- IMAGE_REGEXP,
37
+ getFileType,
44
38
  handleAbridgeName,
45
- hasSlotContent,
46
- returnFileSize
39
+ hasSlotContent
47
40
  };
@@ -1,7 +1,9 @@
1
1
  import { defineComponent, computed, createElementBlock, openBlock, mergeProps, createElementVNode, createCommentVNode, toDisplayString, Fragment, renderList, createBlock, unref, resolveDynamicComponent, normalizeClass, createVNode } from "vue";
2
+ import { formatByte } from "qidian-shared";
2
3
  import { TimeFilledIcon, ErrorCircleFilledIcon, CheckCircleFilledIcon, FileIcon } from "tdesign-icons-vue-next";
3
4
  import { Image, Button, Loading } from "tdesign-vue-next";
4
- import { handleAbridgeName, returnFileSize, IMAGE_REGEXP } from "../common.mjs";
5
+ import { isImage } from "../../../utils/tools.mjs";
6
+ import { getFileType, handleAbridgeName } from "../common.mjs";
5
7
  import QdFileIcon from "../../file-icon/index.vue.mjs";
6
8
  const _hoisted_1 = { style: { "min-width": "120px" } };
7
9
  const _hoisted_2 = { style: { "min-width": "120px" } };
@@ -37,15 +39,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
37
39
  const allSuccess = computed(
38
40
  () => props.files.every((file) => file.status === "success" || !file.status)
39
41
  );
40
- function getFileType(file) {
41
- return file.raw?.type || file.type || file.name || "";
42
- }
43
42
  function hasThumbnail(file) {
44
43
  return !(!file.raw && !file.type && file.url);
45
44
  }
46
- function isImage(file) {
47
- return IMAGE_REGEXP.test(getFileType(file));
48
- }
49
45
  function getStatusIcon(status) {
50
46
  const iconMap = {
51
47
  success: CheckCircleFilledIcon,
@@ -97,7 +93,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
97
93
  createElementVNode("td", _hoisted_6, [
98
94
  createElementVNode("span", _hoisted_7, [
99
95
  __props.showThumbnail && hasThumbnail(file) ? (openBlock(), createElementBlock("span", _hoisted_8, [
100
- isImage(file) ? (openBlock(), createBlock(unref(Image), {
96
+ unref(isImage)(unref(getFileType)(file)) ? (openBlock(), createBlock(unref(Image), {
101
97
  key: 0,
102
98
  class: "t-upload__file-thumbnail",
103
99
  src: file.url || file.raw,
@@ -118,7 +114,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
118
114
  }, toDisplayString(__props.abridgeName?.length && file.name ? unref(handleAbridgeName)(file.name, ...__props.abridgeName) : file.name), 9, _hoisted_9)
119
115
  ])
120
116
  ]),
121
- createElementVNode("td", null, toDisplayString(unref(returnFileSize)(file.size)), 1),
117
+ createElementVNode("td", null, toDisplayString(unref(formatByte)(file.size)), 1),
122
118
  createElementVNode("td", null, [
123
119
  createElementVNode("div", _hoisted_10, [
124
120
  (openBlock(), createBlock(resolveDynamicComponent(getStatusIcon(file.status)))),
@@ -1,7 +1,8 @@
1
1
  import { defineComponent, useSlots, computed, createElementBlock, openBlock, createCommentVNode, Fragment, renderList, createBlock, createVNode, createElementVNode, unref, toDisplayString, renderSlot, withCtx, withModifiers, createTextVNode, normalizeClass } from "vue";
2
2
  import { ErrorCircleFilledIcon, DeleteIcon, BrowseIcon, AddIcon } from "tdesign-icons-vue-next";
3
3
  import { Loading, Image, Space, Divider, Link } from "tdesign-vue-next";
4
- import { IMAGE_REGEXP, handleAbridgeName, hasSlotContent } from "../common.mjs";
4
+ import { isImage } from "../../../utils/tools.mjs";
5
+ import { getFileType, handleAbridgeName, hasSlotContent } from "../common.mjs";
5
6
  import QdFileIcon from "../../file-icon/index.vue.mjs";
6
7
  const _hoisted_1 = { class: "t-upload__card" };
7
8
  const _hoisted_2 = {
@@ -49,12 +50,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
49
50
  const showTrigger = computed(
50
51
  () => props.trigger && !props.disabled && (!props.max || props.files.length < props.max)
51
52
  );
52
- function getFileType(file) {
53
- return file.raw?.type || file.type || file.name || "";
54
- }
55
- function isImage(file) {
56
- return IMAGE_REGEXP.test(getFileType(file));
57
- }
58
53
  function fileName(file) {
59
54
  if (!props.abridgeName || !file.name) return file.name ?? "";
60
55
  return handleAbridgeName(file.name, ...props.abridgeName);
@@ -93,7 +88,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
93
88
  ])
94
89
  ])
95
90
  ])) : (openBlock(), createElementBlock("div", _hoisted_6, [
96
- isImage(file) ? (openBlock(), createBlock(unref(Image), {
91
+ unref(isImage)(unref(getFileType)(file)) ? (openBlock(), createBlock(unref(Image), {
97
92
  key: 0,
98
93
  class: "t-upload__card-image",
99
94
  src: file.url || file.raw,
@@ -1,7 +1,8 @@
1
1
  import { defineComponent, useSlots, createElementBlock, openBlock, Fragment, renderList, createElementVNode, createCommentVNode, normalizeClass, createBlock, createVNode, unref, toDisplayString, renderSlot, withCtx, withModifiers, createTextVNode } from "vue";
2
2
  import { ErrorCircleFilledIcon, BrowseIcon, DeleteIcon, CheckCircleFilledIcon, TimeFilledIcon } from "tdesign-icons-vue-next";
3
3
  import { Loading, Image, Space, Divider } from "tdesign-vue-next";
4
- import { handleAbridgeName, IMAGE_REGEXP, hasSlotContent } from "../common.mjs";
4
+ import { isImage } from "../../../utils/tools.mjs";
5
+ import { getFileType, handleAbridgeName, hasSlotContent } from "../common.mjs";
5
6
  import QdFileIcon from "../../file-icon/index.vue.mjs";
6
7
  const _hoisted_1 = { class: "t-upload__card clearfix" };
7
8
  const _hoisted_2 = {
@@ -35,17 +36,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
35
36
  setup(__props) {
36
37
  const props = __props;
37
38
  const slots = useSlots();
38
- function getFileType(file) {
39
- return file.raw?.type || file.type || file.name || "";
40
- }
41
- function isImage(file) {
42
- return IMAGE_REGEXP.test(getFileType(file));
43
- }
44
39
  function handlePreview({ e }, index, file) {
45
40
  props.onPreview?.({ file, index, e });
46
41
  }
47
42
  function canMaskPreview(file) {
48
- return props.disabled && (Boolean(file.url) || Boolean(file.raw)) && file.status !== "progress" && file.status !== "fail" && !hasSlotContent(slots["file-middle-actions"]) && !hasSlotContent(slots["file-actions"]);
43
+ return props.disabled && file.status !== "progress" && file.status !== "fail" && !hasSlotContent(slots["file-middle-actions"]) && !hasSlotContent(slots["file-actions"]);
49
44
  }
50
45
  function handleMaskPreview(e, index, file) {
51
46
  if (!canMaskPreview(file)) return;
@@ -67,7 +62,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
67
62
  ])) : file.status === "fail" ? (openBlock(), createElementBlock("div", _hoisted_3, [
68
63
  createVNode(unref(ErrorCircleFilledIcon)),
69
64
  createElementVNode("p", null, toDisplayString(__props.locale.progress.failText), 1)
70
- ])) : (["waiting", "success"].includes(file.status) || !file.status && file.url) && isImage(file) ? (openBlock(), createBlock(unref(Image), {
65
+ ])) : (["waiting", "success"].includes(file.status) || !file.status && file.url) && unref(isImage)(unref(getFileType)(file)) ? (openBlock(), createBlock(unref(Image), {
71
66
  key: 2,
72
67
  class: "t-upload__card-image",
73
68
  src: file.url || file.raw,
@@ -33,4 +33,11 @@ export declare function getTransformOffset(el?: HTMLElement | null, options?: {
33
33
  }): {
34
34
  x: number;
35
35
  y: number;
36
+ node: HTMLElement;
37
+ } | {
38
+ x: number;
39
+ y: number;
40
+ node: null | undefined;
36
41
  };
42
+ export declare const IMAGE_REGEXP: RegExp;
43
+ export declare function isImage(value: string): boolean;
@@ -13,14 +13,20 @@ function getTransformOffset(el, options) {
13
13
  while (node) {
14
14
  if (getComputedStyle(node).transform !== "none" && !skip?.(node)) {
15
15
  const { x, y } = node.getBoundingClientRect();
16
- return { x, y };
16
+ return { x, y, node };
17
17
  }
18
18
  node = node.parentElement;
19
19
  }
20
- return { x: 0, y: 0 };
20
+ return { x: 0, y: 0, node };
21
+ }
22
+ const IMAGE_REGEXP = /(.png|.jpg|.jpeg|.jpe|.webp|.avif|.heic|.heif|.svg|.gif|.bmp)/i;
23
+ function isImage(value) {
24
+ return IMAGE_REGEXP.test(value);
21
25
  }
22
26
  export {
27
+ IMAGE_REGEXP,
23
28
  getPublicUrl,
24
29
  getTransformOffset,
30
+ isImage,
25
31
  useUICsl
26
32
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qidian-vue-ui",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "基于 TDesign 的 QiDian Vue UI 组件库",
5
5
  "author": "qidian",
6
6
  "license": "MIT",
@@ -130,7 +130,7 @@
130
130
  "vue-codemirror": "^6.1.1",
131
131
  "vue-echarts": "^8.0.1",
132
132
  "vue-stream-markdown": "1.0.1-beta.1",
133
- "qidian-shared": "1.0.76"
133
+ "qidian-shared": "1.0.77"
134
134
  },
135
135
  "scripts": {
136
136
  "clean": "rm -rf dist",