@neatui/nuxt 0.1.0 → 1.0.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.
Files changed (85) hide show
  1. package/BUILD.md +128 -0
  2. package/IMPORT_GUIDE.md +142 -0
  3. package/README.md +98 -1
  4. package/SSR_COMPATIBILITY.md +201 -0
  5. package/USAGE.md +291 -0
  6. package/nuxt.config.example.ts +37 -0
  7. package/package.json +34 -11
  8. package/src/components/basic/IDraggable.vue +87 -65
  9. package/src/components/basic/IFollowView.vue +32 -23
  10. package/src/components/basic/IRouterView.vue +38 -23
  11. package/src/components/basic/IScrollView.vue +11 -7
  12. package/src/components/basic/Icon.vue +17 -17
  13. package/src/components/basic/LayerView/Layer.vue +33 -106
  14. package/src/components/basic/follow.ts +133 -0
  15. package/src/components/display/Calendar.vue +14 -14
  16. package/src/components/display/CalendarReg.vue +14 -14
  17. package/src/components/display/Image.vue +8 -8
  18. package/src/components/display/PhotoEditor.vue +36 -36
  19. package/src/components/display/PhotoViewer.vue +8 -8
  20. package/src/components/display/Tree.vue +6 -6
  21. package/src/components/display/TreeView.vue +4 -4
  22. package/src/components/display/index.ts +2 -2
  23. package/src/components/form/Cascader.vue +19 -19
  24. package/src/components/form/Checkbox.vue +64 -0
  25. package/src/components/form/DatePicker.vue +6 -7
  26. package/src/components/form/DateRangePicker@v3.vue +4 -4
  27. package/src/components/form/DateRangeView@v3.vue +18 -19
  28. package/src/components/form/DateView.vue +14 -14
  29. package/src/components/form/DateView@v2.vue +14 -14
  30. package/src/components/form/DateView@v3.vue +331 -318
  31. package/src/components/form/ImgUpload.vue +7 -7
  32. package/src/components/form/Input@v3.vue +11 -11
  33. package/src/components/form/MoreSelect@v3.vue +87 -17
  34. package/src/components/form/MoreSelectList.vue +8 -8
  35. package/src/components/form/MoreSelectPanel@v3.vue +3 -3
  36. package/src/components/form/MoreSelectPicker.vue +7 -7
  37. package/src/components/form/MoreSelectTags.vue +8 -8
  38. package/src/components/form/PageMoreSelect.vue +14 -14
  39. package/src/components/form/PageSelect.vue +16 -16
  40. package/src/components/form/SearchMoreSelect.vue +12 -12
  41. package/src/components/form/SearchSelect@v3.vue +3 -3
  42. package/src/components/form/Select@v3.vue +229 -23
  43. package/src/components/form/SelectList.vue +8 -8
  44. package/src/components/form/SelectPicker.vue +6 -6
  45. package/src/components/form/SelectTags.vue +7 -7
  46. package/src/components/form/SelectTree/SelectTree@v1.vue +5 -5
  47. package/src/components/form/Switch.vue +38 -103
  48. package/src/components/form/TextArea.vue +18 -18
  49. package/src/components/form/Textarea@v2.vue +275 -0
  50. package/src/components/form/TimeView.vue +14 -14
  51. package/src/components/form/Upload.vue +806 -297
  52. package/src/components/form/date.ts +321 -0
  53. package/src/components/form/index.ts +7 -5
  54. package/src/components/form/number.ts +3 -0
  55. package/src/components/form/type.ts +224 -0
  56. package/src/components/icon/OrderIcon.vue +113 -0
  57. package/src/components/loader/FormLoader/FormLoader@v2.vue +193 -195
  58. package/src/components/loader/FormLoader/FormLoader@v3.vue.backup +372 -291
  59. package/src/components/loader/FormLoader/FormRender@v3.vue.backup +4 -0
  60. package/src/components/loader/FormLoader/NodeLoader.vue +85 -0
  61. package/src/components/loader/FormLoader@v1/FormLoader.vue +1 -1
  62. package/src/components/loader/FormLoader@v1/FormRender.vue +49 -24
  63. package/src/components/loader/LayerLoader/LayerLoader.vue +318 -0
  64. package/src/components/loader/LayerLoader/index.ts +2 -0
  65. package/src/components/loader/LayerLoader/style.scss +77 -0
  66. package/src/components/loader/LimitLoader/LimitLoader@v3.vue +39 -28
  67. package/src/components/loader/MoveLoader/MoveLoader.vue +628 -0
  68. package/src/components/loader/MoveLoader/index.ts +2 -0
  69. package/src/components/loader/MoveLoader/style.scss +77 -0
  70. package/src/components/loader/TableLoader/TableLoader.vue +227 -195
  71. package/src/components/loader/TableLoader/TableRender.vue +141 -0
  72. package/src/components/loader/TableLoader/index.ts +47 -0
  73. package/src/components/loader/index.ts +3 -2
  74. package/src/components/tools/Pagination@a.vue +17 -18
  75. package/src/components/tools/Pagination@b.vue +16 -16
  76. package/src/index.ts +2 -1
  77. package/src/module.ts +169 -0
  78. package/src/runtime/types.d.ts +36 -0
  79. package/src/store/{myui.ts → frame.ts} +4 -4
  80. package/src/utils/theme.ts +14 -0
  81. package/tsconfig.json +1 -1
  82. package/src/components/loader/FormLoader/index.ts +0 -2
  83. package/src/components/loader/LimitLoader/LimitLoader.vue.backup +0 -131
  84. package/src/components/loader/LimitLoader/LimitLoader@v2.vue.backup +0 -174
  85. package/src/components/loader/TableLoader/TableColView.vue +0 -115
@@ -1,345 +1,854 @@
1
1
  <template>
