@veltra/desktop 1.0.7 → 1.0.9

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.
Files changed (247) hide show
  1. package/dist/components/action/action-group.vue.d.ts +1 -1
  2. package/dist/components/action/action.vue.d.ts +3 -3
  3. package/dist/components/action/style.js +3 -0
  4. package/dist/components/auto-complete/auto-complete.vue.d.ts +2 -2
  5. package/dist/components/auto-complete/style.js +5 -0
  6. package/dist/components/batch-edit/style.js +9 -0
  7. package/dist/components/breadcrumb/style2.css +0 -6
  8. package/dist/components/button/style.js +1 -0
  9. package/dist/components/cascade/cascade.js +15 -13
  10. package/dist/components/cascade/cascade.js.map +1 -1
  11. package/dist/components/cascade/cascade.vue.d.ts +3 -3
  12. package/dist/components/cascade/node.js.map +1 -1
  13. package/dist/components/cascade/style.js +7 -0
  14. package/dist/components/cascade/use-check.js +3 -2
  15. package/dist/components/cascade/use-check.js.map +1 -1
  16. package/dist/components/cascade/use-select.js +3 -3
  17. package/dist/components/cascade/use-select.js.map +1 -1
  18. package/dist/components/checkbox/style.js +1 -0
  19. package/dist/components/checkbox/style2.css +0 -8
  20. package/dist/components/checkbox-group/checkbox-group.vue.d.ts +2 -2
  21. package/dist/components/checkbox-group/style.js +1 -0
  22. package/dist/components/context-menu/style.js +1 -0
  23. package/dist/components/date-panel/date-panel.vue.d.ts +1 -1
  24. package/dist/components/date-picker/date-picker.js +9 -9
  25. package/dist/components/date-picker/date-picker.js.map +1 -1
  26. package/dist/components/date-picker/date-picker.vue.d.ts +2 -2
  27. package/dist/components/date-picker/style.js +3 -0
  28. package/dist/components/date-range-picker/date-range-picker.js +9 -9
  29. package/dist/components/date-range-picker/date-range-picker.js.map +1 -1
  30. package/dist/components/date-range-picker/date-range-picker.vue.d.ts +2 -2
  31. package/dist/components/date-range-picker/style.js +3 -0
  32. package/dist/components/dialog/style.js +2 -0
  33. package/dist/components/drawer/style.js +1 -0
  34. package/dist/components/drawer/style2.css +4 -16
  35. package/dist/components/empty/style.js +1 -0
  36. package/dist/components/expression-editor/style.js +6 -0
  37. package/dist/components/file-picker/file-picker.vue.d.ts +1 -1
  38. package/dist/components/file-picker/helper.js +19 -7
  39. package/dist/components/file-picker/helper.js.map +1 -1
  40. package/dist/components/file-viewer/file-viewer.js +294 -0
  41. package/dist/components/file-viewer/file-viewer.js.map +1 -0
  42. package/dist/components/file-viewer/file-viewer.vue.d.ts +42 -0
  43. package/dist/components/file-viewer/helper.js +185 -0
  44. package/dist/components/file-viewer/helper.js.map +1 -0
  45. package/dist/components/file-viewer/index.d.ts +1 -0
  46. package/dist/components/file-viewer/previewers/docx-previewer.js +76 -0
  47. package/dist/components/file-viewer/previewers/docx-previewer.js.map +1 -0
  48. package/dist/components/file-viewer/previewers/image-previewer.js +64 -0
  49. package/dist/components/file-viewer/previewers/image-previewer.js.map +1 -0
  50. package/dist/components/file-viewer/previewers/pdf-previewer.js +90 -0
  51. package/dist/components/file-viewer/previewers/pdf-previewer.js.map +1 -0
  52. package/dist/components/file-viewer/previewers/sheet-previewer.js +175 -0
  53. package/dist/components/file-viewer/previewers/sheet-previewer.js.map +1 -0
  54. package/dist/components/file-viewer/previewers/text-previewer.js +74 -0
  55. package/dist/components/file-viewer/previewers/text-previewer.js.map +1 -0
  56. package/dist/components/file-viewer/previewers/video-previewer.js +62 -0
  57. package/dist/components/file-viewer/previewers/video-previewer.js.map +1 -0
  58. package/dist/components/file-viewer/style.d.ts +1 -0
  59. package/dist/components/file-viewer/style.js +4 -0
  60. package/dist/components/file-viewer/style2.css +445 -0
  61. package/dist/components/float-button/style.js +1 -0
  62. package/dist/components/form/style.js +2 -0
  63. package/dist/components/form-item/style.js +1 -0
  64. package/dist/components/form-item/style2.css +2 -3
  65. package/dist/components/grid-input/grid-input.vue.d.ts +1 -1
  66. package/dist/components/index.d.ts +12 -9
  67. package/dist/components/input/input.vue.d.ts +2 -2
  68. package/dist/components/input/style.js +1 -0
  69. package/dist/components/layout/style.js +1 -0
  70. package/dist/components/list/style.js +1 -0
  71. package/dist/components/loading/style2.css +16 -20
  72. package/dist/components/menu/style.js +2 -0
  73. package/dist/components/menu/style2.css +0 -78
  74. package/dist/components/multi-select/multi-select-option.js +14 -10
  75. package/dist/components/multi-select/multi-select-option.js.map +1 -1
  76. package/dist/components/multi-select/multi-select.js +16 -19
  77. package/dist/components/multi-select/multi-select.js.map +1 -1
  78. package/dist/components/multi-select/multi-select.vue.d.ts +3 -3
  79. package/dist/components/multi-select/style.js +7 -0
  80. package/dist/components/multi-tree-select/multi-tree-select.vue.d.ts +4 -4
  81. package/dist/components/multi-tree-select/style.js +7 -0
  82. package/dist/components/notification/notification.vue.d.ts +1 -1
  83. package/dist/components/number-input/number-input.vue.d.ts +2 -2
  84. package/dist/components/number-input/style.js +1 -0
  85. package/dist/components/number-range-input/number-range-input.vue.d.ts +2 -2
  86. package/dist/components/number-range-input/style.js +1 -0
  87. package/dist/components/paginator/style.js +3 -0
  88. package/dist/components/palette/di.js.map +1 -1
  89. package/dist/components/palette/palette-alpha.js +7 -10
  90. package/dist/components/palette/palette-alpha.js.map +1 -1
  91. package/dist/components/palette/palette-hue.js +5 -7
  92. package/dist/components/palette/palette-hue.js.map +1 -1
  93. package/dist/components/palette/palette-sv.js +8 -10
  94. package/dist/components/palette/palette-sv.js.map +1 -1
  95. package/dist/components/palette/palette.js +22 -24
  96. package/dist/components/palette/palette.js.map +1 -1
  97. package/dist/components/palette/style.js +3 -0
  98. package/dist/components/password-input/password-input.vue.d.ts +1 -1
  99. package/dist/components/password-input/style.js +2 -0
  100. package/dist/components/pop-confirm/pop-confirm.vue.d.ts +1 -1
  101. package/dist/components/pop-confirm/style.js +3 -0
  102. package/dist/components/progress-nodes/progress-nodes.vue.d.ts +2 -2
  103. package/dist/components/progress-nodes/style2.css +79 -33
  104. package/dist/components/radio/style2.css +0 -8
  105. package/dist/components/radio-group/radio-group.vue.d.ts +2 -2
  106. package/dist/components/radio-group/style.js +1 -0
  107. package/dist/components/rich-text-editor/rich-text-editor.vue.d.ts +2 -2
  108. package/dist/components/select/select.js +14 -12
  109. package/dist/components/select/select.js.map +1 -1
  110. package/dist/components/select/select.vue.d.ts +2 -2
  111. package/dist/components/select/style.js +5 -0
  112. package/dist/components/select/style2.css +0 -20
  113. package/dist/components/slider/slider-thumb.js +10 -13
  114. package/dist/components/slider/slider-thumb.js.map +1 -1
  115. package/dist/components/slider/slider.js +12 -15
  116. package/dist/components/slider/slider.js.map +1 -1
  117. package/dist/components/steps/style.js +2 -0
  118. package/dist/components/steps/style2.css +28 -35
  119. package/dist/components/switch/style2.css +0 -3
  120. package/dist/components/table/di.js.map +1 -1
  121. package/dist/components/table/node/row.d.ts +10 -1
  122. package/dist/components/table/node/row.js +9 -0
  123. package/dist/components/table/node/row.js.map +1 -1
  124. package/dist/components/table/style.js +7 -0
  125. package/dist/components/table/style2.css +0 -41
  126. package/dist/components/table/table-body.js +25 -26
  127. package/dist/components/table/table-body.js.map +1 -1
  128. package/dist/components/table/table-cell.js +16 -9
  129. package/dist/components/table/table-cell.js.map +1 -1
  130. package/dist/components/table/table-row.js +78 -19
  131. package/dist/components/table/table-row.js.map +1 -1
  132. package/dist/components/table/table.js +76 -34
  133. package/dist/components/table/table.js.map +1 -1
  134. package/dist/components/table/use-fixed-columns.js +27 -8
  135. package/dist/components/table/use-fixed-columns.js.map +1 -1
  136. package/dist/components/table/use-rows.js +2 -2
  137. package/dist/components/table/use-rows.js.map +1 -1
  138. package/dist/components/table/use-table.js +25 -3
  139. package/dist/components/table/use-table.js.map +1 -1
  140. package/dist/components/table-editor/style.js +2 -0
  141. package/dist/components/tabs/index.d.ts +3 -1
  142. package/dist/components/tabs/style.js +2 -0
  143. package/dist/components/tabs/style2.css +169 -107
  144. package/dist/components/tabs/tabs-horizontal.js +133 -0
  145. package/dist/components/tabs/tabs-horizontal.js.map +1 -0
  146. package/dist/components/tabs/tabs-horizontal.vue.d.ts +29 -0
  147. package/dist/components/tabs/tabs-vertical.js +89 -0
  148. package/dist/components/tabs/tabs-vertical.js.map +1 -0
  149. package/dist/components/tabs/tabs-vertical.vue.d.ts +28 -0
  150. package/dist/components/tabs/tabs.js +75 -105
  151. package/dist/components/tabs/tabs.js.map +1 -1
  152. package/dist/components/tabs/tabs.vue.d.ts +9 -23
  153. package/dist/components/tabs/use-tabs-bar.js +105 -0
  154. package/dist/components/tabs/use-tabs-bar.js.map +1 -0
  155. package/dist/components/tag/style2.css +0 -6
  156. package/dist/components/textarea/style.js +1 -0
  157. package/dist/components/textarea/textarea.vue.d.ts +2 -2
  158. package/dist/components/theme/style.js +5 -0
  159. package/dist/components/tip/tip.vue.d.ts +2 -2
  160. package/dist/components/tree/style.js +4 -0
  161. package/dist/components/tree/style2.css +45 -23
  162. package/dist/components/tree/tree-node.js +50 -35
  163. package/dist/components/tree/tree-node.js.map +1 -1
  164. package/dist/components/tree/tree.js +27 -20
  165. package/dist/components/tree/tree.js.map +1 -1
  166. package/dist/components/tree/tree.vue.d.ts +1 -1
  167. package/dist/components/tree/use-check.js +52 -14
  168. package/dist/components/tree/use-check.js.map +1 -1
  169. package/dist/components/tree/use-tree-nodes.js +51 -24
  170. package/dist/components/tree/use-tree-nodes.js.map +1 -1
  171. package/dist/components/tree-select/style.js +4 -0
  172. package/dist/components/tree-select/tree-select.vue.d.ts +3 -3
  173. package/dist/components/watermark/watermark.vue.d.ts +1 -1
  174. package/dist/index.d.ts +57 -53
  175. package/dist/index.js +51 -48
  176. package/dist/types/file-viewer.d.ts +71 -0
  177. package/dist/types/index.d.ts +4 -3
  178. package/dist/types/tabs.d.ts +106 -14
  179. package/dist/types/tree.d.ts +6 -1
  180. package/package.json +32 -31
  181. package/src/components/breadcrumb/style.scss +0 -6
  182. package/src/components/cascade/cascade.vue +18 -18
  183. package/src/components/cascade/node.ts +4 -1
  184. package/src/components/cascade/use-check.ts +5 -4
  185. package/src/components/cascade/use-select.ts +4 -5
  186. package/src/components/checkbox/style.scss +0 -4
  187. package/src/components/date-picker/date-picker.vue +10 -9
  188. package/src/components/date-range-picker/date-range-picker.vue +12 -12
  189. package/src/components/drawer/style.scss +18 -11
  190. package/src/components/file-picker/helper.ts +21 -10
  191. package/src/components/file-viewer/file-viewer.vue +311 -0
  192. package/src/components/file-viewer/helper.ts +193 -0
  193. package/src/components/file-viewer/index.ts +1 -0
  194. package/src/components/file-viewer/previewers/docx-previewer.vue +78 -0
  195. package/src/components/file-viewer/previewers/image-previewer.vue +64 -0
  196. package/src/components/file-viewer/previewers/pdf-previewer.vue +90 -0
  197. package/src/components/file-viewer/previewers/sheet-previewer.vue +210 -0
  198. package/src/components/file-viewer/previewers/text-previewer.vue +76 -0
  199. package/src/components/file-viewer/previewers/video-previewer.vue +72 -0
  200. package/src/components/file-viewer/style.scss +504 -0
  201. package/src/components/file-viewer/style.ts +4 -0
  202. package/src/components/form-item/style.scss +2 -3
  203. package/src/components/index.ts +2 -0
  204. package/src/components/loading/style.scss +125 -126
  205. package/src/components/menu/style.scss +0 -31
  206. package/src/components/multi-select/multi-select-option.vue +16 -3
  207. package/src/components/multi-select/multi-select.vue +17 -9
  208. package/src/components/palette/di.ts +5 -3
  209. package/src/components/palette/palette-alpha.vue +7 -10
  210. package/src/components/palette/palette-hue.vue +5 -7
  211. package/src/components/palette/palette-sv.vue +12 -14
  212. package/src/components/palette/palette.vue +19 -21
  213. package/src/components/progress-nodes/style.scss +28 -22
  214. package/src/components/radio/style.scss +0 -4
  215. package/src/components/select/select.vue +21 -19
  216. package/src/components/select/style.scss +0 -12
  217. package/src/components/slider/slider-thumb.vue +11 -14
  218. package/src/components/slider/slider.vue +15 -15
  219. package/src/components/steps/style.scss +10 -10
  220. package/src/components/switch/style.scss +0 -4
  221. package/src/components/table/di.ts +20 -3
  222. package/src/components/table/node/row.ts +10 -1
  223. package/src/components/table/style.scss +0 -26
  224. package/src/components/table/table-body.vue +28 -33
  225. package/src/components/table/table-cell.vue +17 -9
  226. package/src/components/table/table-row.tsx +83 -17
  227. package/src/components/table/table.vue +73 -45
  228. package/src/components/table/use-fixed-columns.ts +46 -12
  229. package/src/components/table/use-rows.ts +14 -3
  230. package/src/components/table/use-table.ts +27 -3
  231. package/src/components/tabs/index.ts +2 -0
  232. package/src/components/tabs/style.scss +217 -134
  233. package/src/components/tabs/tabs-horizontal.vue +125 -0
  234. package/src/components/tabs/tabs-vertical.vue +75 -0
  235. package/src/components/tabs/tabs.vue +56 -127
  236. package/src/components/tabs/use-tabs-bar.ts +119 -0
  237. package/src/components/tag/style.scss +0 -6
  238. package/src/components/tree/style.scss +74 -29
  239. package/src/components/tree/tree-node.vue +10 -4
  240. package/src/components/tree/tree.vue +39 -12
  241. package/src/components/tree/use-check.ts +79 -23
  242. package/src/components/tree/use-tree-nodes.ts +85 -34
  243. package/src/types/file-viewer.ts +69 -0
  244. package/src/types/index.ts +1 -0
  245. package/src/types/tabs.ts +113 -19
  246. package/src/types/tree.ts +6 -1
  247. package/dist/types/index.js +0 -0