2
- <ul ui-flex="row lm :wrap" class="mb-ss-sub mr-ss-sub" v-bind="{ ...attrs }">
3
- <li v-for="(file, idx) in state.files" :key="idx" ui-flex="col cm" class="flex-fixed bg-fore n-sl w-ms h-ms b-solid bk-line b-xs">
4
- <img v-if="file.type === 'image'" :src="file.path" alt="" />
5
- <template v-else-if="file.type === 'pdf'">
6
- <svg t="1705645350800" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1913">
7
- <path
8
- d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
9
- fill="#B7B7BD"
10
- p-id="1914"
11
- ></path>
12
- <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="1915"></path>
13
- <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1916"></path>
14
- <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1917"></path>
15
- <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1918"></path>
16
- <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1919"></path>
17
- <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1920"></path>
18
- <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#FF4867" p-id="1921"></path>
19
- <path
20
- d="M314.229 459.289c-21.407 0-40.606-36.765-50.708-60.673-16.991-7.098-35.722-13.728-53.918-18.014-15.92 10.514-43.014 26.251-63.818 26.251-12.911 0-22.21-6.496-25.622-17.813-2.609-9.309-0.4-15.738 2.409-19.22 5.485-7.5 16.79-11.317 33.715-11.317 13.714 0 31.107 2.41 50.507 7.098 12.51-8.907 25.22-19.22 36.525-30.135-5.017-23.84-10.503-62.48 3.412-80.294 6.89-8.505 17.393-11.318 30.103-7.5 13.914 4.017 19.199 12.522 20.804 19.22 5.887 23.237-20.804 54.578-38.8 72.994 4.015 15.938 9.3 32.747 15.721 48.15 25.822 11.518 56.527 28.728 60.006 47.48 1.405 6.495-0.602 12.522-5.887 17.813-4.549 3.75-9.365 5.96-14.45 5.96z m-31.647-52.419c12.785 26.402 24.975 38.862 31.4 38.862 0.995 0 2.386-0.404 4.373-2.02 2.385-2.425 2.385-4.041 1.988-5.523-1.325-6.937-12.124-18.32-37.761-31.319z m-126.377-35.247c-16.73 0-21.33 4.093-22.73 6.003-0.399 0.614-1.599 2.455-0.399 7.23 1 4.092 3.8 8.458 12.464 8.458 10.865 0 26.595-6.207 44.857-17.325-13.063-2.933-24.594-4.366-34.192-4.366z m67.632-1.765c10.845 2.983 22.09 6.827 32.535 10.803-3.792-9.809-6.853-20.015-9.448-29.824-7.651 6.561-15.436 12.99-23.087 19.021zM265.9 259.556c-3.827 0-6.513 1.409-8.93 4.024-7.118 8.917-7.924 31.38-2.418 60.144 20.884-22.26 32.232-42.711 29.411-53.64-0.402-1.61-1.611-6.504-11.348-9.32-2.686-0.805-4.7-1.208-6.715-1.208z"
21
- fill="#FFFFFF"
22
- p-id="1922"
23
- ></path>
24
- </svg>
25
- </template>
26
- <template v-else-if="file.type === 'xlsx'">
27
- <svg t="1705645380770" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2060">
28
- <path
29
- d="M205.799024 64.936585H664.850732l231.773658 231.773659v638.376585c0 13.79353-11.18208 24.97561-24.97561 24.97561H205.799024c-13.79353 0-24.97561-11.18208-24.975609-24.97561V89.912195c0-13.79353 11.18208-24.97561 24.975609-24.97561z m448.70681 24.97561H205.799024v845.174634H871.64878V307.055141L654.505834 89.912195z"
30
- fill="#B7B7BD"
31
- p-id="2061"
32
- ></path>
33
- <path
34
- d="M664.850732 64.936585l-10.989269 23.477074v196.807804c0 13.79353 11.18208 24.97561 24.97561 24.97561h194.310244L896.62439 296.710244 664.850732 64.936585z m13.986341 49.306849L849.815102 285.221463H678.837073V114.243434z"
35
- fill="#B7B7BD"
36
- p-id="2062"
37
- ></path>
38
- <path
39
- d="M255.250732 571.441951m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
40
- fill="#B7B7BD"
41
- p-id="2063"
42
- ></path>
43
- <path
44
- d="M255.250732 707.309268m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
45
- fill="#B7B7BD"
46
- p-id="2064"
47
- ></path>
48
- <path
49
- d="M255.250732 639.37561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
50
- fill="#B7B7BD"
51
- p-id="2065"
52
- ></path>
53
- <path
54
- d="M255.250732 774.243902m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
55
- fill="#B7B7BD"
56
- p-id="2066"
57
- ></path>
58
- <path
59
- d="M255.250732 842.177561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
60
- fill="#B7B7BD"
61
- p-id="2067"
62
- ></path>
63
- <path
64
- d="M67.434146 193.810732m15.984391 0l286.72 0q15.98439 0 15.98439 15.98439l0 286.72q0 15.98439-15.98439 15.98439l-286.72 0q-15.98439 0-15.984391-15.98439l0-286.72q0-15.98439 15.984391-15.98439Z"
65
- fill="#00C090"
66
- p-id="2068"
67
- ></path>
68
- <path
69
- d="M242.569116 353.23904l89.224866 84.502478c4.337764 4.107988 4.523582 10.954302 0.415595 15.291067-4.107988 4.337764-10.954302 4.523582-15.291068 0.415595l-89.224866-84.502479-84.502478 89.224867c-4.107988 4.337764-10.954302 4.523582-15.291067 0.415594-4.337764-4.107988-4.523582-10.954302-0.415595-15.291067l84.502478-89.224867-89.224866-84.502478c-4.337764-4.107988-4.523582-10.954302-0.415594-15.291067 4.107988-4.337764 10.954302-4.523582 15.291067-0.415594l89.224867 84.502478 84.502478-89.224867c4.107988-4.337764 10.954302-4.523582 15.291067-0.415594 4.337764 4.107988 4.523582 10.954302 0.415594 15.291068l-84.502478 89.224866z"
70
- fill="#FFFFFF"
71
- p-id="2069"
72
- ></path>
73
- </svg>
74
- </template>
75
- <template v-else-if="file.type === 'docx'">
76
- <svg t="1705645401993" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2207">
77
- <path
78
- d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
79
- fill="#B7B7BD"
80
- p-id="2208"
81
- ></path>
82
- <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2209"></path>
83
- <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#4297FC" p-id="2210"></path>
84
- <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2211"></path>
85
- <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2212"></path>
86
- <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2213"></path>
87
- <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2214"></path>
88
- <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2215"></path>
89
- <path
90
- d="M315.269 451.314c7.015 7.61 19.731 2.651 19.731-7.693V271h-22.737v143.524l-76.9-83.418c-4.503-4.885-12.223-4.885-16.726 0l-76.9 83.418V271H119v172.62c0 10.345 12.716 15.303 19.731 7.694L227 355.564l88.269 95.75z"
91
- fill="#FFFFFF"
92
- p-id="2216"
93
- ></path>
94
- </svg>
95
- </template>
96
- <template v-else-if="file.type === 'pptx'">
97
- <svg t="1705645414803" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2354">
98
- <path
99
- d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
100
- fill="#B7B7BD"
101
- p-id="2355"
102
- ></path>
103
- <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2356"></path>
104
- <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2357"></path>
105
- <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2358"></path>
106
- <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2359"></path>
107
- <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2360"></path>
108
- <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2361"></path>
109
- <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#FF7A64" p-id="2362"></path>
110
- <path
111
- d="M314 312.055c0 16.701-8.014 25.218-26.96 27.787H129c-5.523 0-10 4.477-10 10V451.5c0 5.523 4.477 10 10 10s10-4.477 10-10v-91.658h148.694a10 10 0 0 0 1.264-0.08C317.98 356.065 334 339.393 334 312.055c0-26.83-15.298-44.972-43.677-52.703a10 10 0 0 0-2.629-0.352H129c-5.523 0-10 4.477-10 10s4.477 10 10 10h157.317C305.466 284.553 314 295.032 314 312.055z"
112
- fill="#FFFFFF"
113
- p-id="2363"
114
- ></path>
115
- </svg>
116
- </template>
117
- <template v-else>
118
- <svg t="1705645436666" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2501">
119
- <path
120
- d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
121
- fill="#B7B7BD"
122
- p-id="2502"
123
- ></path>
124
- <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2503"></path>
125
- <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2504"></path>
126
- <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2505"></path>
127
- <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2506"></path>
128
- <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2507"></path>
129
- <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2508"></path>
130
- <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#CCCCCC" p-id="2509"></path>
131
- <path
132
- d="M226.274 240c14.845 0 29.79 4.862 41.18 13.738C280.444 263.86 288 278.458 288 295.57c0 22.53-15.93 43.423-46.252 63.48l-0.31 0.203V387c0 6.525-5.208 11.834-11.695 11.996l-0.305 0.004c-6.627 0-12-5.373-12-12v-34.314a12 12 0 0 1 5.673-10.196C250.988 325.189 264 309.227 264 295.57c0-9.498-3.99-17.207-11.298-22.902-7.059-5.5-16.796-8.668-26.428-8.668-9.598 0-19.202 3.147-26.147 8.625-7.064 5.571-10.985 13.116-11.123 22.435l-0.004 0.51c0 6.628-5.373 12-12 12s-12-5.372-12-12c0-33.201 28.361-55.57 61.274-55.57zM230 453c9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17 7.611 17 17 17z"
133
- fill="#FFFFFF"
134
- p-id="2510"
135
- ></path>
136
- </svg>
137
- </template>
138
- <p ui-omit="1" class="fs-ss ac">{{ file.name }}</p>
139
- </li>
140
- <li v-if="(!multiple && state.files && state.files.length === 0) || (multiple && (!limit || (limit && state.files && state.files.length < limit)))">
141
- <slot>
142
- <label ui-flex="row cm" ui-form="@a type:upload tips:hover" :class="`upload w-ms h-ms n-sl bg-fore b-solid bk-line b-xs ${uploadClass}`" :style="uploadStyle">
143
- <div v-if="tips" ui-form-tips>
144
- {{ tips }}
2
+ <div ui-flex="row lm :wrap" class="neatui-upload mb-sm-sub mr-sm-sub" v-bind="{ ...attrs }">
3
+ <div v-for="(file, idx) in state.files" :key="idx" ui-flex="col cm" class="flex-fixed pr" :style="`width: ${isNumber(width) ? width + 'em' : width}`" v-bind="itemAttrs">
4
+ <div class="w-full pr" :style="`box-sizing: content-box; padding-top: ${ratio}%`">
5
+ <div class="pa full ot-no ol-no bg-fore n-sm b-solid bk-case b-xs r-sm" ui-flex="col cm">
6
+ <!-- 删除按钮 -->
7
+ <div class="pa w-xs h-xs ot-ss or-ss bg-fore co-risk lh-xs r-xl parent-hover:show z-ls" ui-flex="row cm" @click="remove(idx)">
8
+ <svg class="block" style="width: 1.2em; height: 1.2em" viewBox="0 0 1024 1024">
9
+ <path
10
+ d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66 0.3L512 563.4l-99.3 118.4-66.1-0.3c-4.4 0-8-3.5-8-8 0-1.9 0.7-3.7 1.9-5.2l130.1-155L340.5 359c-1.2-1.5-1.9-3.3-1.9-5.2 0-4.4 3.6-8 8-8l66.1 0.3L512 464.6l99.3-118.4 66-0.3c4.4 0 8 3.5 8 8 0 1.9-0.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
11
+ fill="currentColor"
12
+ />
13
+ </svg>
145
14
  </div>
146
- <input style="display: none" type="file" class="pa input" :multiple="multiple" @change="change" />
147
- <div class="co-note ac input-icon">
148
- <svg t="1705642180833" style="width: 1em; height: 1em" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4215">
15
+
16
+ <!-- 文件预览 -->
17
+ <img v-if="file.type === 'image'" class="r-ss" :src="getImageUrl(file)" alt="" />
18
+ <video v-else-if="file.type === 'video'" class="full r-ss" :src="getImageUrl(file)" controls muted></video>
19
+ <template v-else-if="file.type === 'pdf'">
20
+ <svg t="1705645350800" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1913">
21
+ <path
22
+ d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
23
+ fill="#B7B7BD"
24
+ p-id="1914"
25
+ ></path>
26
+ <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="1915"></path>
27
+ <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1916"></path>
28
+ <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1917"></path>
29
+ <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1918"></path>
30
+ <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1919"></path>
31
+ <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="1920"></path>
32
+ <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#FF4867" p-id="1921"></path>
33
+ <path
34
+ d="M314.229 459.289c-21.407 0-40.606-36.765-50.708-60.673-16.991-7.098-35.722-13.728-53.918-18.014-15.92 10.514-43.014 26.251-63.818 26.251-12.911 0-22.21-6.496-25.622-17.813-2.609-9.309-0.4-15.738 2.409-19.22 5.485-7.5 16.79-11.317 33.715-11.317 13.714 0 31.107 2.41 50.507 7.098 12.51-8.907 25.22-19.22 36.525-30.135-5.017-23.84-10.503-62.48 3.412-80.294 6.89-8.505 17.393-11.318 30.103-7.5 13.914 4.017 19.199 12.522 20.804 19.22 5.887 23.237-20.804 54.578-38.8 72.994 4.015 15.938 9.3 32.747 15.721 48.15 25.822 11.518 56.527 28.728 60.006 47.48 1.405 6.495-0.602 12.522-5.887 17.813-4.549 3.75-9.365 5.96-14.45 5.96z m-31.647-52.419c12.785 26.402 24.975 38.862 31.4 38.862 0.995 0 2.386-0.404 4.373-2.02 2.385-2.425 2.385-4.041 1.988-5.523-1.325-6.937-12.124-18.32-37.761-31.319z m-126.377-35.247c-16.73 0-21.33 4.093-22.73 6.003-0.399 0.614-1.599 2.455-0.399 7.23 1 4.092 3.8 8.458 12.464 8.458 10.865 0 26.595-6.207 44.857-17.325-13.063-2.933-24.594-4.366-34.192-4.366z m67.632-1.765c10.845 2.983 22.09 6.827 32.535 10.803-3.792-9.809-6.853-20.015-9.448-29.824-7.651 6.561-15.436 12.99-23.087 19.021zM265.9 259.556c-3.827 0-6.513 1.409-8.93 4.024-7.118 8.917-7.924 31.38-2.418 60.144 20.884-22.26 32.232-42.711 29.411-53.64-0.402-1.61-1.611-6.504-11.348-9.32-2.686-0.805-4.7-1.208-6.715-1.208z"
35
+ fill="#FFFFFF"
36
+ p-id="1922"
37
+ ></path>
38
+ </svg>
39
+ </template>
40
+ <template v-else-if="file.type === 'xlsx'">
41
+ <svg t="1705645380770" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2060">
42
+ <path
43
+ d="M205.799024 64.936585H664.850732l231.773658 231.773659v638.376585c0 13.79353-11.18208 24.97561-24.97561 24.97561H205.799024c-13.79353 0-24.97561-11.18208-24.975609-24.97561V89.912195c0-13.79353 11.18208-24.97561 24.975609-24.97561z m448.70681 24.97561H205.799024v845.174634H871.64878V307.055141L654.505834 89.912195z"
44
+ fill="#B7B7BD"
45
+ p-id="2061"
46
+ ></path>
47
+ <path
48
+ d="M664.850732 64.936585l-10.989269 23.477074v196.807804c0 13.79353 11.18208 24.97561 24.97561 24.97561h194.310244L896.62439 296.710244 664.850732 64.936585z m13.986341 49.306849L849.815102 285.221463H678.837073V114.243434z"
49
+ fill="#B7B7BD"
50
+ p-id="2062"
51
+ ></path>
52
+ <path
53
+ d="M255.250732 571.441951m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
54
+ fill="#B7B7BD"
55
+ p-id="2063"
56
+ ></path>
57
+ <path
58
+ d="M255.250732 707.309268m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
59
+ fill="#B7B7BD"
60
+ p-id="2064"
61
+ ></path>
62
+ <path
63
+ d="M255.250732 639.37561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
64
+ fill="#B7B7BD"
65
+ p-id="2065"
66
+ ></path>
67
+ <path
68
+ d="M255.250732 774.243902m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
69
+ fill="#B7B7BD"
70
+ p-id="2066"
71
+ ></path>
72
+ <path
73
+ d="M255.250732 842.177561m9.990244 0l555.457561 0q9.990244 0 9.990243 9.990244l0 0q0 9.990244-9.990243 9.990244l-555.457561 0q-9.990244 0-9.990244-9.990244l0 0q0-9.990244 9.990244-9.990244Z"
74
+ fill="#B7B7BD"
75
+ p-id="2067"
76
+ ></path>
77
+ <path
78
+ d="M67.434146 193.810732m15.984391 0l286.72 0q15.98439 0 15.98439 15.98439l0 286.72q0 15.98439-15.98439 15.98439l-286.72 0q-15.98439 0-15.984391-15.98439l0-286.72q0-15.98439 15.984391-15.98439Z"
79
+ fill="#00C090"
80
+ p-id="2068"
81
+ ></path>
149
82
  <path