@@ -1,5 +1,4 @@
1
1
  import { dfs, type Forest } from '@cat-kit/core'
2
- import type { Updater } from '@veltra/compositions'
3
2
  import {
4
3
  computed,
5
4
  shallowRef,
@@ -19,7 +18,7 @@ interface CheckOptions {
19
18
  disabled: Ref<boolean>
20
19
  readonly: Ref<boolean>
21
20
  forest: Ref<Forest<Record<string, unknown>, any>>
22
- updater: Updater
21
+ isUserActive: () => boolean
23
22
  getPanelItemList: (data?: CascadeNode[]) => void
24
23
  }
25
24
 
@@ -34,7 +33,8 @@ interface UseCheckReturned {
34
33
  }
35
34
 
36
35
  export function useCheck(options: CheckOptions): UseCheckReturned {
37
- const { props, emit, dataMap, disabled, readonly, forest, updater, getPanelItemList } = options
36
+ const { props, emit, dataMap, disabled, readonly, forest, isUserActive, getPanelItemList } =
37
+ options
38
38
 
39
39
  const hovered = shallowRef(false)
40
40
 
@@ -119,7 +119,8 @@ export function useCheck(options: CheckOptions): UseCheckReturned {
119
119
  watch(
120
120
  [() => props.multiple, () => props.modelValue, forest],
121
121
  ([multiple]) => {
122
- multiple && updater.update(() => initMultipleCheck())
122
+ if (!multiple || isUserActive()) return
123
+ initMultipleCheck()
123
124
  },
124
125
  { immediate: false }
125
126
  )
@@ -1,5 +1,4 @@
1
1
  import { Forest } from '@cat-kit/core'
2
- import type { Updater } from '@veltra/compositions'
3
2
  import { createIncrease } from '@veltra/utils'
4
3
  import {
5
4
  computed,
@@ -23,7 +22,7 @@ import type {
23
22
  interface SelectOptions {
24
23
  props: CascadeProps
25
24
  emit: CascadeEmits
26
- updater: Updater
25
+ isUserActive: () => boolean
27
26
  forest: Ref<Forest<Record<string, unknown>, any>>
28
27
  dataMap: Ref<Map<string, CascadeNode>>
29
28
  dropdownRef: Ref<DropdownExposed | undefined>
@@ -42,7 +41,7 @@ interface UseSelectReturned {
42
41
  }
43
42
 
44
43
  export function useSelect(options: SelectOptions): UseSelectReturned {
45
- const { props, emit, dataMap, updater, dropdownRef, forest } = options
44
+ const { props, emit, dataMap, isUserActive, dropdownRef, forest } = options
46
45
 
47
46
  /** 选中的节点key */
48
47
  const selectedNodeKeys = shallowRef<string[]>([])
@@ -152,8 +151,8 @@ export function useSelect(options: SelectOptions): UseSelectReturned {
152
151
  watch(
153
152
  [() => props.multiple, () => props.modelValue, forest],
154
153
  ([multiple]) => {
155
- if (multiple) return
156
- updater.update(() => initSingleSelect())
154
+ if (multiple || isUserActive()) return
155
+ initSingleSelect()
157
156
  },
158
157
  { immediate: true }
159
158
  )
@@ -4,10 +4,6 @@
4
4
 
5
5
  $root-name: checkbox;
6
6
  @include m.b($root-name) {
7
- @include m.dark() {
8
- --u-checkbox-border: #595959;
9
- }
10
-
11
7
  user-select: none;
12
8
  vertical-align: middle;
13
9
  cursor: pointer;
@@ -36,7 +36,7 @@
36
36
 
37
37
  <script lang="ts" setup>
38
38
  import { date, type Dater } from '@cat-kit/core'
39
- import { useFormComponent, useFormFallbackProps, useUpdateLock } from '@veltra/compositions'
39
+ import { useFormComponent, useFormFallbackProps, useUserAction } from '@veltra/compositions'
40
40
  import { Calendar } from '@veltra/icons/normal'
41
41
  import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
42
42
  import { computed, shallowRef, watch } from 'vue'
@@ -89,14 +89,13 @@ const formatStr = computed(() => {
89
89
 
90
90
  const currentDate = shallowRef<Dater>()
91
91
 
92
- const { update, updateAndLock } = useUpdateLock()
92
+ const { userAction, isUserActive } = useUserAction()
93
93
 
94
94
  watch(
95
95
  () => props.modelValue,
96
96
  (modelValue) => {
97
- update(() => {
98
- currentDate.value = modelValue ? date(modelValue) : undefined
99
- })
97
+ if (isUserActive()) return
98
+ currentDate.value = modelValue ? date(modelValue) : undefined
100
99
  },
101
100
  { immediate: true }
102
101
  )
@@ -105,11 +104,13 @@ const displayedValue = computed(() => {
105
104
  return currentDate.value?.format(formatStr.value) ?? ''
106
105
  })
107
106
 
107
+ const commitSelectedDate = userAction((date: Dater) => {
108
+ currentDate.value = date
109
+ emit('update:modelValue', date.format(formatStr.value))
110
+ })
111
+
108
112
  async function handleSelectDate(date: Dater) {
109
- await updateAndLock(() => {
110
- currentDate.value = date
111
- emit('update:modelValue', date.format(formatStr.value))
112
- })
113
+ await commitSelectedDate(date)
113
114
  dropdownRef.value?.close()
114
115
  }
115
116
 
@@ -61,8 +61,7 @@
61
61
 
62
62
  <script lang="ts" setup>
63
63
  import { date, type Dater } from '@cat-kit/core'
64
- import { useFormComponent, useFormFallbackProps } from '@veltra/compositions'
65
- import { useUpdateLock } from '@veltra/compositions'
64
+ import { useFormComponent, useFormFallbackProps, useUserAction } from '@veltra/compositions'
66
65
  import { Calendar, Close } from '@veltra/icons/normal'
67
66
  import { bem, FORM_EMPTY_CONTENT } from '@veltra/utils'
68
67
  import { computed, shallowRef, watch } from 'vue'
@@ -102,7 +101,7 @@ const { size, disabled, readonly } = useFormFallbackProps([formProps ?? {}, prop
102
101
 
103
102
  const dropdownRef = shallowRef<DropdownExposed>()
104
103
 
105
- const { update, updateAndLock } = useUpdateLock()
104
+ const { userAction, isUserActive } = useUserAction()
106
105
 
107
106
  const currentRangeDate = shallowRef<[Dater, Dater]>()
108
107
 
@@ -126,20 +125,21 @@ const formatStr = computed(() => {
126
125
  watch(
127
126
  () => props.modelValue,
128
127
  (val) => {
129
- update(() => {
130
- if (val?.length === 2) {
131
- currentRangeDate.value = [date(val[0]), date(val[1])]
132
- }
133
- })
128
+ if (isUserActive()) return
129
+ if (val?.length === 2) {
130
+ currentRangeDate.value = [date(val[0]), date(val[1])]
131
+ }
134
132
  },
135
133
  { immediate: true }
136
134
  )
137
135
 
136
+ const commitSelectedRange = userAction((rangeDate: [Dater, Dater] | undefined) => {
137
+ currentRangeDate.value = rangeDate
138
+ emit('update:modelValue', rangeDate?.map((d) => d.format(formatStr.value)) as [string, string])
139
+ })
140
+
138
141
  async function handleSelect(rangeDate: [Dater, Dater] | undefined) {
139
- await updateAndLock(() => {
140
- currentRangeDate.value = rangeDate
141
- emit('update:modelValue', rangeDate?.map((d) => d.format(formatStr.value)) as [string, string])
142
- })
142
+ await commitSelectedRange(rangeDate)
143
143
  dropdownRef.value?.close()
144
144
  }
145
145
 
@@ -39,12 +39,6 @@ $direction-map: (
39
39
  justify-content: center;
40
40
  }
41
41
 
42
- @each $direction in $directions {
43
- @include m.is($direction) {
44
- --transform: #{map.get($direction-map, $direction)};
45
- }
46
- }
47
-
48
42
  @include m.is(right, left) {
49
43
  top: $edge-offset;
50
44
  bottom: $edge-offset;
@@ -137,9 +131,22 @@ $direction-map: (
137
131
  transition: transform 0.3s ease-in-out;
138
132
  }
139
133
 
140
- @each $direction in $directions {
141
- .drawer-slide-#{$direction}-enter-from,
142
- .drawer-slide-#{$direction}-leave-to {
143
- transform: var(--transform);
144
- }
134
+ .drawer-slide-right-enter-from,
135
+ .drawer-slide-right-leave-to {
136
+ transform: #{map.get($direction-map, right)};
137
+ }
138
+
139
+ .drawer-slide-left-enter-from,
140
+ .drawer-slide-left-leave-to {
141
+ transform: #{map.get($direction-map, left)};
142
+ }
143
+
144
+ .drawer-slide-top-enter-from,
145
+ .drawer-slide-top-leave-to {
146
+ transform: #{map.get($direction-map, top)};
147
+ }
148
+
149
+ .drawer-slide-bottom-enter-from,
150
+ .drawer-slide-bottom-leave-to {
151
+ transform: #{map.get($direction-map, bottom)};
145
152
  }
@@ -1,22 +1,33 @@
1
1
  /**
2
- * 校验文件是否匹配 accept 字符串 (仅支持 MIME 类型)
3
- * @param file 文件对象
4
- * @param accept 接受的文件类型,例如:image/*,application/pdf
2
+ * 校验文件是否匹配 accept 字符串(支持 MIME 类型与通配符)。
3
+ *
4
+ * 支持的形式:
5
+ * - `*` / `*\/*`:接受所有文件
6
+ * - `image/*`:MIME 类型组匹配
7
+ * - `application/pdf`:精确 MIME 类型
8
+ * - `.pdf` / `.docx`:扩展名匹配(不区分大小写)
9
+ *
10
+ * 多个值以逗号分隔,命中任一即视为匹配。
5
11
  */
6
12
  export function matchAccept(file: File, accept?: string): boolean {
7
13
  if (!accept) return true
8
14
 
9
15
  const fileType = file.type.toLowerCase()
10
- const acceptList = accept.split(',').map((item) => item.trim().toLowerCase())
16
+ const fileName = file.name.toLowerCase()
17
+ const acceptList = accept
18
+ .split(',')
19
+ .map((item) => item.trim().toLowerCase())
20
+ .filter(Boolean)
21
+
22
+ if (!acceptList.length) return true
11
23
 
12
24
  return acceptList.some((item) => {
25
+ if (item === '*' || item === '*/*') return true
26
+ if (item.startsWith('.')) return fileName.endsWith(item)
13
27
  if (item.endsWith('/*')) {
14
- // MIME 类型组匹配 (例如:image/*)
15
- const group = item.replace('/*', '')
16
- return fileType.startsWith(group)
17
- } else {
18
- // MIME 类型精确匹配
19
- return fileType === item
28
+ const group = item.slice(0, -2)
29
+ return fileType.startsWith(`${group}/`)
20
30
  }
31
+ return fileType === item
21
32
  })
22
33
  }
@@ -0,0 +1,311 @@
1
+ <template>
2
+ <Teleport to="body" :disabled="!isModal">
3
+ <transition :name="isModal ? 'u-file-viewer-modal' : ''">
4
+ <div
5
+ v-if="!isModal || openModel"
6
+ :class="[cls.b, bem.is('modal', isModal)]"
7
+ :tabindex="isModal ? -1 : undefined"
8
+ ref="rootRef"
9
+ @mousedown.self="handleBackdropMousedown"
10
+ >
11
+ <div v-if="isModal" :class="cls.e('backdrop')" @mousedown.self="handleBackdropMousedown" />
12
+ <div :class="cls.e('inner')">
13
+ <aside v-if="showSidebar" :class="cls.e('sidebar')" :style="{ width: sidebarWidthCss }">
14
+ <header :class="cls.e('sidebar-head')">
15
+ <span :class="cls.e('sidebar-title')">文件</span>
16
+ <span :class="cls.e('sidebar-count')">{{ normalizedFiles.length }}</span>
17
+ </header>
18
+ <u-scroll tag="ul" :class="cls.e('list')">
19
+ <li
20
+ v-for="f in normalizedFiles"
21
+ :key="f.id"
22
+ :class="[cls.e('item'), bem.is('active', f.id === activeId)]"
23
+ @click="activate(f.id)"
24
+ >
25
+ <span :class="[cls.e('badge'), cls.em('badge', f.kind)]">
26
+ {{ label(f.kind) }}
27
+ </span>
28
+ <span :class="cls.e('item-meta')">
29
+ <span :class="cls.e('item-name')" :title="f.name">{{ f.name }}</span>
30
+ <span :class="cls.e('item-size')">{{ formatBytes(f.size) }}</span>
31
+ </span>
32
+ </li>
33
+ <li v-if="!normalizedFiles.length" :class="cls.e('list-empty')">
34
+ <u-empty text="暂无文件" :size="32" />
35
+ </li>
36
+ </u-scroll>
37
+ </aside>
38
+
39
+ <section :class="cls.e('stage')">
40
+ <header :class="cls.e('stage-head')">
41
+ <div :class="cls.e('stage-title')">
42
+ <span v-if="activeFile" :class="[cls.e('badge'), cls.em('badge', activeFile.kind)]">
43
+ {{ label(activeFile.kind) }}
44
+ </span>
45
+ <span :class="cls.e('stage-name')">
46
+ {{ activeFile?.name ?? '—' }}
47
+ </span>
48
+ <span v-if="activeFile?.size" :class="cls.e('stage-sub')">
49
+ {{ formatBytes(activeFile.size) }}
50
+ </span>
51
+ </div>
52
+ <div :class="cls.e('stage-actions')">
53
+ <button :class="cls.e('action')" :disabled="!hasPrev" type="button" @click="prev">
54
+ 上一个
55
+ </button>
56
+ <button :class="cls.e('action')" :disabled="!hasNext" type="button" @click="next">
57
+ 下一个
58
+ </button>
59
+ <button
60
+ v-if="downloadable && activeFile"
61
+ :class="[cls.e('action'), cls.em('action', 'primary')]"
62
+ type="button"
63
+ @click="download"
64
+ >
65
+ 下载
66
+ </button>
67
+ <button
68
+ v-if="isModal"
69
+ :class="[cls.e('action'), cls.em('action', 'icon')]"
70
+ type="button"
71
+ aria-label="关闭预览"
72
+ title="关闭"
73
+ @click="handleClose"
74
+ >
75
+ <u-icon :size="16">
76
+ <Close />
77
+ </u-icon>
78
+ </button>
79
+ </div>
80
+ </header>
81
+ <div :class="cls.e('body')">
82
+ <transition name="u-file-viewer-fade" mode="out-in">
83
+ <component
84
+ :is="PreviewerMap[activeFile.kind]"
85
+ v-if="activeFile"
86
+ :key="activeFile.id"
87
+ :file="activeFile"
88
+ :max-rows="sheetMaxRows"
89
+ @error="handleChildError"
90
+ />
91
+ <div v-else :class="cls.e('empty')">
92
+ <u-empty text="暂无可预览文件" />
93
+ </div>
94
+ </transition>
95
+ </div>
96
+ </section>
97
+ </div>
98
+ </div>
99
+ </transition>
100
+ </Teleport>
101
+ </template>
102
+
103
+ <script lang="ts" setup>
104
+ import { Close } from '@veltra/icons/normal'
105
+ import { bem, withUnit } from '@veltra/utils'
106
+ import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, shallowRef, watch } from 'vue'
107
+
108
+ import type {
109
+ _FileViewerExposed,
110
+ FileViewerEmits,
111
+ FileViewerItem,
112
+ FileViewerKind,
113
+ FileViewerProps
114
+ } from '../../types/file-viewer'
115
+ import { UEmpty } from '../empty'
116
+ import { UIcon } from '../icon'
117
+ import { UScroll } from '../scroll'
118
+ import { FILE_VIEWER_KIND_LABEL, downloadFile, formatBytes, inferKind } from './helper'
119
+
120
+ defineOptions({
121
+ name: 'FileViewer',
122
+ inheritAttrs: false
123
+ })
124
+
125
+ const props = withDefaults(defineProps<FileViewerProps>(), {
126
+ modelValue: undefined,
127
+ sidebarWidth: '280px',
128
+ sheetMaxRows: 50_000,
129
+ downloadable: true,
130
+ open: undefined,
131
+ closeOnClickBackdrop: true,
132
+ closeOnEsc: true
133
+ })
134
+
135
+ const emit = defineEmits<FileViewerEmits>()
136
+
137
+ const cls = bem('file-viewer')
138
+
139
+ const activeId = defineModel<string | undefined>('modelValue', { default: undefined })
140
+ const openModel = defineModel<boolean | undefined>('open', { default: undefined })
141
+
142
+ const rootRef = shallowRef<HTMLDivElement>()
143
+
144
+ const PreviewerMap: Record<FileViewerKind, ReturnType<typeof defineAsyncComponent>> = {
145
+ image: defineAsyncComponent(() => import('./previewers/image-previewer.vue')),
146
+ video: defineAsyncComponent(() => import('./previewers/video-previewer.vue')),
147
+ pdf: defineAsyncComponent(() => import('./previewers/pdf-previewer.vue')),
148
+ sheet: defineAsyncComponent(() => import('./previewers/sheet-previewer.vue')),
149
+ docx: defineAsyncComponent(() => import('./previewers/docx-previewer.vue')),
150
+ text: defineAsyncComponent(() => import('./previewers/text-previewer.vue'))
151
+ }
152
+
153
+ interface NormalizedFile extends FileViewerItem {
154
+ id: string
155
+ kind: FileViewerKind
156
+ }
157
+
158
+ const normalizedFiles = computed<NormalizedFile[]>(() =>
159
+ props.files.map((f, i) => ({
160
+ ...f,
161
+ id: f.id ?? `file-${i}`,
162
+ kind: inferKind(f.name, f.kind)
163
+ }))
164
+ )
165
+
166
+ const activeFile = computed<NormalizedFile | undefined>(() =>
167
+ normalizedFiles.value.find((f) => f.id === activeId.value)
168
+ )
169
+
170
+ const activeIndex = computed(() => normalizedFiles.value.findIndex((f) => f.id === activeId.value))
171
+
172
+ const hasPrev = computed(() => activeIndex.value > 0)
173
+ const hasNext = computed(
174
+ () => activeIndex.value >= 0 && activeIndex.value < normalizedFiles.value.length - 1
175
+ )
176
+
177
+ const showSidebar = computed(() => props.sidebarWidth !== false && props.sidebarWidth !== 0)
178
+
179
+ const sidebarWidthCss = computed(() => {
180
+ const w = props.sidebarWidth
181
+ if (w === false || w === 0) return undefined
182
+ return withUnit(w ?? '280px', 'px')
183
+ })
184
+
185
+ /** 是否启用模态模式:只要父组件显式传入 open(含 v-model:open),即进入模态 */
186
+ const isModal = computed(() => openModel.value !== undefined)
187
+
188
+ function label(kind: FileViewerKind): string {
189
+ return FILE_VIEWER_KIND_LABEL[kind]
190
+ }
191
+
192
+ function activate(id: string) {
193
+ if (activeId.value === id) return
194
+ activeId.value = id
195
+ const f = normalizedFiles.value.find((x) => x.id === id)
196
+ if (f) emit('change', f)
197
+ }
198
+
199
+ function prev() {
200
+ const i = activeIndex.value
201
+ if (i <= 0) return
202
+ const target = normalizedFiles.value[i - 1]
203
+ if (target) activate(target.id)
204
+ }
205
+
206
+ function next() {
207
+ const i = activeIndex.value
208
+ if (i < 0 || i >= normalizedFiles.value.length - 1) return
209
+ const target = normalizedFiles.value[i + 1]
210
+ if (target) activate(target.id)
211
+ }
212
+
213
+ async function download() {
214
+ if (!activeFile.value) return
215
+ try {
216
+ await downloadFile(activeFile.value)
217
+ } catch (err) {
218
+ emit('error', { file: activeFile.value, error: err })
219
+ }
220
+ }
221
+
222
+ function handleChildError(err: unknown) {
223
+ if (!activeFile.value) return
224
+ emit('error', { file: activeFile.value, error: err })
225
+ }
226
+
227
+ function handleClose() {
228
+ if (!isModal.value) return
229
+ openModel.value = false
230
+ }
231
+
232
+ function handleWindowKeydown(e: KeyboardEvent) {
233
+ if (e.key !== 'Escape') return
234
+ if (!isModal.value || !openModel.value) return
235
+ if (props.closeOnEsc === false) return
236
+ e.stopPropagation()
237
+ handleClose()
238
+ }
239
+
240
+ function handleBackdropMousedown() {
241
+ if (!isModal.value) return
242
+ if (props.closeOnClickBackdrop === false) return
243
+ handleClose()
244
+ }
245
+
246
+ watch(
247
+ normalizedFiles,
248
+ (files) => {
249
+ if (!files.length) {
250
+ activeId.value = undefined
251
+ return
252
+ }
253
+ if (!activeId.value || !files.some((f) => f.id === activeId.value)) {
254
+ activeId.value = files[0]!.id
255
+ }
256
+ },
257
+ { immediate: true }
258
+ )
259
+
260
+ // 模态打开时:锁定 body 滚动 + 让容器获得焦点以便接收 ESC
261
+ let previousBodyOverflow = ''
262
+ let isBodyLocked = false
263
+
264
+ function lockBody() {
265
+ if (isBodyLocked || typeof document === 'undefined') return
266
+ previousBodyOverflow = document.body.style.overflow
267
+ document.body.style.overflow = 'hidden'
268
+ isBodyLocked = true
269
+ }
270
+
271
+ function unlockBody() {
272
+ if (!isBodyLocked || typeof document === 'undefined') return
273
+ document.body.style.overflow = previousBodyOverflow
274
+ isBodyLocked = false
275
+ }
276
+
277
+ watch(
278
+ [isModal, openModel],
279
+ ([modal, open]) => {
280
+ if (modal && open) {
281
+ lockBody()
282
+ nextTick(() => {
283
+ rootRef.value?.focus()
284
+ })
285
+ if (typeof window !== 'undefined') {
286
+ window.addEventListener('keydown', handleWindowKeydown, true)
287
+ }
288
+ } else {
289
+ unlockBody()
290
+ if (typeof window !== 'undefined') {
291
+ window.removeEventListener('keydown', handleWindowKeydown, true)
292
+ }
293
+ }
294
+ },
295
+ { immediate: true }
296
+ )
297
+
298
+ onBeforeUnmount(() => {
299
+ unlockBody()
300
+ if (typeof window !== 'undefined') {
301
+ window.removeEventListener('keydown', handleWindowKeydown, true)
302
+ }
303
+ })
304
+
305
+ defineExpose<_FileViewerExposed>({
306
+ activeId,
307
+ activate,
308
+ next,
309
+ prev
310
+ })
311
+ </script>