150
- d="M906.212134 565.732986 565.732986 565.732986 565.732986 906.212134C565.732986 926.013685 541.666486 959.972 511.97312 959.972 482.297674 959.972 458.213254 926.013685 458.213254 906.212134L458.213254 565.732986 117.734106 565.732986C97.950475 565.732986 63.97424 541.666486 63.97424 511.97312 63.97424 482.279754 97.950475 458.213254 117.734106 458.213254L458.213254 458.213254 458.213254 117.734106C458.213254 97.950475 482.297674 63.97424 511.97312 63.97424 541.666486 63.97424 565.732986 97.950475 565.732986 117.734106L565.732986 458.213254 906.212134 458.213254C925.995765 458.213254 959.972 482.279754 959.972 511.97312 959.972 541.666486 925.995765 565.732986 906.212134 565.732986Z"
151
- p-id="4216"
83
+ d="M242.569116 353.23904l89.224866 84.502478c4.337764 4.107988 4.523582 10.954302 0.415595 15.291067-4.107988 4.337764-10.954302 4.523582-15.291067 0.415595l-89.224866-84.502479-84.502478 89.224867c-4.107988 4.337764-10.954302 4.523582-15.291067 0.415594-4.337764-4.107988-4.523582-10.954302-0.415595-15.291067l84.502478-89.224867-89.224866-84.502478c-4.337764-4.107988-4.523582-10.954302-0.415594-15.291067 4.107988-4.337764 10.954302-4.523582 15.291067-0.415594l89.224867 84.502478 84.502478-89.224867c4.107988-4.337764 10.954302-4.523582 15.291067-0.415594 4.337764 4.107988 4.523582 10.954302 0.415594 15.291068l-84.502478 89.224866z"
84
+ fill="#FFFFFF"
85
+ p-id="2069"
152
86
  ></path>
153
87
  </svg>
88
+ </template>
89
+ <template v-else-if="file.type === 'docx'">
90
+ <svg t="1705645401993" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2207">
91
+ <path
92
+ d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
93
+ fill="#B7B7BD"
94
+ p-id="2208"
95
+ ></path>
96
+ <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2209"></path>
97
+ <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#4297FC" p-id="2210"></path>
98
+ <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2211"></path>
99
+ <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2212"></path>
100
+ <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2213"></path>
101
+ <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2214"></path>
102
+ <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2215"></path>
103
+ <path
104
+ d="M315.269 451.314c7.015 7.61 19.731 2.651 19.731-7.693V271h-22.737v143.524l-76.9-83.418c-4.503-4.885-12.223-4.885-16.726 0l-76.9 83.418V271H119v172.62c0 10.345 12.716 15.303 19.731 7.694L227 355.564l88.269 95.75z"
105
+ fill="#FFFFFF"
106
+ p-id="2216"
107
+ ></path>
108
+ </svg>
109
+ </template>
110
+ <template v-else-if="file.type === 'pptx'">
111
+ <svg t="1705645414803" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2354">
112
+ <path
113
+ d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
114
+ fill="#B7B7BD"
115
+ p-id="2355"
116
+ ></path>
117
+ <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2356"></path>
118
+ <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2357"></path>
119
+ <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2358"></path>
120
+ <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2359"></path>
121
+ <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2360"></path>
122
+ <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2361"></path>
123
+ <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#FF7A64" p-id="2362"></path>
124
+ <path
125
+ d="M314 312.055c0 16.701-8.014 25.218-26.96 27.787H129c-5.523 0-10 4.477-10 10V451.5c0 5.523 4.477 10 10 10s10-4.477 10-10v-91.658h148.694a10 10 0 0 0 1.264-0.08C317.98 356.065 334 339.393 334 312.055c0-26.83-15.298-44.972-43.677-52.703a10 10 0 0 0-2.629-0.352H129c-5.523 0-10 4.477-10 10s4.477 10 10 10h157.317C305.466 284.553 314 295.032 314 312.055z"
126
+ fill="#FFFFFF"
127
+ p-id="2363"
128
+ ></path>
129
+ </svg>
130
+ </template>
131
+ <template v-else>
132
+ <svg t="1705645436666" class="w-ss h-ss" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2501">
133
+ <path
134
+ d="M205.5 64H665l232 232v639c0 13.807-11.193 25-25 25H205.5c-13.807 0-25-11.193-25-25V89c0-13.807 11.193-25 25-25z m449.145 25H205.5v846H872V306.355L654.645 89z"
135
+ fill="#B7B7BD"
136
+ p-id="2502"
137
+ ></path>
138
+ <path d="M665 64l-11 23.5v197c0 13.807 11.193 25 25 25h194.5L897 296 665 64z m14 49.355L850.145 284.5H679V113.355z" fill="#B7B7BD" p-id="2503"></path>
139
+ <path d="M255 571m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2504"></path>
140
+ <path d="M255 707m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2505"></path>
141
+ <path d="M255 639m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2506"></path>
142
+ <path d="M255 774m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2507"></path>
143
+ <path d="M255 842m10 0l556 0q10 0 10 10l0 0q0 10-10 10l-556 0q-10 0-10-10l0 0q0-10 10-10Z" fill="#B7B7BD" p-id="2508"></path>
144
+ <path d="M67 193m16 0l287 0q16 0 16 16l0 287q0 16-16 16l-287 0q-16 0-16-16l0-287q0-16 16-16Z" fill="#CCCCCC" p-id="2509"></path>
145
+ <path
146
+ d="M226.274 240c14.845 0 29.79 4.862 41.18 13.738C280.444 263.86 288 278.458 288 295.57c0 22.53-15.93 43.423-46.252 63.48l-0.31 0.203V387c0 6.525-5.208 11.834-11.695 11.996l-0.305 0.004c-6.627 0-12-5.373-12-12v-34.314a12 12 0 0 1 5.673-10.196C250.988 325.189 264 309.227 264 295.57c0-9.498-3.99-17.207-11.298-22.902-7.059-5.5-16.796-8.668-26.428-8.668-9.598 0-19.202 3.147-26.147 8.625-7.064 5.571-10.985 13.116-11.123 22.435l-0.004 0.51c0 6.628-5.373 12-12 12s-12-5.372-12-12c0-33.201 28.361-55.57 61.274-55.57zM230 453c9.389 0 17-7.611 17-17s-7.611-17-17-17-17 7.611-17 17 7.611 17 17 17z"
147
+ fill="#FFFFFF"
148
+ p-id="2510"
149
+ ></path>
150
+ </svg>
151
+ </template>
152
+
153
+ <!-- 上传状态 -->
154
+ <div class="upload-status" :data-status="file.status">
155
+ <div class="neatui-upload-icon"><i></i></div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- 上传按钮 -->
162
+ <template v-if="showUploadButton">
163
+ <slot name="btn">
164
+ <label class="pr flex-block" ui-form="@a type:upload tips:hover" :style="`width: ${isNumber(width) ? width + 'em' : width}`" v-bind="itemAttrs">
165
+ <div class="w-full pr" :style="`box-sizing: content-box; padding-top: ${ratio}%`">
166
+ <div ui-flex="row cm" :class="`pa ot-no ol-no full upload n-sl bg-fore b-solid bk-case b-xs r-sm ${uploadClass}`" :style="uploadStyle">
167
+ <div v-if="tips" ui-form-tips>
168
+ {{ tips }}
169
+ </div>
170
+ <input style="display: none" type="file" class="pa input" :multiple="!max || max > 1" :accept="accept" @change="change" />
171
+ <div class="co-note ac input-icon">
172
+ <svg t="1705642180833" style="width: 1em; height: 1em" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4215">
173
+ <path
174
+ d="M906.212134 565.732986 565.732986 565.732986 565.732986 906.212134C565.732986 926.013685 541.666486 959.972 511.97312 959.972 482.297674 959.972 458.213254 926.013685 458.213254 906.212134L458.213254 565.732986 117.734106 565.732986C97.950475 565.732986 63.97424 541.666486 63.97424 511.97312 63.97424 482.279754 97.950475 458.213254 117.734106 458.213254L458.213254 458.213254 458.213254 117.734106C458.213254 97.950475 482.297674 63.97424 511.97312 63.97424 541.666486 63.97424 565.732986 97.950475 565.732986 117.734106L565.732986 458.213254 906.212134 458.213254C925.995765 458.213254 959.972 482.279754 959.972 511.97312 959.972 541.666486 925.995765 565.732986 906.212134 565.732986Z"
175
+ p-id="4216"
176
+ ></path>
177
+ </svg>
178
+ </div>
179
+ </div>
154
180
  </div>
155
181
  </label>
156
182
  </slot>
157
- </li>
158
- </ul>
159
- <Layer :id="layerId" am="as"></Layer>
183
+ </template>
184
+ </div>
160
185
  </template>
161
186
  <script setup lang="ts">
162
- import { reactive, ref, watch } from 'vue';
163
- import { Image } from '../display';
164
- import draggable from 'vuedraggable';
187
+ import { reactive, watch, onUnmounted, computed } from 'vue';
165
188
  import Compressor from 'compressorjs';
166
- import { Layer, LayerById } from '../basic';
167
- import { isArray, isFunction } from '@fekit/utils';
168
-
169
- const layerId = Math.random().toString(36).slice(-6);
189
+ import { isNumber } from '@fekit/utils';
170
190
 
171
191
  interface Props {
172
192
  modelValue?: string | any[];
173
- // 文件限制数量
174
- limit?: number;
193
+ max?: number;
175
194
  attrs?: object;
195
+ itemAttrs?: object;
176
196
  uploadClass?: string;
177
197
  uploadStyle?: string;
178
- id?: string;
179
- tips?: string; // 提示文案
180
- // 文件类型
181
- accept?: string;
182
- // 是否可以上传多个文件
183
- multiple?: boolean;
184
- // 是否需要抠图处理
185
- crop?: boolean;
186
- // 上传路径
187
- action?: string;
188
- // 请求上传接口
198
+ tips?: string;
199
+ accept?: string | 'image/*' | 'video/*' | 'audio/*';
189
200
  upload?: (formData: FormData) => Promise<any>;
201
+ maxSize?: number;
202
+ compress?: boolean;
203
+ disabled?: boolean;
204
+ width?: number | string;
205
+ ratio?: number;
190
206
  }
191
207
 
192
208
  const props = withDefaults(defineProps<Props>(), {
209
+ modelValue: '',
193
210
  uploadClass: '',
194
211
  uploadStyle: '',
195
- id: '',
196
212
  attrs: () => ({}),
213
+ itemAttrs: () => ({}),
197
214
  tips: '',
198
- modelValue: '',
199
- accept: 'image/*',
200
- multiple: false,
201
- crop: false,
202
- action: '/common/updateFile',
203
- upload: async () => null
215
+ accept: '*',
216
+ upload: async () => null,
217
+ maxSize: 10,
218
+ compress: true,
219
+ max: 0,
220
+ disabled: false,
221
+ width: 10,
222
+ ratio: 100,
204
223
  });
205
224
 
206
- const state: any = reactive({
207
- files: []
225
+ interface UploadFile {
226
+ name: string;
227
+ type: string;
228
+ path: string;
229
+ status: 'ing' | 'yes' | 'err' | 'end';
230
+ size?: number;
231
+ progress?: number;
232
+ result?: any; // 直接保存上传函数的返回值
233
+ timer?: number; // 用于存储定时器ID
234
+ }
235
+
236
+ const state = reactive<{
237
+ files: UploadFile[];
238
+ isInternalUpdate: boolean; // 标记是否为内部更新
239
+ }>({
240
+ files: [],
241
+ isInternalUpdate: false,
208
242
  });
209
243
 
210
- const emit = defineEmits(['update:modelValue', 'change']);
211
-
212
- // const files = ref<any>(isArray(props.modelValue) ? props.modelValue : props.modelValue ? [{ imgUrl: props.modelValue }] : []);
213
-
214
- // 删除图片
215
- // const delImg = (src: any) => {
216
- // files.value = files.value.filter((item: any) => {
217
- // return item.imgUrl !== src.imgUrl;
218
- // });
219
- // };
220
-
221
- // const uploadClick = () => {
222
- // LayerById(layerId).show();
223
- // };
224
-
225
- // const update = () => {
226
- // emit('update:modelValue', isArray(props.modelValue) ? files.value : files.value.length > 0 ? files.value[0].imgUrl : '');
227
- // };
228
-
229
- const change = async (e: any = {}) => {
230
- const files = e?.target?.files || [];
231
- for (let i = 0; i < files.length; i++) {
232
- const _file: any = files[i];
233
-
234
- // 文件类型
235
- let type = '';
236
- if (_file.type === 'application/msword' || _file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
237
- type = 'docx';
238
- } else if (_file.type === 'application/vnd.ms-excel' || _file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
239
- type = 'xlsx';
240
- } else if (_file.type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') {
241
- type = 'pptx';
242
- } else if (_file.type === 'application/pdf') {
243
- type = 'pdf';
244
- } else if (_file.type === 'text/html') {
245
- type = 'html';
246
- } else if (_file.type.startsWith('image/') && _file.type !== 'image/vnd.adobe.photoshop') {
247
- type = 'image';
248
- }
249
- const file: any = { name: _file.name, type, path: URL.createObjectURL(_file), url: await props.upload(_file) };
250
- // 上传文件
251
- state.files.push(file);
252
-
253
- // if (isFunction(props.upload)) {
254
- // const src: any = await props.upload(_file);
255
- // if (src) {
256
- // file.src = src;
257
- // }
258
- // }
244
+ const emit = defineEmits(['update:modelValue', 'change', 'error']);
245
+
246
+ // 获取图片URL
247
+ const getImageUrl = (file: UploadFile): string => {
248
+ if ((file.status === 'yes' || file.status === 'end') && file.result) {
249
+ // 如果上传成功,优先使用上传返回的URL
250
+ if (typeof file.result === 'string') {
251
+ return file.result;
252
+ }
253
+ // 如果是对象,尝试获取url或path
254
+ return file.result.url || file.result.path || file.path;
255
+ }
256
+ // 否则使用预览URL
257
+ return file.path;
258
+ };
259
+
260
+ // 计算属性
261
+ const showUploadButton = computed(() => {
262
+ const currentCount = state.files.length;
263
+
264
+ if (!props.max) {
265
+ return true; // 无限制
266
+ }
267
+
268
+ return currentCount < props.max;
269
+ });
270
+
271
+ // 获取文件类型
272
+ const getFileType = (file: File): string => {
273
+ if (file.type === 'application/msword' || file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
274
+ return 'docx';
275
+ } else if (file.type === 'application/vnd.ms-excel' || file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
276
+ return 'xlsx';
277
+ } else if (file.type === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') {
278
+ return 'pptx';
279
+ } else if (file.type === 'application/pdf') {
280
+ return 'pdf';
281
+ } else if (file.type === 'text/html') {
282
+ return 'html';
283
+ } else if (file.type.startsWith('image/') && file.type !== 'image/vnd.adobe.photoshop') {
284
+ return 'image';
285
+ } else if (file.type.startsWith('video/')) {
286
+ return 'video';
287
+ }
288
+ return 'file';
289
+ };
290
+
291
+ // 从URL判断文件类型
292
+ const getFileTypeFromUrl = (url: string): string => {
293
+ const extension = url.split('.').pop()?.toLowerCase() || '';
294
+
295
+ // 图片格式
296
+ if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'bmp', 'ico'].includes(extension)) {
297
+ return 'image';
298
+ }
299
+
300
+ // 视频格式
301
+ if (['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv', 'm4v', '3gp'].includes(extension)) {
302
+ return 'video';
303
+ }
304
+
305
+ // 文档格式
306
+ if (['pdf'].includes(extension)) {
307
+ return 'pdf';
308
+ }
309
+ if (['doc', 'docx'].includes(extension)) {
310
+ return 'docx';
311
+ }
312
+ if (['xls', 'xlsx'].includes(extension)) {
313
+ return 'xlsx';
314
+ }
315
+ if (['ppt', 'pptx'].includes(extension)) {
316
+ return 'pptx';
317
+ }
318
+
319
+ return 'file';
320
+ };
321
+
322
+ // 验证文件
323
+ const validateFile = (file: File): string | null => {
324
+ // 检查文件大小
325
+ if (props.maxSize && file.size > props.maxSize * 1024 * 1024) {
326
+ return `文件大小不能超过 ${props.maxSize}MB`;
327
+ }
328
+
329
+ // 检查文件类型
330
+ if (props.accept && props.accept !== '*') {
331
+ const acceptTypes = props.accept.split(',').map((type) => type.trim());
332
+ const isValidType = acceptTypes.some((type) => {
333
+ if (type.endsWith('/*')) {
334
+ return file.type.startsWith(type.replace('/*', ''));
335
+ }
336
+ return file.type === type;
337
+ });
338
+
339
+ if (!isValidType) {
340
+ return `不支持的文件类型: ${file.type}`;
341
+ }
342
+ }
343
+
344
+ return null;
345
+ };
346
+
347
+ // 压缩图片
348
+ const compressImage = async (file: File): Promise<File> => {
349
+ if (!props.compress || !file.type.startsWith('image/')) {
350
+ return file;
351
+ }
352
+
353
+ return new Promise((resolve, reject) => {
354
+ new Compressor(file, {
355
+ quality: 0.8,
356
+ maxWidth: 1920,
357
+ maxHeight: 1080,
358
+ checkOrientation: true,
359
+ success: (result) => {
360
+ resolve(new File([result], file.name, { type: result.type }));
361
+ },
362
+ error: reject,
363
+ });
364
+ });
365
+ };
366
+
367
+ // 清除文件定时器
368
+ const clearFileTimer = (file: UploadFile) => {
369
+ if (file.timer) {
370
+ clearTimeout(file.timer);
371
+ file.timer = undefined;
372
+ }
373
+ };
374
+
375
+ // 设置状态转换定时器
376
+ const setStatusTransitionTimer = (file: UploadFile, delay: number = 2000) => {
377
+ clearFileTimer(file);
378
+ file.timer = window.setTimeout(() => {
379
+ const fileIndex = state.files.findIndex((f) => f.path === file.path);
380
+ if (fileIndex !== -1) {
381
+ if (file.status === 'yes') {
382
+ // yes 状态 3 秒后变为 end
383
+ state.files[fileIndex].status = 'end';
384
+ clearFileTimer(state.files[fileIndex]);
385
+ } else if (file.status === 'err') {
386
+ // err 状态 3 秒后清除占位(删除文件)
387
+ removeFile(fileIndex, true);
388
+ }
389
+ }
390
+ }, delay);
391
+ };
392
+
393
+ // 删除文件函数
394
+ const remove = (index: number) => {
395
+ removeFile(index, false);
396
+ };
397
+
398
+ // 内部删除文件函数
399
+ const removeFile = (index: number, isAutoRemove: boolean = false) => {
400
+ const file = state.files[index];
401
+ if (!file) return;
402
+
403
+ // 清除定时器
404
+ clearFileTimer(file);
405
+
406
+ // 释放对象URL,防止内存泄漏
407
+ if (file.path && file.path.startsWith('blob:')) {
408
+ URL.revokeObjectURL(file.path);
409
+ }
410
+
411
+ // 从数组中移除文件
412
+ state.files.splice(index, 1);
413
+
414
+ // 如果不是自动移除,发射更新事件
415
+ if (!isAutoRemove) {
416
+ emitUpdateEvents();
417
+ } else {
418
+ // 自动移除时也需要更新外部数据源
419
+ emitUpdateEvents();
420
+ }
421
+ };
422
+
423
+ // 发射更新事件
424
+ const emitUpdateEvents = () => {
425
+ // 设置内部更新标志
426
+ state.isInternalUpdate = true;
427
+
428
+ const successFiles = state.files.filter((file) => file.status === 'yes' || file.status === 'end');
429
+
430
+ // 如果没有成功文件,返回空值
431
+ if (successFiles.length === 0) {
432
+ const emptyValue = props.max === 1 ? '' : [];
433
+ emit('update:modelValue', emptyValue);
434
+ emit('change', emptyValue);
435
+ } else {
436
+ // 直接使用上传函数的返回值
437
+ const filesForModel = successFiles.map((file) => file.result);
438
+ const modelValue = props.max === 1 ? filesForModel[0] || '' : filesForModel;
439
+
440
+ emit('update:modelValue', modelValue);
441
+ emit('change', modelValue);
442
+ }
443
+
444
+ // 重置标志
445
+ setTimeout(() => {
446
+ state.isInternalUpdate = false;
447
+ }, 0);
448
+ };
449
+
450
+ // 文件上传处理
451
+ const change = (e: Event) => {
452
+ const target = e.target as HTMLInputElement;
453
+ const selectedFiles = target.files || [];
454
+
455
+ // 检查文件数量限制
456
+ if (props.max && state.files.length + selectedFiles.length > props.max) {
457
+ const errorMsg = `文件数量超过限制: ${props.max}`;
458
+ console.warn(errorMsg);
459
+ emit('error', errorMsg);
460
+ return;
461
+ }
462
+
463
+ // 步骤1:立即添加所有文件到UI,显示占位图
464
+ const validFiles: { file: File; uploadFile: UploadFile }[] = [];
465
+
466
+ for (let i = 0; i < selectedFiles.length; i++) {
467
+ const file = selectedFiles[i];
468
+
469
+ // 验证文件
470
+ const validationError = validateFile(file);
471
+ if (validationError) {
472
+ emit('error', validationError);
473
+ continue;
474
+ }
475
+
476
+ // 创建预览URL
477
+ const previewUrl = URL.createObjectURL(file);
478
+
479
+ // 添加到文件列表
480
+ const uploadFile: UploadFile = {
481
+ name: file.name,
482
+ type: getFileType(file),
483
+ path: previewUrl,
484
+ status: 'ing',
485
+ size: file.size,
486
+ };
487
+ state.files.push(uploadFile);
488
+ validFiles.push({ file, uploadFile });
259
489
  }
260
490
 
261
- // const _files = [];
262
- // for (let i = 0; i < files.length; i++) {
263
- // const file = files[i];
264
- // const blob = URL.createObjectURL(file);
265
- // _files.push({ blob, file });
266
- // }
267
- // console.log(98, _files);
268
- // for (let i = 0; i < _files.length; i++) {
269
- // let { file = '' } = _files[i];
270
- // // 若为图片,则进行压缩处理
271
- // if (/image/.test(file.type)) {
272
- // const _blob: any = await new Promise((resolve, reject) => {
273
- // new Compressor(file, {
274
- // quality: 1,
275
- // checkOrientation: false,
276
- // success: resolve,
277
- // maxWidth: 1000,
278
- // maxHeight: 700,
279
- // error: reject
280
- // });
281
- // });
282
- // const { name = '' }: any = _blob || {};
283
- // file = new File([_blob], name);
284
- // }
285
- // const formData = new FormData();
286
- // formData.append('file', file);
287
- // if (isFunction(props.upload)) {
288
- // const url: any = await props.upload(formData);
289
- // console.log(126, url);
290
- // if (url) {
291
- // files.value = [...files.value, { url }];
292
- // }
293
- // }
294
- // }
491
+ // 步骤2:异步上传所有文件
492
+ validFiles.forEach(({ file, uploadFile }) => {
493
+ uploadSingleFile(file, uploadFile);
494
+ });
495
+
496
+ // 清空input值,允许重复选择同一文件
497
+ target.value = '';
498
+ };
499
+
500
+ // 单个文件上传函数
501
+ const uploadSingleFile = async (file: File, uploadFile: UploadFile) => {
502
+ try {
503
+ // 压缩图片(如果需要)
504
+ let processedFile = file;
505
+ if (props.compress && file.type.startsWith('image/')) {
506
+ try {
507
+ processedFile = await compressImage(file);
508
+ } catch (error) {
509
+ console.warn('图片压缩失败,使用原文件:', error);
510
+ }
511
+ }
512
+
513
+ // 上传到服务器
514
+ if (typeof props.upload !== 'function') {
515
+ throw new Error('upload 函数未定义');
516
+ }
517
+
518
+ const formData = new FormData();
519
+ formData.append('file', processedFile);
520
+
521
+ // 获取文件扩展名
522
+ const fileExtension = file.name.split('.').pop()?.toLowerCase() || '';
523
+
524
+ // 通过修改upload函数调用来传递扩展名
525
+ const result = await props.upload(formData, fileExtension);
526
+
527
+ // 更新文件状态
528
+ const fileIndex = state.files.findIndex((f) => f.path === uploadFile.path);
529
+ if (fileIndex !== -1) {
530
+ if (result) {
531
+ // 直接保存上传函数的返回值
532
+ state.files[fileIndex].result = result;
533
+ state.files[fileIndex].status = 'yes';
534
+ // 设置3秒后转换为end状态的定时器
535
+ setStatusTransitionTimer(state.files[fileIndex]);
536
+ // 每个文件完成后更新外部数据源
537
+ emitUpdateEvents();
538
+ } else {
539
+ // 上传失败时先显示错误状态,3秒后移除文件
540
+ state.files[fileIndex].status = 'err';
541
+ // 设置3秒后删除文件的定时器
542
+ setStatusTransitionTimer(state.files[fileIndex]);
543
+ // 发射错误事件
544
+ emit('error', `文件 ${file.name} 上传失败: 上传函数返回空值`);
545
+ }
546
+ }
547
+ } catch (error) {
548
+ console.error('文件处理失败:', error);
549
+
550
+ // 上传失败时先显示错误状态,3秒后移除文件
551
+ const fileIndex = state.files.findIndex((f) => f.path === uploadFile.path);
552
+ if (fileIndex !== -1) {
553
+ state.files[fileIndex].status = 'err';
554
+ // 设置3秒后删除文件的定时器
555
+ setStatusTransitionTimer(state.files[fileIndex]);
556
+ }
557
+
558
+ emit('error', `文件 ${file.name} 上传失败: ${error}`);
559
+ }
295
560
  };
296
561
 
297
- // watch(
298
- // () => [props.modelValue, state.files],
299
- // (n: any, o: any) => {
300
- // if (n[0] !== o[0]) {
301
- // files.value = isArray(props.modelValue) ? props.modelValue : props.modelValue ? [{ imgUrl: props.modelValue }] : [];
302
- // }
303
- // if (n[1] !== o[1]) {
304
- // emit('update:modelValue', isArray(props.modelValue) ? files.value : files.value.length > 0 ? files.value[0].imgUrl : '');
305
- // }
306
- // },
307
- // { deep: true }
308
- // );
309
-
310
- // defineExpose({ change, uploadClick });
562
+ // 监听外部传入的modelValue变化,同步到内部状态
563
+ watch(
564
+ () => props.modelValue,
565
+ (newValue) => {
566
+ // 如果是内部更新,跳过处理
567
+ if (state.isInternalUpdate) {
568
+ return;
569
+ }
570
+ if (newValue && Array.isArray(newValue)) {
571
+ state.files = newValue.map((item) => {
572
+ if (typeof item === 'string') {
573
+ // 如果是字符串URL,尝试从URL判断类型
574
+ const urlType = getFileTypeFromUrl(item);
575
+ return {
576
+ name: 'file',
577
+ type: urlType,
578
+ path: item,
579
+ status: 'end' as const,
580
+ result: item,
581
+ };
582
+ } else {
583
+ // 如果是对象
584
+ return {
585
+ name: item.name || 'file',
586
+ type: item.type || 'file',
587
+ path: item.url || item.path || '',
588
+ status: 'end' as const,
589
+ result: item,
590
+ };
591
+ }
592
+ });
593
+ } else if (newValue && typeof newValue === 'string') {
594
+ const urlType = getFileTypeFromUrl(newValue);
595
+ state.files = [
596
+ {
597
+ name: 'file',
598
+ type: urlType,
599
+ path: newValue,
600
+ status: 'end',
601
+ result: newValue,
602
+ },
603
+ ];
604
+ } else if (newValue && typeof newValue === 'object') {
605
+ const objValue = newValue as any;
606
+ const objPath = objValue.url || objValue.path || '';
607
+ const objType = objValue.type || (objPath ? getFileTypeFromUrl(objPath) : 'file');
608
+ state.files = [
609
+ {
610
+ name: objValue.name || 'file',
611
+ type: objType,
612
+ path: objPath,
613
+ status: 'end',
614
+ result: newValue,
615
+ },
616
+ ];
617
+ } else {
618
+ state.files = [];
619
+ }
620
+ },
621
+ { immediate: true, deep: true },
622
+ );
623
+
624
+ // 组件销毁时清理资源
625
+ onUnmounted(() => {
626
+ state.files.forEach((file) => {
627
+ // 清除定时器
628
+ clearFileTimer(file);
629
+ // 释放对象URL
630
+ if (file.path && file.path.startsWith('blob:')) {
631
+ URL.revokeObjectURL(file.path);
632
+ }
633
+ });
634
+ });
311
635
  </script>
312
636
  <style lang="scss">
313
- .mc-upload {
637
+ .neatui-upload {
314
638
  flex-wrap: wrap;
315
-
316
- .upload {
317
- width: 8em;
318
- height: 8em;
639
+ // 上传状态样式
640
+ .upload-status {
641
+ position: absolute;
642
+ top: 0;
643
+ left: 0;
644
+ right: 0;
645
+ bottom: 0;
646
+ background: rgba(255, 255, 255, 0.8);
647
+ backdrop-filter: blur(0.5em);
648
+ -webkit-backdrop-filter: blur(0.5em);
649
+ display: flex;
650
+ flex-direction: column;
651
+ align-items: center;
652
+ justify-content: center;
653
+ color: #666;
654
+ font-size: 0.75em;
319
655
  border-radius: 0.25em;
320
- transition:
321
- all 0.35s,
322
- background-color 0s;
323
- // background-color: #ffffff;
324
- position: relative;
656
+ transition: opacity 0.3s ease;
325
657
 
326
- .input {
658
+ // 完成
659
+ &[data-status='end'] {
327
660
  opacity: 0;
328
- height: 100%;
661
+ pointer-events: none;
662
+ }
663
+
664
+ .status-text {
665
+ margin-top: 0.5em;
666
+ }
667
+ }
668
+
669
+ // 加载动画
670
+ .loading-spinner {
671
+ width: 1.5em;
672
+ height: 1.5em;
673
+ border: 2px solid rgba(255, 255, 255, 0.3);
674
+ border-top: 2px solid white;
675
+ border-radius: 50%;
676
+ animation: spin 1s linear infinite;
677
+ }
678
+
679
+ @keyframes spin {
680
+ 0% {
681
+ transform: rotate(0deg);
682
+ }
683
+ 100% {
684
+ transform: rotate(360deg);
685
+ }
686
+ }
687
+
688
+ // 上传动画
689
+ @keyframes am-ing {
690
+ 0% {
691
+ transform: translateY(120%);
692
+ }
693
+ 50% {
694
+ transform: translateY(0%);
695
+ }
696
+ 60% {
697
+ transform: translateY(2%);
698
+ }
699
+ 99% {
700
+ transform: translateY(-80%);
701
+ }
702
+ 100% {
703
+ transform: translateY(0%);
704
+ }
705
+ }
706
+ // 动画定义
707
+ @keyframes am-yes-a {
708
+ 0% {
709
+ transform: rotate(43deg) scale3d(0, 1, 1);
710
+ }
711
+ 40%,
712
+ 100% {
713
+ transform: rotate(43deg) scale3d(1, 1, 1);
714
+ }
715
+ }
716
+
717
+ @keyframes am-yes-b {
718
+ 0%,
719
+ 40% {
720
+ transform: rotate(-53deg) scale3d(0, 1, 1);
721
+ }
722
+ 100% {
723
+ transform: rotate(-53deg) scale3d(1, 1, 1);
724
+ }
725
+ }
726
+
727
+ @keyframes am-err-a {
728
+ 0%,
729
+ 50% {
730
+ transform: rotate(45deg) scale3d(0, 1, 1);
731
+ }
732
+ 100% {
733
+ transform: rotate(45deg) scale3d(1, 1, 1);
734
+ }
735
+ }
736
+ @keyframes am-err-b {
737
+ 0% {
738
+ transform: rotate(-45deg) scale3d(0, 1, 1);
739
+ }
740
+ 50%,
741
+ 100% {
742
+ transform: rotate(-45deg) scale3d(1, 1, 1);
743
+ }
744
+ }
745
+
746
+ .neatui-upload-icon {
747
+ position: relative;
748
+ width: 2em;
749
+ height: 2em;
750
+ border-radius: 50%;
751
+ border: 1px solid currentColor;
752
+ overflow: hidden;
753
+ }
754
+
755
+ // 上传
756
+ [data-status='ing'] .neatui-upload-icon {
757
+ color: var(--co-warn);
758
+
759
+ & > * {
760
+ position: absolute;
761
+ display: block;
329
762
  width: 100%;
763
+ height: 100%;
764
+ animation: am-ing 0.8s infinite both;
765
+
766
+ &::before,
767
+ &::after {
768
+ content: '';
769
+ position: absolute;
770
+ top: 24%;
771
+ }
772
+
773
+ &::before {
774
+ border-left: 2px solid currentColor;
775
+ border-top: 2px solid currentColor;
776
+ width: 0.7em;
777
+ height: 0.7em;
778
+ left: 30.5%;
779
+ transform: rotate(45deg);
780
+ }
781
+ &::after {
782
+ width: 2px;
783
+ height: 1em;
784
+ left: 44%;
785
+ background-color: currentColor;
786
+ }
330
787
  }
331
- .input-icon {
332
- line-height: 1.3;
788
+ }
789
+
790
+ // 成功
791
+ [data-status='yes'] .neatui-upload-icon {
792
+ color: var(--co-well);
793
+
794
+ &:before,
795
+ &:after {
796
+ position: absolute;
797
+ content: '';
798
+ display: block;
799
+ background-color: currentColor;
800
+ border-radius: 10rem;
801
+ }
802
+
803
+ &::before {
804
+ width: 35%;
805
+ height: 10%;
806
+ left: 18%;
807
+ top: 41%;
808
+ transform: rotate(43deg);
809
+ transform-origin: 0% 100%;
810
+ animation: am-yes-a ease-out 0.5s both;
811
+ }
812
+
813
+ &::after {
814
+ width: 61%;
815
+ height: 10%;
816
+ left: 46.5%;
817
+ bottom: 24.5%;
818
+ transform-origin: 0% 100%;
819
+ transform: rotate(-53deg);
820
+ animation: am-yes-b ease-out 0.5s both;
333
821
  }
334
822
  }
335
823
 
336
- &-img {
337
- display: flex;
338
- flex-direction: row;
824
+ // 失败
825
+ [data-status='err'] .neatui-upload-icon {
826
+ color: var(--co-risk);
827
+
828
+ &:before,
829
+ &:after {
830
+ position: absolute;
831
+ content: '';
832
+ display: block;
833
+ background-color: currentColor;
834
+ border-radius: 10rem;
835
+ width: 70%;
836
+ height: 10%;
837
+ top: 17.25%;
838
+ }
839
+
840
+ &::before {
841
+ left: 23%;
842
+ transform: rotate(45deg);
843
+ transform-origin: 0% 100%;
844
+ animation: am-err-a ease 0.5s both;
845
+ }
339
846
 
340
- &-item {
341
- margin-right: 0.75rem;
342
- margin-bottom: 0.75rem;
847
+ &::after {
848
+ right: 23%;
849
+ transform-origin: 100% 100%;
850
+ transform: rotate(45deg);
851
+ animation: am-err-b ease 0.5s both;
343
852
  }
344
853
  }
345
854
  }