eoss-ui 0.8.25 → 0.8.26

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 (262) hide show
  1. package/lib/button-group.js +2 -2
  2. package/lib/calendar.js +2 -2
  3. package/lib/calogin.js +236 -235
  4. package/lib/card.js +2 -2
  5. package/lib/checkbox-group.js +3 -2
  6. package/lib/clients.js +2 -2
  7. package/lib/dialog.js +2 -2
  8. package/lib/enable-drag.js +2 -2
  9. package/lib/enterprise.js +2 -2
  10. package/lib/eoss-ui.common.js +1245 -793
  11. package/lib/error-page.js +2 -2
  12. package/lib/flow.js +246 -246
  13. package/lib/form.js +566 -105
  14. package/lib/index.js +1 -1
  15. package/lib/layout.js +2 -2
  16. package/lib/login.js +3 -2
  17. package/lib/main.js +293 -293
  18. package/lib/qr-code.js +13 -13
  19. package/lib/theme-chalk/form.css +1 -1
  20. package/lib/theme-chalk/index.css +1 -1
  21. package/lib/theme-chalk/login.css +1 -1
  22. package/lib/upload.js +3 -2
  23. package/package.json +162 -162
  24. package/packages/button/index.js +5 -5
  25. package/packages/button/src/main.vue +418 -418
  26. package/packages/button-group/index.js +5 -5
  27. package/packages/calendar/index.js +5 -5
  28. package/packages/calogin/index.js +5 -5
  29. package/packages/calogin/src/plugin.js +915 -915
  30. package/packages/card/index.js +5 -5
  31. package/packages/card/src/main.vue +156 -156
  32. package/packages/cascader/index.js +5 -5
  33. package/packages/cascader/src/main.vue +168 -168
  34. package/packages/checkbox-group/index.js +5 -5
  35. package/packages/checkbox-group/src/main.vue +333 -333
  36. package/packages/clients/index.js +5 -5
  37. package/packages/data-table/index.js +5 -5
  38. package/packages/data-table/src/children.vue +46 -46
  39. package/packages/data-table/src/main.vue +1876 -1876
  40. package/packages/data-table/src/sizer.vue +195 -195
  41. package/packages/data-table-form/index.js +5 -5
  42. package/packages/data-table-form/src/checkbox.vue +101 -101
  43. package/packages/data-table-form/src/colgroup.vue +17 -17
  44. package/packages/data-table-form/src/main.vue +181 -181
  45. package/packages/data-table-form/src/radio.vue +65 -65
  46. package/packages/data-table-form/src/table.vue +233 -233
  47. package/packages/data-table-form/src/tbody.vue +336 -336
  48. package/packages/data-table-form/src/thead.vue +68 -68
  49. package/packages/date-picker/index.js +5 -5
  50. package/packages/date-picker/src/main.vue +236 -236
  51. package/packages/dialog/index.js +5 -5
  52. package/packages/enable-drag/index.js +5 -5
  53. package/packages/enterprise/index.js +5 -5
  54. package/packages/enterprise/src/main.vue +66 -66
  55. package/packages/error-page/index.js +5 -5
  56. package/packages/error-page/src/main.vue +44 -44
  57. package/packages/flow/index.js +5 -5
  58. package/packages/flow/src/component/Circulate.vue +420 -420
  59. package/packages/flow/src/component/CustomPreset.vue +324 -324
  60. package/packages/flow/src/component/FreeCirculation.vue +245 -245
  61. package/packages/flow/src/component/Preset.vue +257 -257
  62. package/packages/flow/src/component/TimeLimit.vue +190 -190
  63. package/packages/flow/src/component/taskUnionExamine.vue +763 -763
  64. package/packages/flow/src/freeStartFlow.vue +2898 -2898
  65. package/packages/flow/src/main.vue +4248 -4248
  66. package/packages/flow/src/processForm.vue +1806 -1806
  67. package/packages/flow/src/processReject.vue +311 -311
  68. package/packages/flow/src/reset.vue +952 -952
  69. package/packages/flow/src/startTaskRead.vue +698 -698
  70. package/packages/flow/src/supervise.vue +162 -162
  71. package/packages/flow-group/index.js +5 -5
  72. package/packages/flow-group/src/main.vue +750 -750
  73. package/packages/flow-list/index.js +5 -5
  74. package/packages/form/index.js +5 -5
  75. package/packages/form/src/1.json +17 -0
  76. package/packages/form/src/form-tab-item-render.vue +145 -0
  77. package/packages/form/src/main.vue +46 -0
  78. package/packages/handle-user/index.js +5 -5
  79. package/packages/handle-user/src/main.vue +137 -137
  80. package/packages/handler/index.js +5 -5
  81. package/packages/handler/src/main.vue +499 -499
  82. package/packages/icon/index.js +5 -5
  83. package/packages/icons/index.js +5 -5
  84. package/packages/icons/src/main.vue +81 -81
  85. package/packages/input/index.js +5 -5
  86. package/packages/input/src/main.vue +356 -356
  87. package/packages/input-number/index.js +5 -5
  88. package/packages/input-number/src/main.vue +106 -106
  89. package/packages/label/index.js +5 -5
  90. package/packages/label/src/main.vue +457 -457
  91. package/packages/layout/index.js +5 -5
  92. package/packages/layout/src/item.vue +152 -152
  93. package/packages/layout/src/main.vue +31 -31
  94. package/packages/login/index.js +5 -5
  95. package/packages/login/src/main.vue +2088 -2088
  96. package/packages/main/index.js +5 -5
  97. package/packages/main/src/default/message.vue +249 -249
  98. package/packages/main/src/default/notice.vue +157 -157
  99. package/packages/main/src/default/userinfo.vue +513 -513
  100. package/packages/main/src/simplicity/index.vue +2403 -2403
  101. package/packages/main/src/simplicity/lists.vue +84 -84
  102. package/packages/main/src/simplicity/router-page.vue +45 -45
  103. package/packages/main/src/simplicity/userinfo.vue +408 -408
  104. package/packages/main/src/simplicityTop/index.vue +2688 -2688
  105. package/packages/main/src/simplicityTop/userinfo.vue +408 -408
  106. package/packages/menu/index.js +5 -5
  107. package/packages/menu/src/main.vue +597 -597
  108. package/packages/nav/index.js +5 -5
  109. package/packages/notify/index.js +5 -5
  110. package/packages/notify/src/main.vue +538 -538
  111. package/packages/page/index.js +5 -5
  112. package/packages/page/src/main.vue +167 -167
  113. package/packages/pagination/index.js +5 -5
  114. package/packages/pagination/src/main.vue +96 -96
  115. package/packages/player/index.js +5 -5
  116. package/packages/player/src/main.vue +194 -194
  117. package/packages/qr-code/index.js +5 -5
  118. package/packages/qr-code/src/main.vue +170 -170
  119. package/packages/radio-group/index.js +6 -6
  120. package/packages/radio-group/src/main.vue +319 -319
  121. package/packages/retrial-auth/index.js +5 -5
  122. package/packages/retrial-auth/src/main.vue +299 -299
  123. package/packages/select/index.js +5 -5
  124. package/packages/select-ganged/index.js +5 -5
  125. package/packages/select-ganged/src/main.vue +724 -724
  126. package/packages/selector/index.js +5 -5
  127. package/packages/selector-panel/index.js +5 -5
  128. package/packages/selector-panel/src/tree.vue +129 -129
  129. package/packages/sizer/index.js +5 -5
  130. package/packages/sizer/src/main.vue +257 -257
  131. package/packages/steps/index.js +5 -5
  132. package/packages/steps/src/main.vue +181 -181
  133. package/packages/switch/index.js +5 -5
  134. package/packages/table-form/index.js +5 -5
  135. package/packages/tabs/index.js +5 -5
  136. package/packages/tabs/src/main.vue +788 -788
  137. package/packages/tabs-panel/index.js +5 -5
  138. package/packages/tabs-panel/src/main.vue +29 -29
  139. package/packages/theme-chalk/lib/base.css +1 -0
  140. package/packages/theme-chalk/lib/button-group.css +1 -0
  141. package/packages/theme-chalk/lib/button.css +1 -0
  142. package/packages/theme-chalk/lib/calendar.css +1 -0
  143. package/packages/theme-chalk/lib/calogin.css +0 -0
  144. package/packages/theme-chalk/lib/card.css +1 -0
  145. package/packages/theme-chalk/lib/cascader.css +0 -0
  146. package/packages/theme-chalk/lib/checkbox-group.css +1 -0
  147. package/packages/theme-chalk/lib/clients.css +1 -0
  148. package/packages/theme-chalk/lib/data-table-form.css +1 -0
  149. package/packages/theme-chalk/lib/data-table.css +1 -0
  150. package/packages/theme-chalk/lib/date-picker.css +1 -0
  151. package/packages/theme-chalk/lib/dialog.css +1 -0
  152. package/packages/theme-chalk/lib/enable-drag.css +1 -0
  153. package/packages/theme-chalk/lib/enterprise.css +1 -0
  154. package/packages/theme-chalk/lib/error-page.css +1 -0
  155. package/packages/theme-chalk/lib/flow-chart.css +0 -0
  156. package/packages/theme-chalk/lib/flow-group.css +1 -0
  157. package/packages/theme-chalk/lib/flow-list.css +1 -0
  158. package/packages/theme-chalk/lib/flow.css +1 -0
  159. package/packages/theme-chalk/lib/fonts/iconfont.ttf +0 -0
  160. package/packages/theme-chalk/lib/fonts/iconfont.woff +0 -0
  161. package/packages/theme-chalk/lib/form.css +1 -0
  162. package/packages/theme-chalk/lib/handle-user.css +1 -0
  163. package/packages/theme-chalk/lib/handler.css +1 -0
  164. package/packages/theme-chalk/lib/icon.css +1 -0
  165. package/packages/theme-chalk/lib/icons.css +1 -0
  166. package/packages/theme-chalk/lib/index.css +1 -0
  167. package/packages/theme-chalk/lib/input-number.css +0 -0
  168. package/packages/theme-chalk/lib/input.css +1 -0
  169. package/packages/theme-chalk/lib/label.css +1 -0
  170. package/packages/theme-chalk/lib/layout.css +1 -0
  171. package/packages/theme-chalk/lib/login.css +1 -0
  172. package/packages/theme-chalk/lib/main.css +1 -0
  173. package/packages/theme-chalk/lib/menu.css +1 -0
  174. package/packages/theme-chalk/lib/nav.css +1 -0
  175. package/packages/theme-chalk/lib/notify.css +0 -0
  176. package/packages/theme-chalk/lib/page.css +1 -0
  177. package/packages/theme-chalk/lib/pagination.css +1 -0
  178. package/packages/theme-chalk/lib/player.css +1 -0
  179. package/packages/theme-chalk/lib/qr-code.css +1 -0
  180. package/packages/theme-chalk/lib/radio-group.css +1 -0
  181. package/packages/theme-chalk/lib/retrial-auth.css +1 -0
  182. package/packages/theme-chalk/lib/select-ganged.css +1 -0
  183. package/packages/theme-chalk/lib/select.css +1 -0
  184. package/packages/theme-chalk/lib/selector-panel.css +1 -0
  185. package/packages/theme-chalk/lib/selector.css +1 -0
  186. package/packages/theme-chalk/lib/simplicity-top.css +1 -0
  187. package/packages/theme-chalk/lib/simplicity.css +1 -0
  188. package/packages/theme-chalk/lib/sizer.css +1 -0
  189. package/packages/theme-chalk/lib/steps.css +1 -0
  190. package/packages/theme-chalk/lib/switch.css +1 -0
  191. package/packages/theme-chalk/lib/table-form.css +0 -0
  192. package/packages/theme-chalk/lib/tabs-panel.css +0 -0
  193. package/packages/theme-chalk/lib/tabs.css +1 -0
  194. package/packages/theme-chalk/lib/tips.css +1 -0
  195. package/packages/theme-chalk/lib/toolbar.css +1 -0
  196. package/packages/theme-chalk/lib/tree-group.css +1 -0
  197. package/packages/theme-chalk/lib/tree.css +1 -0
  198. package/packages/theme-chalk/lib/upload.css +1 -0
  199. package/packages/theme-chalk/lib/wujie.css +0 -0
  200. package/packages/theme-chalk/lib/wxlogin.css +1 -0
  201. package/packages/theme-chalk/src/base.scss +261 -261
  202. package/packages/theme-chalk/src/button-group.scss +176 -176
  203. package/packages/theme-chalk/src/button.scss +24 -24
  204. package/packages/theme-chalk/src/calendar.scss +113 -113
  205. package/packages/theme-chalk/src/card.scss +99 -99
  206. package/packages/theme-chalk/src/checkbox-group.scss +8 -8
  207. package/packages/theme-chalk/src/clients.scss +87 -87
  208. package/packages/theme-chalk/src/data-table-form.scss +67 -67
  209. package/packages/theme-chalk/src/date-picker.scss +7 -7
  210. package/packages/theme-chalk/src/dialog.scss +77 -77
  211. package/packages/theme-chalk/src/enable-drag.scss +181 -181
  212. package/packages/theme-chalk/src/enterprise.scss +5 -5
  213. package/packages/theme-chalk/src/error-page.scss +18 -18
  214. package/packages/theme-chalk/src/flow-group.scss +110 -110
  215. package/packages/theme-chalk/src/flow.scss +376 -376
  216. package/packages/theme-chalk/src/form.scss +3 -0
  217. package/packages/theme-chalk/src/handle-user.scss +40 -40
  218. package/packages/theme-chalk/src/icons.scss +99 -99
  219. package/packages/theme-chalk/src/input.scss +9 -9
  220. package/packages/theme-chalk/src/label.scss +24 -24
  221. package/packages/theme-chalk/src/layout.scss +46 -46
  222. package/packages/theme-chalk/src/mixins/color.scss +117 -117
  223. package/packages/theme-chalk/src/nav.scss +111 -111
  224. package/packages/theme-chalk/src/page.scss +3 -3
  225. package/packages/theme-chalk/src/pagination.scss +29 -29
  226. package/packages/theme-chalk/src/player.scss +9 -9
  227. package/packages/theme-chalk/src/qr-code.scss +17 -17
  228. package/packages/theme-chalk/src/radio-group.scss +9 -9
  229. package/packages/theme-chalk/src/retrial-auth.scss +38 -38
  230. package/packages/theme-chalk/src/select-ganged.scss +8 -8
  231. package/packages/theme-chalk/src/select.scss +8 -8
  232. package/packages/theme-chalk/src/selector-panel.scss +204 -204
  233. package/packages/theme-chalk/src/sizer.scss +36 -36
  234. package/packages/theme-chalk/src/steps.scss +88 -88
  235. package/packages/theme-chalk/src/switch.scss +3 -3
  236. package/packages/theme-chalk/src/table-form.scss +1 -1
  237. package/packages/theme-chalk/src/tabs.scss +87 -87
  238. package/packages/theme-chalk/src/tips.scss +7 -7
  239. package/packages/theme-chalk/src/toolbar.scss +179 -179
  240. package/packages/theme-chalk/src/tree-group.scss +72 -72
  241. package/packages/theme-chalk/src/tree.scss +167 -167
  242. package/packages/theme-chalk/src/wxlogin.scss +3 -3
  243. package/packages/tips/index.js +5 -5
  244. package/packages/toolbar/index.js +5 -5
  245. package/packages/toolbar/src/main.vue +430 -430
  246. package/packages/tree/index.js +5 -5
  247. package/packages/tree-group/index.js +5 -5
  248. package/packages/upload/index.js +5 -5
  249. package/packages/upload/src/main.vue +1813 -1813
  250. package/packages/upload/src/picture.js +15 -15
  251. package/packages/wujie/index.js +5 -5
  252. package/packages/wxlogin/index.js +5 -5
  253. package/packages/wxlogin/src/main.vue +128 -128
  254. package/src/config/api.js +356 -356
  255. package/src/config/image.js +2 -2
  256. package/src/index.js +1 -1
  257. package/src/utils/bus.js +3 -3
  258. package/src/utils/date-util.js +312 -312
  259. package/src/utils/http.js +50 -50
  260. package/src/utils/store.js +21 -21
  261. package/src/utils/webSocket.js +107 -107
  262. package/packages/theme-chalk/gulpfile.js +0 -25
@@ -1,1813 +1,1813 @@
1
- <template>
2
- <el-upload
3
- v-if="isRender && uploadUrl"
4
- ref="upload"
5
- v-bind="$attrs"
6
- class="es-upload"
7
- :portrait="portrait"
8
- :list-type="listType"
9
- :wait="1000"
10
- :height="boxHeight"
11
- :multiple="portrait ? false : multiple"
12
- :action="uploadUrl"
13
- :http-request="chunkHttpRequest"
14
- :handles="chunkHandles"
15
- :show-file-list="showList"
16
- :file-list="lists"
17
- :result-file="resultFile"
18
- :disabled="isDisabled || isReadonly"
19
- :readonly="isReadonly"
20
- :class="[
21
- {
22
- 'es-avatar-uploader': portrait && !$slots.default,
23
- 'es-upload-readonly': isReadonly && !downloads
24
- },
25
- 'es-upload-' + listType
26
- ]"
27
- :previewAdjunct="host + previewAdjunct"
28
- :headers="headers"
29
- :data="datas"
30
- :show-info="showList ? showInfo : undefined"
31
- :auto-upload="autoUpload"
32
- :response="true"
33
- :operate="operate"
34
- :accept="accepts"
35
- :drag-sort="isSort"
36
- :preview="preview"
37
- :exclude="excludes"
38
- :download="isDownload"
39
- :deleted="isRemove"
40
- :fileIcons="_icons"
41
- :picture="_picture"
42
- :before-upload="handleBeforeUpload"
43
- :before-remove="handleBeforeRemove"
44
- :on-preview="handlePreview"
45
- :on-downloads="handleDownloads"
46
- :on-download="handleDownload"
47
- :on-sort="handleSort"
48
- :on-success="handleSuccess"
49
- :on-change="handleChange"
50
- :on-remove="handleRemove"
51
- :on-error="handleError"
52
- >
53
- <template v-if="prepend">
54
- <template v-if="Array.isArray(prepend)">
55
- <el-button
56
- v-for="(item, index) in prepend"
57
- v-bind="{ ...item, size: btnSize }"
58
- :key="index"
59
- @click.stop="handleClick(item)"
60
- >
61
- {{ item.label || item.text }}
62
- </el-button>
63
- </template>
64
- <el-button
65
- v-else
66
- v-bind="{ ...prepend, size: btnSize }"
67
- @click.stop="handleClick(prepend)"
68
- >
69
- {{ prepend.label || prepend.text }}
70
- </el-button>
71
- </template>
72
- <slot>
73
- <expand-dom v-if="render" :render="render"></expand-dom>
74
- <template v-else>
75
- <template v-if="portrait">
76
- <img
77
- v-if="profile"
78
- :src="profile"
79
- class="avatar"
80
- :style="{ width: width, height: height }"
81
- />
82
- <i
83
- v-else
84
- class="avatar-uploader-icon"
85
- :class="{ 'el-icon-plus': !isReadonly, 'is-disabled': isDisabled }"
86
- :style="{ width: width, height: height, lineHeight: height }"
87
- ></i>
88
- </template>
89
- <template v-else>
90
- <template v-if="!isReadonly">
91
- <i
92
- class="el-icon-plus es-uploader-icon"
93
- :class="{ 'is-disabled': isDisabled }"
94
- v-if="selectType === 'icon-plus'"
95
- ></i>
96
- <el-button
97
- v-else
98
- class="es-upload-button"
99
- :class="icon"
100
- :type="selectType"
101
- :size="btnSize"
102
- :disabled="isDisabled"
103
- >{{ text ? text : autoUpload ? '点击上传' : '选择文件' }}
104
- </el-button>
105
- <el-button
106
- class="es-upload-button"
107
- v-if="!autoUpload"
108
- :type="uploadType"
109
- :size="btnSize"
110
- :disabled="isDisabled"
111
- @click.stop="handleUpload"
112
- >
113
- 上传文件
114
- </el-button>
115
- </template>
116
- <el-button
117
- v-if="showFileList && isDownloads"
118
- slot="handle"
119
- class="es-upload-button es-upload-downloads"
120
- :size="btnSize"
121
- @click.stop="handleDownloads"
122
- >
123
- 批量下载
124
- </el-button>
125
- </template>
126
- </template>
127
- </slot>
128
- <template v-if="append">
129
- <template v-if="Array.isArray(append)">
130
- <el-button
131
- v-for="(item, index) in append"
132
- v-bind="{ ...item, size: btnSize }"
133
- :key="index"
134
- @click.stop="handleClick(item)"
135
- >
136
- {{ item.label || item.text }}
137
- </el-button>
138
- </template>
139
- <el-button
140
- v-else
141
- v-bind="{ ...append, size: btnSize }"
142
- @click.stop="handleClick(append)"
143
- >
144
- {{ append.label || append.text }}
145
- </el-button>
146
- </template>
147
- <template slot="tip">
148
- <slot name="tip">
149
- <div v-if="tips" class="el-upload__tip">{{ tips }}</div>
150
- </slot>
151
- </template>
152
- <el-image-viewer
153
- slot="dialog"
154
- v-if="previewScale && showImg"
155
- :z-index="9999"
156
- :initial-index="0"
157
- :url-list="[imgUrl]"
158
- :on-close="closeViewer"
159
- ></el-image-viewer>
160
- <es-dialog v-else :title="title" :visible.sync="showImg" slot="dialog">
161
- <div
162
- v-loading="imgChange"
163
- element-loading-background="rgba(0, 0, 0, 0.8)"
164
- class="es-upload-dialog"
165
- >
166
- <img v-show="!imgChange" ref="showImg" :src="imgUrl" :style="styles" />
167
- </div>
168
- </es-dialog>
169
- <es-dialog :title="title" :visible.sync="showVideo" slot="dialog">
170
- <es-player
171
- autoplay
172
- type="video/mp4"
173
- :is-pause="!showVideo"
174
- :source="source"
175
- ></es-player>
176
- </es-dialog>
177
- </el-upload>
178
- </template>
179
- <script>
180
- import {
181
- chunkCheckFile,
182
- chunkMergeChunk,
183
- chunkUploadChunk,
184
- delAdjunct,
185
- downloadByAdjunctId,
186
- getAdjunctFileInfos,
187
- getAdjunctProperties,
188
- getDochubBucket,
189
- previewAdjunct,
190
- previewAdjunct2,
191
- previewAdjunctOffice,
192
- uploadDownloads,
193
- uploadOnlyOne,
194
- uploads,
195
- uploadSort
196
- } from 'eoss-ui/src/config/api.js';
197
- import { debounce } from 'throttle-debounce';
198
- import util from 'eoss-ui/src/utils/util.js';
199
- import picture from './picture.js';
200
- import store from 'eoss-ui/src/utils/store';
201
- import { Base64 } from 'js-base64';
202
- import SparkMD5 from 'spark-md5';
203
-
204
- export default {
205
- name: 'EsUpload',
206
- components: {
207
- expandDom: {
208
- functional: true,
209
- props: {
210
- render: [Function, String]
211
- },
212
- render: (h, ctx) => {
213
- if (typeof ctx.props.render == 'string') {
214
- try {
215
- return util.toFunction(ctx.props.render)(h, params);
216
- } catch (error) {
217
- throw error;
218
- }
219
- }
220
- return ctx.props.render(h);
221
- }
222
- }
223
- },
224
- inheritAttrs: false,
225
- inject: {
226
- elForm: {
227
- default: ''
228
- },
229
- elFormItem: {
230
- default: ''
231
- }
232
- },
233
- props: {
234
- prepend: [Array, Object],
235
- append: [Array, Object],
236
- render: Function,
237
- template: String,
238
- value: [String, Array, Object],
239
- name: String,
240
- method: {
241
- type: String,
242
- default: 'get'
243
- },
244
- text: String,
245
- icon: {
246
- type: Boolean,
247
- default: true
248
- },
249
- headers: Object,
250
- listType: {
251
- type: String,
252
- default: 'table' // text,table,picture,picture-card
253
- },
254
- operate: {
255
- type: Boolean,
256
- default: true
257
- },
258
- accept: String,
259
- // 附件code
260
- code: String,
261
- // 业务id
262
- ownId: String,
263
- //附件文档id
264
- documentId: String,
265
- requiredOwnId: {
266
- type: Boolean,
267
- default: true
268
- },
269
- //
270
- downloads: {
271
- type: Boolean,
272
- default: false
273
- },
274
- // 附件扩展编码
275
- extendCode: String,
276
- // 是否添加文件md5值
277
- fileMd5: {
278
- type: Boolean,
279
- default: false
280
- },
281
- // 额外的参数
282
- data: {
283
- type: Object,
284
- default() {
285
- return {};
286
- }
287
- },
288
- param: {
289
- type: Object,
290
- default() {
291
- return {};
292
- }
293
- },
294
- // 单文件大小
295
- size: Number,
296
- totalSize: Number,
297
- // 是否头像模式上传
298
- portrait: {
299
- type: Boolean,
300
- default: false
301
- },
302
- photo: [String, Boolean],
303
- //头像按钮框宽度
304
- width: String,
305
- //头像按钮框高度度
306
- height: String,
307
- // 自定义的文字提示
308
- tips: {
309
- type: String,
310
- default: ''
311
- },
312
- dragSort: {
313
- type: Boolean,
314
- default: false
315
- },
316
- // 上传成功后绑定到file的内容
317
- showInfo: {
318
- type: Array,
319
- default() {
320
- return [
321
- { label: '上传时间', field: 'uploadTime', width: 170 },
322
- { label: '大小', field: 'fileSize', width: 100 },
323
- { label: '上传人', field: 'userName', width: 100 }
324
- ];
325
- }
326
- },
327
- // 上传地址
328
- action: String,
329
- // 自动上传
330
- autoUpload: {
331
- type: Boolean,
332
- default: true
333
- },
334
- // 是否支持多选文件
335
- multiple: {
336
- type: Boolean,
337
- default: true
338
- },
339
- // 显示文件列表
340
- showFileList: {
341
- type: Boolean,
342
- default: true
343
- },
344
- // 上传的文件列表
345
- fileList: [Array, String],
346
- requestFiles: {
347
- type: Boolean,
348
- default: true
349
- },
350
- filesParam: {
351
- type: Object,
352
- default() {
353
- return {};
354
- }
355
- },
356
- state: {
357
- type: Number,
358
- default: 1
359
- },
360
- selectType: {
361
- type: String,
362
- default: 'primary'
363
- },
364
- uploadType: {
365
- type: String,
366
- default: 'success'
367
- },
368
- host: {
369
- type: String,
370
- default() {
371
- return util.getStorage('host') ? util.getStorage('host') : '';
372
- }
373
- },
374
- message: Object,
375
- btnSize: {
376
- type: String,
377
- default: 'medium'
378
- },
379
- // 只读
380
- readonly: {
381
- type: Boolean,
382
- default: false
383
- },
384
- disabled: Boolean,
385
- preview: { type: [Boolean, String], default: true },
386
- exclude: String,
387
- download: { type: [Boolean, String], default: true },
388
- deleted: { type: [Boolean, String], default: true },
389
- remove: { type: [Boolean, String], default: true },
390
- beforeUpload: Function,
391
- onPreview: Function,
392
- // 文件列表排序
393
- onSort: Function,
394
- beforeRemove: Function,
395
- onDownloads: Function,
396
- onDownload: Function,
397
- onSort: Function,
398
- onSuccess: Function,
399
- onChange: Function,
400
- onRemove: Function,
401
- onError: Function,
402
- dangerouslyUseHTMLString: Boolean,
403
- fileCount: Number,
404
- listHeight: String,
405
- display: Boolean,
406
- resultFile: {
407
- type: Boolean,
408
- default: true
409
- },
410
- properties: {
411
- type: String,
412
- default() {
413
- return getAdjunctProperties;
414
- }
415
- },
416
- fileIcons: {
417
- type: Object,
418
- default() {
419
- return {};
420
- }
421
- },
422
- picture: {
423
- type: Object,
424
- default() {
425
- return {};
426
- }
427
- },
428
- previewScale: {
429
- type: Boolean,
430
- default: true
431
- },
432
- useCaseCode: String
433
- },
434
- data() {
435
- return {
436
- uploadUrl: this.action,
437
- lists: [],
438
- image: '',
439
- fileAccept: '',
440
- fileSize: 0,
441
- fileTotalSize: 0,
442
- filesTotalSize: 0,
443
- showImg: false,
444
- imgChange: false,
445
- title: '',
446
- imgUrl: '',
447
- styles: {},
448
- showVideo: false,
449
- videoChange: false,
450
- source: '',
451
- excludeNames: undefined,
452
- boxHeight: this.listHeight,
453
- previewAdjunct: previewAdjunct,
454
- kkfileview: null,
455
- dochubConfig: {},
456
- // 文档中台桶配置下发的分片上传配置(baseBucketConfig),null 表示未下发 → 回退组件 props
457
- bucketChunkConfig: null
458
- };
459
- },
460
- computed: {
461
- isRender() {
462
- let useCaseCodes = util.getStorage('useCaseCodes');
463
- if (useCaseCodes && this.useCaseCode) {
464
- return useCaseCodes.indexOf(this.useCaseCode) > -1;
465
- }
466
- return true;
467
- },
468
- _icons() {
469
- return {
470
- ppt: 'es-icon-ppt',
471
- png: 'es-icon-tupian',
472
- gif: 'es-icon-tupian',
473
- jpg: 'es-icon-tupian',
474
- jpeg: 'es-icon-tupian',
475
- xls: 'es-icon-Excel',
476
- xlsx: 'es-icon-Excel',
477
- docx: 'es-icon-word',
478
- doc: 'es-icon-word',
479
- rar: 'es-icon-yasuobao',
480
- zip: 'es-icon-yasuobao',
481
- txt: 'es-icon-txt',
482
- ipa: 'es-icon-ios',
483
- apk: 'es-icon-android',
484
- mp4: 'es-icon-kebofangwenjian',
485
- avi: 'es-icon-kebofangwenjian',
486
- mp3: 'es-icon-shengyinwenjian',
487
- wma: 'es-icon-shengyinwenjian',
488
- pdf: 'es-icon-pdf',
489
- ...this.fileIcons
490
- };
491
- },
492
- _picture() {
493
- return {
494
- ...picture,
495
- ...this.picture
496
- };
497
- },
498
- show() {
499
- if ((this.photo && typeof this.photo === 'string') || this.value) {
500
- return false;
501
- }
502
- return this.showFileList;
503
- },
504
- excludes() {
505
- return this.exclude ? this.exclude : this.excludeNames;
506
- },
507
- accepts() {
508
- return this.accept
509
- ? this.accept
510
- : this.fileAccept
511
- ? this.fileAccept
512
- : this.portrait
513
- ? 'image/*'
514
- : undefined;
515
- },
516
- _size() {
517
- return this.size ? this.size : this.fileSize ? this.fileSize : 0;
518
- },
519
- _totalSize() {
520
- return this.totalSize
521
- ? this.totalSize
522
- : this.fileTotalSize
523
- ? this.fileTotalSize
524
- : 0;
525
- },
526
- getAdjunctFileInfos() {
527
- return typeof this.fileList === 'string'
528
- ? this.fileList
529
- : getAdjunctFileInfos;
530
- },
531
- showList() {
532
- return this.portrait ? false : this.showFileList;
533
- },
534
- isDownloads() {
535
- return this.downloads;
536
- },
537
- isDownload() {
538
- if (this.download) {
539
- return true;
540
- }
541
- return false;
542
- },
543
- isDisabled() {
544
- return this.disabled || (this.elForm || {}).disabled;
545
- },
546
- isReadonly() {
547
- return this.readonly || (this.elForm || {}).readonly;
548
- },
549
- isRemove() {
550
- if (this.remove == false || this.deleted == false) {
551
- return false;
552
- }
553
- return true;
554
- },
555
- isSort() {
556
- // return this.dragSort && !this.isDisabled && this.isReadonly;
557
- return this.dragSort && !this.isDisabled;
558
- },
559
- datas() {
560
- if (this.data) {
561
- return util.extend({}, this.data, this.params);
562
- }
563
- return this.params;
564
- },
565
- params() {
566
- let params = { userId: util.getStorage('userId'), ...this.param };
567
- if (this.code !== undefined) {
568
- params.code = this.code;
569
- params.bucketCode = this.code;
570
- }
571
- if (this.ownId !== undefined) {
572
- params.ownId = this.ownId;
573
- params.businessId = this.ownId;
574
- }
575
- return params;
576
- },
577
- showMessage() {
578
- if (this.message !== undefined) {
579
- return util.extend({}, { success: true, error: true }, this.message);
580
- }
581
- return { success: true, error: true };
582
- },
583
- profile() {
584
- const previewAdjunct =
585
- this.dochubConfig.downloadDocumentUrl || this.previewAdjunct;
586
- if (this.image) {
587
- if (typeof this.image === 'string') {
588
- return this.image;
589
- }
590
- return `${this.host}${previewAdjunct}?documentId=${
591
- this.image.adjunctId
592
- }&adjunctId=${this.image.adjunctId}&_tt=${new Date().getTime()}`;
593
- }
594
- if (this.value) {
595
- if (typeof this.value === 'string') {
596
- if (this.value.indexOf('data:image/') === 0) {
597
- return this.value;
598
- } else if (this.value.indexOf(previewAdjunct) > -1) {
599
- let val =
600
- this.host +
601
- util.jointUrl({
602
- url: this.value,
603
- reg: '/main2'
604
- });
605
- return val;
606
- } else if (this.value.indexOf('/') === -1) {
607
- return `${this.host}${previewAdjunct}?documentId=${
608
- this.value
609
- }&adjunctId=${this.value}&_tt=${new Date().getTime()}`;
610
- }
611
- return this.value;
612
- } else if (Array.isArray(this.value)) {
613
- let adjunctId = this.value[0].response
614
- ? this.value[0].response.adjunctId
615
- : this.value[0].adjunctId;
616
- return `${
617
- this.host
618
- }${previewAdjunct}?documentId=${adjunctId}&adjunctId=${adjunctId}&_tt=${new Date().getTime()}`;
619
- } else {
620
- let adjunctId = this.value.response
621
- ? this.value.response.adjunctId
622
- : this.value.adjunctId;
623
- return `${
624
- this.host
625
- }${previewAdjunct}?documentId=${adjunctId}&adjunctId=${adjunctId}&_tt=${new Date().getTime()}`;
626
- }
627
- }
628
- if (this.photo && typeof this.photo === 'string') {
629
- if (this.photo.indexOf('data:image/') === 0) {
630
- return this.photo;
631
- } else if (this.photo.indexOf(previewAdjunct) > -1) {
632
- let val =
633
- this.host +
634
- util.jointUrl({
635
- url: this.value,
636
- reg: '/main2'
637
- });
638
- return val;
639
- } else if (this.photo.indexOf('/') === -1) {
640
- return `${this.host}${previewAdjunct}?documentId=${
641
- this.photo
642
- }&adjunctId=${this.photo}&_tt=${new Date().getTime()}`;
643
- }
644
- }
645
- return this.photo;
646
- },
647
- // 是否走分片上传:完全由后台桶配置(/v2/chunk/config)的开关决定,且具备桶编码(code)与业务id(ownId)
648
- isChunk() {
649
- const cfg = this.bucketChunkConfig;
650
- return !!(cfg && cfg.enableChunkUpload) && !!this.code && !!this.ownId;
651
- },
652
- // 生效的分片大小(字节):桶配置 > 默认 5MB
653
- _chunkSize() {
654
- const cfg = this.bucketChunkConfig;
655
- return (
656
- (cfg && Number(cfg.chunkSize) > 0 && Number(cfg.chunkSize)) ||
657
- 5 * 1024 * 1024
658
- );
659
- },
660
- // 生效的分片并发数:桶配置 > 默认 3
661
- _chunkConcurrent() {
662
- const cfg = this.bucketChunkConfig;
663
- return (
664
- (cfg &&
665
- Number(cfg.chunkConcurrent) > 0 &&
666
- Number(cfg.chunkConcurrent)) ||
667
- 3
668
- );
669
- },
670
- // 分片三接口地址:优先 dochubConfig 下发,否则回退 api.js 常量(与本组件 dochubConfig.x || 常量 的写法一致)
671
- chunkUrls() {
672
- const cfg = this.dochubConfig || {};
673
- return {
674
- check: cfg.chunkCheckUrl || chunkCheckFile,
675
- upload: cfg.chunkUploadUrl || chunkUploadChunk,
676
- merge: cfg.chunkMergeUrl || chunkMergeChunk
677
- };
678
- },
679
- // 传给 el-upload 的自定义上传实现;非分片模式返回 undefined 以走默认上传
680
- chunkHttpRequest() {
681
- return this.isChunk ? this.chunkUpload : undefined;
682
- },
683
- // 操作列按钮:非分片模式回退用户经 $attrs 传入的 handles(或 undefined → el-upload 默认),
684
- // 分片模式注入"暂停/继续"并保留预览/下载/删除(emit 复用既有事件链)。仅分片模式改变渲染,不影响存量业务。
685
- chunkHandles() {
686
- if (!this.isChunk) return this.$attrs.handles;
687
- const done = (f) => f.status === 'success' || f.status == 0;
688
- return [
689
- {
690
- icon: 'el-icon-video-pause',
691
- title: '暂停',
692
- rules: (f) => this.isChunkUploading(f) && !f.chunkPaused,
693
- event: (f) => this.pauseUpload(f)
694
- },
695
- {
696
- icon: 'el-icon-video-play',
697
- title: '继续',
698
- rules: (f) => this.isChunkUploading(f) && !!f.chunkPaused,
699
- event: (f) => this.resumeUpload(f)
700
- },
701
- {
702
- icon: 'el-icon-document-copy',
703
- title: '预览',
704
- rules: (f) => !!this.preview && done(f),
705
- emit: 'preview'
706
- },
707
- {
708
- icon: 'el-icon-download',
709
- title: '下载',
710
- rules: (f) => this.isDownload && done(f),
711
- emit: 'download'
712
- },
713
- {
714
- icon: 'el-icon-delete',
715
- title: '删除',
716
- rules: (f) => this.isRemove,
717
- emit: 'remove'
718
- }
719
- ];
720
- }
721
- },
722
- watch: {
723
- fileList: {
724
- immediate: true,
725
- deep: true,
726
- handler(val) {
727
- if (Array.isArray(val) && val.length > 0) {
728
- this.lists = val;
729
- let filesTotalSize = 0;
730
- val.forEach((item) => {
731
- if (
732
- Object.prototype.hasOwnProperty.call(item, 'fileSize') &&
733
- item.fileSize
734
- ) {
735
- filesTotalSize += parseFloat(item.fileSize, 10);
736
- } else {
737
- filesTotalSize += item.size
738
- ? Math.round((item.size / 1024) * 10) / 10
739
- : 0;
740
- }
741
- });
742
- this.filesTotalSize = filesTotalSize;
743
- this.$emit('input', val);
744
- }
745
- }
746
- },
747
- value: {
748
- immediate: true,
749
- deep: true,
750
- handler(val) {
751
- if (val && typeof val === 'object' && this.showFileList) {
752
- if (Array.isArray(val)) {
753
- this.lists = val;
754
- } else {
755
- this.lists = [val];
756
- }
757
- }
758
- }
759
- },
760
- params: {
761
- deep: true,
762
- handler() {
763
- this.getFiles();
764
- }
765
- },
766
- lists: {
767
- deep: true,
768
- handler(val) {
769
- this.$emit('update:fileCount', val.length);
770
- this.$emit('getFilesAmount', val.length);
771
- }
772
- },
773
- display(val) {
774
- val && this.getHeight();
775
- }
776
- },
777
- beforeCreate() {
778
- this.getFiles = debounce(500, () => {
779
- this.getFileLists();
780
- });
781
- this.getAdjunctPropertie = debounce(500, () => {
782
- this.getAdjunctProperties();
783
- });
784
- },
785
- created() {
786
- // 分片上传的运行态(按 file.uid 索引,非响应式,仅用于流程控制)
787
- this._chunkState = {};
788
- const dochubConfig = sessionStorage.getItem('dochubConfig');
789
- if (dochubConfig) {
790
- this.dochubConfig = JSON.parse(dochubConfig);
791
- }
792
- this.getAdjunctPropertie();
793
- },
794
- mounted() {
795
- this.$nextTick(() => {
796
- this.getHeight();
797
- });
798
- },
799
- methods: {
800
- getHeight() {
801
- if (this.height === 'auto') {
802
- let pt =
803
- parseInt(util.getStyle(this.$el.parentNode, 'padding-top'), 10) || 0;
804
- let pb =
805
- parseInt(util.getStyle(this.$el.parentNode, 'padding-bottom'), 10) ||
806
- 0;
807
- let btn = this.$refs.upload
808
- ? this.$refs.upload.$children[0].$el.offsetHeight || 0
809
- : 0;
810
- this.boxHeight =
811
- this.$el.parentNode.offsetHeight - pt - pb - btn + 'px';
812
- }
813
- },
814
- //根据code获取附件参数配置
815
- getAdjunctProperties() {
816
- if (this.code !== undefined) {
817
- let config = store.get(this.code);
818
- if (config) {
819
- this.fileAccept = config.accept;
820
- this.fileSize = config.size;
821
- this.fileTotalSize = config.totalSize;
822
- if (config.dochubConfig) {
823
- this.dochubConfig = config.dochubConfig;
824
- if (config.bucketChunkConfig !== undefined) {
825
- this.bucketChunkConfig = config.bucketChunkConfig;
826
- } else {
827
- this.getBucketChunkConfig();
828
- }
829
- }
830
-
831
- let url =
832
- this.portrait || this.documentId
833
- ? this.dochubConfig.reuploadDocumentUrl || uploadOnlyOne
834
- : this.dochubConfig.uploadDocumentUrl || uploads;
835
- this.uploadUrl = url.indexOf(this.host) > -1 ? url : this.host + url;
836
- this.requestFiles && this.getFiles();
837
- } else {
838
- util
839
- .ajax({
840
- method: this.method,
841
- url: this.properties,
842
- data: { code: this.code, ...this.param },
843
- params: { code: this.code, ...this.param }
844
- })
845
- .then((res) => {
846
- if (res.rCode === 0) {
847
- if (res.results) {
848
- this.excludeNames = res.results.excludeName;
849
- if (res.results.fileTypeExtName) {
850
- let fileTypeExtName =
851
- res.results.fileTypeExtName.split(';');
852
- this.fileAccept = fileTypeExtName
853
- .filter((item) => {
854
- return item;
855
- })
856
- .join(',');
857
- }
858
- if (res.results.dochubConfig) {
859
- this.dochubConfig = res.results.dochubConfig;
860
- sessionStorage.setItem(
861
- 'dochubConfig',
862
- JSON.stringify(res.results.dochubConfig)
863
- );
864
- // dochubConfig 有数据 → 走文档中台,再拉取该桶的桶配置(分片上传开关/参数)
865
- this.getBucketChunkConfig();
866
- }
867
- let url = this.portrait
868
- ? this.dochubConfig.reuploadDocumentUrl || uploadOnlyOne
869
- : this.dochubConfig.uploadDocumentUrl || uploads;
870
- this.uploadUrl =
871
- url.indexOf(this.host) > -1 ? url : this.host + url;
872
- if (res.results.kkViewRootPath) {
873
- this.kkfileview = res.results.kkViewRootPath;
874
- }
875
- this.fileSize = res.results.limitFileSize
876
- ? res.results.limitFileSize
877
- : 0;
878
- this.fileTotalSize = res.results.limitTotalSize;
879
- store.set(this.code, {
880
- accept: this.fileAccept,
881
- size: this.fileSize,
882
- totalSize: this.fileTotalSize,
883
- dochubConfig: this.dochubConfig
884
- });
885
- }
886
- this.requestFiles && this.getFiles();
887
- }
888
- })
889
- .catch((err) => {
890
- if (err.message && err.message !== 'canceled') {
891
- this.$message.error(err.message);
892
- }
893
- });
894
- }
895
- }
896
- },
897
- // 根据桶编码调中台获取桶配置接口(/v2/getBucket),取 baseBucketConfig 中的分片上传开关与参数。
898
- getBucketChunkConfig() {
899
- if (!this.code) return;
900
- let url = getDochubBucket;
901
- util
902
- .ajax({
903
- method: this.method,
904
- url: url,
905
- data: { bucketCode: this.code },
906
- params: { bucketCode: this.code }
907
- })
908
- .then((res) => {
909
- if (res.rCode === 0 && res.results && res.results.baseBucketConfig) {
910
- const cfg = res.results.baseBucketConfig;
911
- this.bucketChunkConfig = {
912
- enableChunkUpload: cfg.enableChunkUpload,
913
- chunkSize: cfg.chunkSize,
914
- chunkConcurrent: cfg.chunkConcurrent
915
- };
916
- } else {
917
- this.bucketChunkConfig = null;
918
- }
919
- // 回写本地缓存,避免同 code 的其他实例重复请求
920
- let cached = store.get(this.code);
921
- if (cached) {
922
- store.set(this.code, {
923
- ...cached,
924
- bucketChunkConfig: this.bucketChunkConfig
925
- });
926
- }
927
- })
928
- .catch(() => {
929
- // 桶配置获取失败不阻断上传,维持 props 行为
930
- this.bucketChunkConfig = null;
931
- });
932
- },
933
- getFileLists() {
934
- if (
935
- !this.show ||
936
- (this.fileList &&
937
- Array.isArray(this.fileList) &&
938
- this.fileList.length) ||
939
- (this.requiredOwnId &&
940
- !Object.prototype.hasOwnProperty.call(this.params, 'ownId'))
941
- ) {
942
- return false;
943
- }
944
- let url =
945
- typeof this.fileList === 'string'
946
- ? this.fileList
947
- : this.dochubConfig.getDocumentList
948
- ? this.dochubConfig.getDocumentList
949
- : getAdjunctFileInfos;
950
- util
951
- .ajax({
952
- method: this.method,
953
- url: url,
954
- data: this.params,
955
- params: this.params,
956
- format: false
957
- })
958
- .then((res) => {
959
- if (res.rCode === 0) {
960
- if (this.portrait === true) {
961
- this.image = JSON.parse(JSON.stringify(res.results))[0];
962
- } else {
963
- this.lists = JSON.parse(JSON.stringify(res.results));
964
- let filesTotalSize = 0;
965
- this.lists.forEach((item) => {
966
- if (
967
- Object.prototype.hasOwnProperty.call(item, 'fileSize') &&
968
- item.fileSize
969
- ) {
970
- filesTotalSize += parseFloat(item.fileSize, 10);
971
- } else {
972
- filesTotalSize += item.size
973
- ? Math.round((item.size / 1024) * 10) / 10
974
- : 0;
975
- }
976
- });
977
- this.filesTotalSize = filesTotalSize;
978
- if (this.lists.length) {
979
- this.$emit('input', this.lists);
980
- }
981
- }
982
- } else {
983
- let msg = res.msg || '系统错误,请联系管理员!';
984
- this.$message.error(msg);
985
- }
986
- })
987
- .catch((err) => {
988
- if (err.message && err.message !== 'canceled') {
989
- this.$message.error(err.message);
990
- }
991
- });
992
- },
993
- handleUpload() {
994
- this.$refs.upload.submit();
995
- },
996
- openKkfileview(file) {
997
- let host = util.win.location.origin;
998
- if (this.host) {
999
- if (util.startWith(this.host, 'http')) {
1000
- host = this.host;
1001
- } else {
1002
- host += this.host;
1003
- }
1004
- }
1005
- let url = encodeURIComponent(
1006
- Base64.encode(
1007
- host +
1008
- (typeof this.download === 'string'
1009
- ? this.download
1010
- : downloadByAdjunctId) +
1011
- `?adjunctId=${
1012
- file.adjunctId || file.response.adjunctId
1013
- }&fullfilename=${file.originalName}`
1014
- )
1015
- );
1016
- util.win.open(`${this.kkfileview}?url=${url}`);
1017
- },
1018
- handlePreview(res) {
1019
- if (this.preview) {
1020
- if (this.onPreview) {
1021
- this.onPreview(res);
1022
- } else {
1023
- let file = res.response ? res.response : res;
1024
- let url =
1025
- typeof this.preview === 'string'
1026
- ? this.preview
1027
- : typeof this.preview === 'function'
1028
- ? this.preview(file)
1029
- : null;
1030
- if (res.status === 'success' || res.status == 0) {
1031
- let suffix = file.suffix;
1032
- suffix = suffix.toLowerCase();
1033
- if (this.dochubConfig.previewDocumentUrl) {
1034
- if (
1035
- suffix.includes('jpeg') ||
1036
- suffix.includes('jpg') ||
1037
- suffix.includes('gif') ||
1038
- suffix.includes('png')
1039
- ) {
1040
- this.imgUrl =
1041
- res.url && util.startWith(url, ['http', '/', true])
1042
- ? res.url
1043
- : this.host +
1044
- (url
1045
- ? url
1046
- : `${this.dochubConfig.downloadDocumentUrl}?documentId=${file.adjunctId}`);
1047
- this.title = file.originalName;
1048
- this.showImg = true;
1049
- this.$nextTick(() => {
1050
- this.loadImage();
1051
- });
1052
- } else if (suffix.includes('mp4')) {
1053
- this.source = {
1054
- src:
1055
- this.host +
1056
- (url
1057
- ? url
1058
- : `${this.dochubConfig.downloadDocumentUrl}?documentId=${file.adjunctId}`)
1059
- };
1060
- this.title = file.originalName;
1061
- this.showVideo = true;
1062
- } else {
1063
- util.win.open(
1064
- this.host +
1065
- (url
1066
- ? url
1067
- : `${this.dochubConfig.previewDocumentUrl}?documentId=${file.adjunctId}&action=附件预览`)
1068
- );
1069
- }
1070
- } else {
1071
- if (
1072
- suffix.includes('doc') ||
1073
- suffix.includes('docx') ||
1074
- suffix.includes('xls') ||
1075
- suffix.includes('xlsx') ||
1076
- suffix.includes('ppt')
1077
- ) {
1078
- if (this.kkfileview) {
1079
- this.openKkfileview(file);
1080
- } else {
1081
- util.win.open(
1082
- this.host +
1083
- (url ? url : previewAdjunctOffice) +
1084
- '?cmd=view&bucketName=' +
1085
- file.absolutePath +
1086
- '&fileId=' +
1087
- file.adjunctId +
1088
- '&fileName=' +
1089
- file.newName
1090
- );
1091
- }
1092
- } else if (suffix.includes('pdf')) {
1093
- if (this.kkfileview) {
1094
- this.openKkfileview(file);
1095
- } else {
1096
- util.win.open(
1097
- this.host +
1098
- (url ? url : previewAdjunct2) +
1099
- '/' +
1100
- file.originalName +
1101
- '?adjunctId=' +
1102
- file.adjunctId
1103
- );
1104
- }
1105
- } else if (
1106
- suffix.includes('jpeg') ||
1107
- suffix.includes('jpg') ||
1108
- suffix.includes('gif') ||
1109
- suffix.includes('png')
1110
- ) {
1111
- this.imgUrl =
1112
- res.url && util.startWith(url, ['http', '/', true])
1113
- ? res.url
1114
- : this.host +
1115
- (url ? url : this.previewAdjunct) +
1116
- '?adjunctId=' +
1117
- file.adjunctId;
1118
- this.title = file.originalName;
1119
- this.showImg = true;
1120
- this.$nextTick(() => {
1121
- this.loadImage();
1122
- });
1123
- } else if (suffix.includes('mp4')) {
1124
- this.source = {
1125
- src:
1126
- this.host +
1127
- (url ? url : this.previewAdjunct) +
1128
- '?adjunctId=' +
1129
- file.adjunctId
1130
- };
1131
- this.title = file.originalName;
1132
- this.showVideo = true;
1133
- } else {
1134
- if (this.kkfileview) {
1135
- this.openKkfileview(file);
1136
- } else {
1137
- util.win.open(
1138
- this.host +
1139
- (url ? url : this.previewAdjunct) +
1140
- '?adjunctId=' +
1141
- file.adjunctId
1142
- );
1143
- }
1144
- }
1145
- }
1146
- } else {
1147
- util.win.open(URL.createObjectURL(file.raw));
1148
- }
1149
- }
1150
- }
1151
- },
1152
- closeViewer() {
1153
- this.showImg = false;
1154
- },
1155
- loadImage() {
1156
- this.$refs.showImg &&
1157
- (this.$refs.showImg.onload = () => {
1158
- this.imgChange = false;
1159
- let w = this.$refs.showImg.naturalWidth;
1160
- let h = this.$refs.showImg.naturalHeight;
1161
- let pw = this.$refs.showImg.parentNode.offsetWidth;
1162
- let ph = this.$refs.showImg.parentNode.offsetHeight;
1163
- if (w / h > pw / ph) {
1164
- this.styles = { 'max-width': '100%' };
1165
- } else {
1166
- this.styles = { 'max-height': '100%' };
1167
- }
1168
- if (w < pw) {
1169
- this.styles.width = w + 'px';
1170
- }
1171
- if (h < ph) {
1172
- this.styles.height = h + 'px';
1173
- }
1174
- });
1175
- },
1176
- isLt(file) {
1177
- let flag = false;
1178
- let flieArr = file.name.split('.');
1179
- let suffix = '.' + flieArr[flieArr.length - 1].toLowerCase();
1180
- if (this.accepts) {
1181
- let accepts = this.accepts.toLowerCase().split(',');
1182
- flag = accepts.includes(suffix);
1183
- if (!flag) {
1184
- this.$message.error(`文件类型错误,请上传${this.accepts}类型文件!`);
1185
- return flag;
1186
- }
1187
- }
1188
- let size = this._size;
1189
- let text = '单个附件';
1190
- let se = Math.round((file.size / 1024) * 10) / 10;
1191
- if (this._size === 0 && this._totalSize === 0) {
1192
- flag = true;
1193
- } else if (this._size > 0 && this._totalSize === 0) {
1194
- flag = se < this._size;
1195
- size = this._size;
1196
- text = '单个附件';
1197
- } else if (this._size === 0 && this._totalSize > 0) {
1198
- flag = this.filesTotalSize + se < this._totalSize;
1199
- size = this._totalSize;
1200
- text = '附件总';
1201
- } else {
1202
- flag = se < this._size && this.filesTotalSize + se < this._totalSize;
1203
- if (se < this._size) {
1204
- size = this._size;
1205
- text = '单个附件';
1206
- } else {
1207
- size = this._totalSize;
1208
- text = '附件总';
1209
- }
1210
- }
1211
- if (!flag) {
1212
- this.$message.error(`上传${text}大小不能超过${size}KB!`);
1213
- return flag;
1214
- }
1215
- return flag;
1216
- },
1217
- handleBeforeUpload(file) {
1218
- const flag = this.isLt(file);
1219
- if (this.portrait) {
1220
- if (flag && this.beforeUpload) {
1221
- return this.beforeUpload(file);
1222
- }
1223
- return flag;
1224
- }
1225
- if (this.beforeUpload) {
1226
- if (flag) {
1227
- return this.beforeUpload(file);
1228
- }
1229
- return flag;
1230
- }
1231
- return flag;
1232
- },
1233
- handleBeforeRemove(file, fileList) {
1234
- if (file && (file.status === 'success' || file.status == 0)) {
1235
- return this.$confirm('确定删除文件吗?', '提示', {
1236
- confirmButtonText: '确定',
1237
- cancelButtonText: '取消',
1238
- type: 'warning'
1239
- })
1240
- .then(() => {
1241
- if (this.beforeRemove) {
1242
- return this.beforeRemove(file, fileList);
1243
- } else if (file.status === 'success' || file.status == 0) {
1244
- let userName =
1245
- file.userName ||
1246
- (file.response && file.response.userName) ||
1247
- util.getStorage('userName');
1248
- // eslint-disable-next-line no-undef
1249
- return new Promise((resolve, reject) => {
1250
- let url =
1251
- typeof this.deleted === 'string'
1252
- ? this.deleted
1253
- : this.remove === 'string'
1254
- ? this.remove
1255
- : this.dochubConfig.deleteDocumentUrl || delAdjunct;
1256
- util
1257
- .ajax({
1258
- method: this.method,
1259
- url: url,
1260
- data: {
1261
- userName: userName,
1262
- documentId: file.adjunctId || file.response.adjunctId,
1263
- id: file.adjunctId || file.response.adjunctId
1264
- },
1265
- params: {
1266
- userName: userName,
1267
- documentId: file.adjunctId || file.response.adjunctId,
1268
- id: file.adjunctId || file.response.adjunctId
1269
- }
1270
- })
1271
- .then((res) => {
1272
- if (res.rCode === 0) {
1273
- let se =
1274
- this.filesTotalSize -
1275
- Math.round((file.size / 1024) * 10) / 10;
1276
- this.filesTotalSize = se;
1277
- this.$message.success(res.msg);
1278
- resolve();
1279
- } else {
1280
- let msg = res.msg || '系统错误,请联系管理员!';
1281
- this.$message.error(msg);
1282
- reject();
1283
- }
1284
- })
1285
- .catch((err) => {
1286
- if (err.message && err.message !== 'canceled') {
1287
- this.$message.error(err.message);
1288
- }
1289
- });
1290
- });
1291
- }
1292
- return true;
1293
- })
1294
- .catch(() => {
1295
- return flag;
1296
- });
1297
- }
1298
- },
1299
- handleDownloads() {
1300
- let url =
1301
- typeof this.downloads === 'string'
1302
- ? this.downloads
1303
- : this.dochubConfig.compressDownloadDocumentUrl || uploadDownloads;
1304
- util.win.open(
1305
- this.host +
1306
- url +
1307
- '?ownId=' +
1308
- this.ownId +
1309
- '&code=' +
1310
- this.code +
1311
- (this.extendCode ? '&extendCode=' + this.extendCode : '') +
1312
- '&businessId=' +
1313
- this.ownId +
1314
- '&fileName=文档.zip' +
1315
- '&bucketCode=' +
1316
- this.code
1317
- );
1318
- },
1319
- handleDownload(file) {
1320
- if (file.status === 'success' || file.status == 0) {
1321
- let url =
1322
- typeof this.download === 'string'
1323
- ? this.download
1324
- : this.dochubConfig.downloadDocumentUrl || downloadByAdjunctId;
1325
- util.win.open(
1326
- this.host +
1327
- url +
1328
- '?adjunctId=' +
1329
- (file.adjunctId || file.response.adjunctId) +
1330
- '&documentId=' +
1331
- (file.adjunctId || file.response.adjunctId)
1332
- );
1333
- } else {
1334
- this.aLinkDownload(file);
1335
- }
1336
- },
1337
- handleClick(res) {
1338
- if (res.event && typeof res.event === 'function') {
1339
- res.event(res);
1340
- } else {
1341
- this.$emit('click', res);
1342
- }
1343
- },
1344
- aLinkDownload(file) {
1345
- var a = document.createElement('a');
1346
- let event = new MouseEvent('click');
1347
- a.href = URL.createObjectURL(file.raw);
1348
- a.download = file.name;
1349
- a.dispatchEvent(event);
1350
- },
1351
- handleSort(files) {
1352
- if (this.isSort) {
1353
- if (this.onSort) {
1354
- this.onSort(files);
1355
- } else {
1356
- if (
1357
- !this.ownId ||
1358
- !this.code ||
1359
- (this.fileList && this.fileList.length > 0)
1360
- )
1361
- return;
1362
- let ids = files.map((item) => {
1363
- if (item.status === 'success' || item.status == 0) {
1364
- return item.adjunctId || item.response.adjunctId;
1365
- }
1366
- });
1367
- ids = ids.join(',');
1368
- util
1369
- .ajax({
1370
- method: this.method,
1371
- url: this.dochubConfig.sortDocumentsUrl || uploadSort,
1372
- data: {
1373
- ids: ids,
1374
- documentIds: ids,
1375
- ownId: this.ownId,
1376
- code: this.code,
1377
- bucketCode: this.code,
1378
- businessId: this.ownId
1379
- },
1380
- params: {
1381
- ids: ids,
1382
- documentIds: ids,
1383
- ownId: this.ownId,
1384
- code: this.code,
1385
- bucketCode: this.code,
1386
- businessId: this.ownId
1387
- }
1388
- })
1389
- .then((res) => {
1390
- if (res.rCode === 0) {
1391
- this.$message.success(res.msg);
1392
- } else {
1393
- let msg = res.msg || '系统错误,请联系管理员!';
1394
- this.$message.error(msg);
1395
- }
1396
- })
1397
- .catch((err) => {
1398
- if (err.message && err.message !== 'canceled') {
1399
- this.$message.error(err.message);
1400
- }
1401
- });
1402
- }
1403
- }
1404
- },
1405
- handleSuccess(response, file, fileList) {
1406
- if (this.portrait) {
1407
- this.image = URL.createObjectURL(file.raw);
1408
- }
1409
- if (this.showMessage.success) {
1410
- if (response.rCode) {
1411
- if (this.dangerouslyUseHTMLString) {
1412
- this.$alert(
1413
- response.msg ? response.msg : '上传失败:请联系管理员',
1414
- '提示',
1415
- {
1416
- dangerouslyUseHTMLString: true,
1417
- setHeight: response.msg
1418
- }
1419
- ).catch();
1420
- } else {
1421
- this.$message({
1422
- type: 'error',
1423
- message: response.msg || '成功!'
1424
- });
1425
- }
1426
- } else {
1427
- this.$message.success(response.msg);
1428
- }
1429
- }
1430
- let se =
1431
- this.filesTotalSize +
1432
- (this.resultFile ? Math.round((file.size / 1024) * 10) / 10 : 0);
1433
- this.filesTotalSize = se;
1434
- this.onSuccess &&
1435
- this.onSuccess(response, file, fileList, this.dochubConfig);
1436
- this.$emit('success', response, file, fileList, this.dochubConfig);
1437
- if (this.resultFile) {
1438
- if (this.portrait) {
1439
- this.$emit('input', file);
1440
- } else {
1441
- this.$emit('input', fileList);
1442
- }
1443
- if (this.elForm) {
1444
- this.elForm.clearValidate(this.name);
1445
- }
1446
- }
1447
- },
1448
- handleChange(file, fileList) {
1449
- this.$emit('change', fileList);
1450
- this.onChange && this.onChange(file, fileList);
1451
- },
1452
- handleRemove(file, fileList) {
1453
- this.cleanupChunk(file);
1454
- this.$emit('input', fileList.length ? fileList : '');
1455
- this.$emit('remove', file, fileList);
1456
- this.$emit('change', fileList);
1457
- this.onRemove && this.onRemove(file, fileList);
1458
- },
1459
- handleError(err, file, fileList) {
1460
- if (this.showMessage.error) {
1461
- if (this.dangerouslyUseHTMLString) {
1462
- this.$alert(err.msg ? err.msg : '上传失败:请联系管理员', '提示', {
1463
- dangerouslyUseHTMLString: true,
1464
- setHeight: err.msg
1465
- }).catch();
1466
- } else {
1467
- this.$message({
1468
- type: 'error',
1469
- message: '上传失败:' + (err.msg ? err.msg : '请联系管理员')
1470
- });
1471
- }
1472
- }
1473
- this.$emit('error', err, file, fileList);
1474
- this.onError && this.onError(err, file, fileList);
1475
- },
1476
- // el-upload 的自定义上传实现:建立可暂停的运行态并返回受控 Promise。
1477
- // 暂停时不 resolve/reject(promise 维持 pending,文件保持"上传中"而非失败),继续时再次进入流程。
1478
- chunkUpload(options) {
1479
- const file = options.file;
1480
- const uid = file.uid;
1481
- const state = {
1482
- paused: false,
1483
- controller: null,
1484
- fileMd5: '',
1485
- totalChunks: Math.max(1, Math.ceil(file.size / this._chunkSize)),
1486
- options: options
1487
- };
1488
- this._chunkState[uid] = state;
1489
- return new Promise((resolve, reject) => {
1490
- state.resolve = resolve;
1491
- state.reject = reject;
1492
- this.runChunkFlow(uid);
1493
- });
1494
- },
1495
- // 分片主流程(可被"继续"重新进入):MD5(缓存) → 秒传/断点续传检测 → 并发上传缺失分片 → 合并入库。
1496
- runChunkFlow(uid) {
1497
- const state = this._chunkState[uid];
1498
- if (!state) return;
1499
- const options = state.options;
1500
- const file = options.file;
1501
- const onProgress = options.onProgress || function () {};
1502
- const baseParams = () => ({
1503
- ...this.param,
1504
- fileMd5: state.fileMd5,
1505
- fileName: file.name,
1506
- fileSize: file.size,
1507
- totalChunks: state.totalChunks,
1508
- bucketCode: this.code,
1509
- businessId: this.ownId
1510
- });
1511
- const isAbort = (e) =>
1512
- !!e &&
1513
- (e.__aborted === true ||
1514
- e.name === 'CanceledError' ||
1515
- e.name === 'AbortError' ||
1516
- (typeof e.message === 'string' && /cancel|abort/i.test(e.message)));
1517
-
1518
- Promise.resolve()
1519
- .then(() => state.fileMd5 || this.computeFileMd5(file, onProgress))
1520
- .then((md5) => {
1521
- state.fileMd5 = md5;
1522
- if (state.paused) return null;
1523
- return util.ajax({
1524
- method: 'post',
1525
- url: this.chunkUrls.check,
1526
- data: baseParams(),
1527
- params: baseParams(),
1528
- headers: options.headers
1529
- });
1530
- })
1531
- .then((checkRes) => {
1532
- if (!checkRes) return; // 暂停于 MD5/检测阶段,维持 pending
1533
- if (checkRes.rCode !== 0) return Promise.reject(checkRes);
1534
- const info = checkRes.results || {};
1535
- // 秒传命中:该业务桶下已存在同 MD5 文件,直接视为成功
1536
- if (info.instant) {
1537
- onProgress({ percent: 100 });
1538
- this.normalizeChunkDoc(info, file.name);
1539
- this.finishChunk(uid, {
1540
- rCode: 0,
1541
- msg: checkRes.msg || '秒传成功',
1542
- results: info
1543
- });
1544
- // 秒传文件已存在于服务端,刷新列表以展示其完整信息(含可下载/预览的 id)
1545
- this.requestFiles && this.getFiles();
1546
- return;
1547
- }
1548
- // 断点续传:跳过服务端已存在的分片
1549
- const uploaded = new Set(info.uploadedChunks || []);
1550
- const pending = [];
1551
- for (let i = 0; i < state.totalChunks; i++) {
1552
- if (!uploaded.has(i)) pending.push(i);
1553
- }
1554
- let finished = state.totalChunks - pending.length;
1555
- // 上传阶段占 5~95%(MD5 校验已占 0~5%),保证进度单调不回退
1556
- onProgress({
1557
- percent: Math.min(
1558
- 95,
1559
- 5 + Math.round((finished / state.totalChunks) * 90)
1560
- )
1561
- });
1562
- state.controller =
1563
- typeof AbortController !== 'undefined'
1564
- ? new AbortController()
1565
- : null;
1566
- const signal = state.controller ? state.controller.signal : undefined;
1567
- return this.runChunkQueue(pending, this._chunkConcurrent, (index) => {
1568
- if (state.paused) return Promise.reject({ __aborted: true });
1569
- return this.uploadOneChunk(
1570
- file,
1571
- index,
1572
- state.totalChunks,
1573
- state.fileMd5,
1574
- options.headers,
1575
- signal
1576
- ).then(() => {
1577
- finished++;
1578
- onProgress({
1579
- percent: Math.min(
1580
- 95,
1581
- 5 + Math.round((finished / state.totalChunks) * 90)
1582
- )
1583
- });
1584
- });
1585
- }).then(() => {
1586
- if (state.paused) return; // 暂停于上传阶段,维持 pending
1587
- return util
1588
- .ajax({
1589
- method: 'post',
1590
- url: this.chunkUrls.merge,
1591
- data: baseParams(),
1592
- params: baseParams(),
1593
- headers: options.headers
1594
- })
1595
- .then((mergeRes) => {
1596
- if (mergeRes.rCode !== 0) return Promise.reject(mergeRes);
1597
- onProgress({ percent: 100 });
1598
- this.normalizeChunkDoc(mergeRes.results, file.name);
1599
- this.finishChunk(uid, mergeRes);
1600
- // 刷新列表:让刚入库的文件以与其他行一致的结构展示(时间/大小/上传人等)
1601
- this.requestFiles && this.getFiles();
1602
- });
1603
- });
1604
- })
1605
- .catch((e) => {
1606
- // 暂停导致的中止:保持 pending 等待"继续";其余才算失败
1607
- if (state.paused || isAbort(e)) return;
1608
- this.failChunk(uid, e);
1609
- });
1610
- },
1611
- // 暂停:置暂停标记并中止在传分片;promise 维持 pending,文件保持"上传中"
1612
- pauseUpload(file) {
1613
- const state = file && this._chunkState[file.uid];
1614
- if (!state) return;
1615
- state.paused = true;
1616
- if (state.controller) {
1617
- try {
1618
- state.controller.abort();
1619
- } catch (e) {
1620
- /* ignore */
1621
- }
1622
- }
1623
- this.$set(file, 'chunkPaused', true);
1624
- this.$emit('chunk-pause', file);
1625
- },
1626
- // 继续:清暂停标记,重新进入流程(checkFile 会返回已传分片,自动续传)
1627
- resumeUpload(file) {
1628
- const state = file && this._chunkState[file.uid];
1629
- if (!state || !state.paused) return; // 仅暂停态可续传,防止重复触发并发流程
1630
- state.paused = false;
1631
- this.$set(file, 'chunkPaused', false);
1632
- this.$emit('chunk-resume', file);
1633
- this.runChunkFlow(file.uid);
1634
- },
1635
- // 该文件是否处于分片上传运行态(用于操作列按钮显隐)
1636
- isChunkUploading(file) {
1637
- return !!(file && this._chunkState && this._chunkState[file.uid]);
1638
- },
1639
- // 归一化合并响应:dochub Document 用 docTagId 标识、且无 suffix 字段,
1640
- // 而组件下载读 adjunctId、预览读 suffix/originalName,这里统一映射补齐,避免预览取 suffix 报错。
1641
- normalizeChunkDoc(results, fileName) {
1642
- if (!results || typeof results !== 'object') return results;
1643
- const id =
1644
- results.adjunctId ||
1645
- results.docTagId ||
1646
- results.documentId ||
1647
- results.id;
1648
- if (id) {
1649
- if (!results.adjunctId) results.adjunctId = id;
1650
- if (!results.documentId) results.documentId = id;
1651
- }
1652
- // 文件名:优先用上传时的原始名,其次后端 documentFile.docName
1653
- const name =
1654
- fileName ||
1655
- (results.documentFile && results.documentFile.docName) ||
1656
- results.originalName ||
1657
- results.name ||
1658
- '';
1659
- if (!results.originalName && name) results.originalName = name;
1660
- // 后缀:预览按后缀判断类型,缺省从文件名推导;始终保证为字符串
1661
- if (results.suffix === undefined || results.suffix === null) {
1662
- results.suffix =
1663
- name && name.lastIndexOf('.') > -1
1664
- ? name.slice(name.lastIndexOf('.') + 1)
1665
- : '';
1666
- }
1667
- // 展示字段:dochub 的元数据在 documentFile 下且字段名不同,映射成 showInfo 默认读取的扁平字段
1668
- const df = results.documentFile;
1669
- if (df && typeof df === 'object') {
1670
- if (!results.uploadTime) {
1671
- results.uploadTime = df.createTime || df.lastModifyTime || '';
1672
- }
1673
- if (!results.userName) {
1674
- results.userName = df.createUserName || df.lastModifyUserName || '';
1675
- }
1676
- if (!results.fileSize && (df.docSize || df.docSize === 0)) {
1677
- results.fileSize = this.formatFileSize(df.docSize);
1678
- }
1679
- }
1680
- return results;
1681
- },
1682
- // 字节数格式化为可读大小(与列表展示风格一致:1 位小数、无空格)
1683
- formatFileSize(bytes) {
1684
- const n = Number(bytes);
1685
- if (!n || n <= 0) return '0B';
1686
- if (n >= 1073741824) return (n / 1073741824).toFixed(1) + 'GB';
1687
- if (n >= 1048576) return (n / 1048576).toFixed(1) + 'MB';
1688
- if (n >= 1024) return (n / 1024).toFixed(1) + 'KB';
1689
- return n + 'B';
1690
- },
1691
- // 完成:兑现 promise 并清理运行态
1692
- finishChunk(uid, response) {
1693
- const state = this._chunkState[uid];
1694
- if (!state) return;
1695
- state.resolve && state.resolve(response);
1696
- delete this._chunkState[uid];
1697
- },
1698
- // 失败:拒绝 promise 并清理运行态
1699
- failChunk(uid, err) {
1700
- const state = this._chunkState[uid];
1701
- if (!state) return;
1702
- state.reject && state.reject(err);
1703
- delete this._chunkState[uid];
1704
- },
1705
- // 移除文件时清理分片运行态(中止在传分片,孤立 pending promise 不再触发回调)
1706
- cleanupChunk(file) {
1707
- const uid = file && file.uid;
1708
- const state = uid && this._chunkState && this._chunkState[uid];
1709
- if (!state) return;
1710
- state.paused = true;
1711
- state.resolve = null;
1712
- state.reject = null;
1713
- if (state.controller) {
1714
- try {
1715
- state.controller.abort();
1716
- } catch (e) {
1717
- /* ignore */
1718
- }
1719
- }
1720
- delete this._chunkState[uid];
1721
- },
1722
- // 上传单个分片(multipart/form-data);signal 用于暂停时中止
1723
- uploadOneChunk(file, index, totalChunks, fileMd5, headers, signal) {
1724
- const start = index * this._chunkSize;
1725
- const end = Math.min(start + this._chunkSize, file.size);
1726
- const formData = new FormData();
1727
- formData.append('file', file.slice(start, end), file.name);
1728
- formData.append('fileMd5', fileMd5);
1729
- formData.append('fileName', file.name);
1730
- formData.append('fileSize', file.size);
1731
- formData.append('chunkIndex', index);
1732
- formData.append('totalChunks', totalChunks);
1733
- formData.append('bucketCode', this.code);
1734
- formData.append('businessId', this.ownId);
1735
- Object.keys(this.param || {}).forEach((key) => {
1736
- formData.append(key, this.param[key]);
1737
- });
1738
- return util
1739
- .ajax({
1740
- method: 'post',
1741
- url: this.chunkUrls.upload,
1742
- data: formData,
1743
- format: false,
1744
- headers: headers,
1745
- signal: signal
1746
- })
1747
- .then((res) => {
1748
- if (res.rCode !== 0) return Promise.reject(res);
1749
- return res;
1750
- });
1751
- },
1752
- // 分块读取文件并增量计算 MD5(避免大文件一次性读入内存)
1753
- computeFileMd5(file, onProgress) {
1754
- return new Promise((resolve, reject) => {
1755
- const size = this._chunkSize;
1756
- const chunks = Math.max(1, Math.ceil(file.size / size));
1757
- const spark = new SparkMD5.ArrayBuffer();
1758
- const reader = new FileReader();
1759
- let current = 0;
1760
- const loadNext = () => {
1761
- const start = current * size;
1762
- reader.readAsArrayBuffer(
1763
- file.slice(start, Math.min(start + size, file.size))
1764
- );
1765
- };
1766
- reader.onload = (e) => {
1767
- spark.append(e.target.result);
1768
- current++;
1769
- // 校验阶段占进度 0~5%,剩余留给上传与合并
1770
- onProgress &&
1771
- onProgress({ percent: Math.round((current / chunks) * 5) });
1772
- if (current < chunks) {
1773
- loadNext();
1774
- } else {
1775
- // 取标准 32 位 MD5 的中间 16 位,与后端 hutool digestHex16(即 doc_md5)口径一致,
1776
- // 否则秒传按 md5 比对永远不命中
1777
- resolve(spark.end().substring(8, 24));
1778
- }
1779
- };
1780
- reader.onerror = () => reject({ msg: '文件读取失败,无法计算MD5' });
1781
- loadNext();
1782
- });
1783
- },
1784
- // 限制并发的分片任务队列
1785
- runChunkQueue(indexes, concurrency, worker) {
1786
- return new Promise((resolve, reject) => {
1787
- if (!indexes.length) return resolve();
1788
- let cursor = 0;
1789
- let active = 0;
1790
- let failed = false;
1791
- const schedule = () => {
1792
- if (failed) return;
1793
- if (cursor >= indexes.length && active === 0) return resolve();
1794
- while (active < concurrency && cursor < indexes.length) {
1795
- const index = indexes[cursor++];
1796
- active++;
1797
- worker(index)
1798
- .then(() => {
1799
- active--;
1800
- schedule();
1801
- })
1802
- .catch((err) => {
1803
- failed = true;
1804
- reject(err);
1805
- });
1806
- }
1807
- };
1808
- schedule();
1809
- });
1810
- }
1811
- }
1812
- };
1813
- </script>
1
+ <template>
2
+ <el-upload
3
+ v-if="isRender && uploadUrl"
4
+ ref="upload"
5
+ v-bind="$attrs"
6
+ class="es-upload"
7
+ :portrait="portrait"
8
+ :list-type="listType"
9
+ :wait="1000"
10
+ :height="boxHeight"
11
+ :multiple="portrait ? false : multiple"
12
+ :action="uploadUrl"
13
+ :http-request="chunkHttpRequest"
14
+ :handles="chunkHandles"
15
+ :show-file-list="showList"
16
+ :file-list="lists"
17
+ :result-file="resultFile"
18
+ :disabled="isDisabled || isReadonly"
19
+ :readonly="isReadonly"
20
+ :class="[
21
+ {
22
+ 'es-avatar-uploader': portrait && !$slots.default,
23
+ 'es-upload-readonly': isReadonly && !downloads
24
+ },
25
+ 'es-upload-' + listType
26
+ ]"
27
+ :previewAdjunct="host + previewAdjunct"
28
+ :headers="headers"
29
+ :data="datas"
30
+ :show-info="showList ? showInfo : undefined"
31
+ :auto-upload="autoUpload"
32
+ :response="true"
33
+ :operate="operate"
34
+ :accept="accepts"
35
+ :drag-sort="isSort"
36
+ :preview="preview"
37
+ :exclude="excludes"
38
+ :download="isDownload"
39
+ :deleted="isRemove"
40
+ :fileIcons="_icons"
41
+ :picture="_picture"
42
+ :before-upload="handleBeforeUpload"
43
+ :before-remove="handleBeforeRemove"
44
+ :on-preview="handlePreview"
45
+ :on-downloads="handleDownloads"
46
+ :on-download="handleDownload"
47
+ :on-sort="handleSort"
48
+ :on-success="handleSuccess"
49
+ :on-change="handleChange"
50
+ :on-remove="handleRemove"
51
+ :on-error="handleError"
52
+ >
53
+ <template v-if="prepend">
54
+ <template v-if="Array.isArray(prepend)">
55
+ <el-button
56
+ v-for="(item, index) in prepend"
57
+ v-bind="{ ...item, size: btnSize }"
58
+ :key="index"
59
+ @click.stop="handleClick(item)"
60
+ >
61
+ {{ item.label || item.text }}
62
+ </el-button>
63
+ </template>
64
+ <el-button
65
+ v-else
66
+ v-bind="{ ...prepend, size: btnSize }"
67
+ @click.stop="handleClick(prepend)"
68
+ >
69
+ {{ prepend.label || prepend.text }}
70
+ </el-button>
71
+ </template>
72
+ <slot>
73
+ <expand-dom v-if="render" :render="render"></expand-dom>
74
+ <template v-else>
75
+ <template v-if="portrait">
76
+ <img
77
+ v-if="profile"
78
+ :src="profile"
79
+ class="avatar"
80
+ :style="{ width: width, height: height }"
81
+ />
82
+ <i
83
+ v-else
84
+ class="avatar-uploader-icon"
85
+ :class="{ 'el-icon-plus': !isReadonly, 'is-disabled': isDisabled }"
86
+ :style="{ width: width, height: height, lineHeight: height }"
87
+ ></i>
88
+ </template>
89
+ <template v-else>
90
+ <template v-if="!isReadonly">
91
+ <i
92
+ class="el-icon-plus es-uploader-icon"
93
+ :class="{ 'is-disabled': isDisabled }"
94
+ v-if="selectType === 'icon-plus'"
95
+ ></i>
96
+ <el-button
97
+ v-else
98
+ class="es-upload-button"
99
+ :class="icon"
100
+ :type="selectType"
101
+ :size="btnSize"
102
+ :disabled="isDisabled"
103
+ >{{ text ? text : autoUpload ? '点击上传' : '选择文件' }}
104
+ </el-button>
105
+ <el-button
106
+ class="es-upload-button"
107
+ v-if="!autoUpload"
108
+ :type="uploadType"
109
+ :size="btnSize"
110
+ :disabled="isDisabled"
111
+ @click.stop="handleUpload"
112
+ >
113
+ 上传文件
114
+ </el-button>
115
+ </template>
116
+ <el-button
117
+ v-if="showFileList && isDownloads"
118
+ slot="handle"
119
+ class="es-upload-button es-upload-downloads"
120
+ :size="btnSize"
121
+ @click.stop="handleDownloads"
122
+ >
123
+ 批量下载
124
+ </el-button>
125
+ </template>
126
+ </template>
127
+ </slot>
128
+ <template v-if="append">
129
+ <template v-if="Array.isArray(append)">
130
+ <el-button
131
+ v-for="(item, index) in append"
132
+ v-bind="{ ...item, size: btnSize }"
133
+ :key="index"
134
+ @click.stop="handleClick(item)"
135
+ >
136
+ {{ item.label || item.text }}
137
+ </el-button>
138
+ </template>
139
+ <el-button
140
+ v-else
141
+ v-bind="{ ...append, size: btnSize }"
142
+ @click.stop="handleClick(append)"
143
+ >
144
+ {{ append.label || append.text }}
145
+ </el-button>
146
+ </template>
147
+ <template slot="tip">
148
+ <slot name="tip">
149
+ <div v-if="tips" class="el-upload__tip">{{ tips }}</div>
150
+ </slot>
151
+ </template>
152
+ <el-image-viewer
153
+ slot="dialog"
154
+ v-if="previewScale && showImg"
155
+ :z-index="9999"
156
+ :initial-index="0"
157
+ :url-list="[imgUrl]"
158
+ :on-close="closeViewer"
159
+ ></el-image-viewer>
160
+ <es-dialog v-else :title="title" :visible.sync="showImg" slot="dialog">
161
+ <div
162
+ v-loading="imgChange"
163
+ element-loading-background="rgba(0, 0, 0, 0.8)"
164
+ class="es-upload-dialog"
165
+ >
166
+ <img v-show="!imgChange" ref="showImg" :src="imgUrl" :style="styles" />
167
+ </div>
168
+ </es-dialog>
169
+ <es-dialog :title="title" :visible.sync="showVideo" slot="dialog">
170
+ <es-player
171
+ autoplay
172
+ type="video/mp4"
173
+ :is-pause="!showVideo"
174
+ :source="source"
175
+ ></es-player>
176
+ </es-dialog>
177
+ </el-upload>
178
+ </template>
179
+ <script>
180
+ import {
181
+ chunkCheckFile,
182
+ chunkMergeChunk,
183
+ chunkUploadChunk,
184
+ delAdjunct,
185
+ downloadByAdjunctId,
186
+ getAdjunctFileInfos,
187
+ getAdjunctProperties,
188
+ getDochubBucket,
189
+ previewAdjunct,
190
+ previewAdjunct2,
191
+ previewAdjunctOffice,
192
+ uploadDownloads,
193
+ uploadOnlyOne,
194
+ uploads,
195
+ uploadSort
196
+ } from 'eoss-ui/src/config/api.js';
197
+ import { debounce } from 'throttle-debounce';
198
+ import util from 'eoss-ui/src/utils/util.js';
199
+ import picture from './picture.js';
200
+ import store from 'eoss-ui/src/utils/store';
201
+ import { Base64 } from 'js-base64';
202
+ import SparkMD5 from 'spark-md5';
203
+
204
+ export default {
205
+ name: 'EsUpload',
206
+ components: {
207
+ expandDom: {
208
+ functional: true,
209
+ props: {
210
+ render: [Function, String]
211
+ },
212
+ render: (h, ctx) => {
213
+ if (typeof ctx.props.render == 'string') {
214
+ try {
215
+ return util.toFunction(ctx.props.render)(h, params);
216
+ } catch (error) {
217
+ throw error;
218
+ }
219
+ }
220
+ return ctx.props.render(h);
221
+ }
222
+ }
223
+ },
224
+ inheritAttrs: false,
225
+ inject: {
226
+ elForm: {
227
+ default: ''
228
+ },
229
+ elFormItem: {
230
+ default: ''
231
+ }
232
+ },
233
+ props: {
234
+ prepend: [Array, Object],
235
+ append: [Array, Object],
236
+ render: Function,
237
+ template: String,
238
+ value: [String, Array, Object],
239
+ name: String,
240
+ method: {
241
+ type: String,
242
+ default: 'get'
243
+ },
244
+ text: String,
245
+ icon: {
246
+ type: Boolean,
247
+ default: true
248
+ },
249
+ headers: Object,
250
+ listType: {
251
+ type: String,
252
+ default: 'table' // text,table,picture,picture-card
253
+ },
254
+ operate: {
255
+ type: Boolean,
256
+ default: true
257
+ },
258
+ accept: String,
259
+ // 附件code
260
+ code: String,
261
+ // 业务id
262
+ ownId: String,
263
+ //附件文档id
264
+ documentId: String,
265
+ requiredOwnId: {
266
+ type: Boolean,
267
+ default: true
268
+ },
269
+ //
270
+ downloads: {
271
+ type: Boolean,
272
+ default: false
273
+ },
274
+ // 附件扩展编码
275
+ extendCode: String,
276
+ // 是否添加文件md5值
277
+ fileMd5: {
278
+ type: Boolean,
279
+ default: false
280
+ },
281
+ // 额外的参数
282
+ data: {
283
+ type: Object,
284
+ default() {
285
+ return {};
286
+ }
287
+ },
288
+ param: {
289
+ type: Object,
290
+ default() {
291
+ return {};
292
+ }
293
+ },
294
+ // 单文件大小
295
+ size: Number,
296
+ totalSize: Number,
297
+ // 是否头像模式上传
298
+ portrait: {
299
+ type: Boolean,
300
+ default: false
301
+ },
302
+ photo: [String, Boolean],
303
+ //头像按钮框宽度
304
+ width: String,
305
+ //头像按钮框高度度
306
+ height: String,
307
+ // 自定义的文字提示
308
+ tips: {
309
+ type: String,
310
+ default: ''
311
+ },
312
+ dragSort: {
313
+ type: Boolean,
314
+ default: false
315
+ },
316
+ // 上传成功后绑定到file的内容
317
+ showInfo: {
318
+ type: Array,
319
+ default() {
320
+ return [
321
+ { label: '上传时间', field: 'uploadTime', width: 170 },
322
+ { label: '大小', field: 'fileSize', width: 100 },
323
+ { label: '上传人', field: 'userName', width: 100 }
324
+ ];
325
+ }
326
+ },
327
+ // 上传地址
328
+ action: String,
329
+ // 自动上传
330
+ autoUpload: {
331
+ type: Boolean,
332
+ default: true
333
+ },
334
+ // 是否支持多选文件
335
+ multiple: {
336
+ type: Boolean,
337
+ default: true
338
+ },
339
+ // 显示文件列表
340
+ showFileList: {
341
+ type: Boolean,
342
+ default: true
343
+ },
344
+ // 上传的文件列表
345
+ fileList: [Array, String],
346
+ requestFiles: {
347
+ type: Boolean,
348
+ default: true
349
+ },
350
+ filesParam: {
351
+ type: Object,
352
+ default() {
353
+ return {};
354
+ }
355
+ },
356
+ state: {
357
+ type: Number,
358
+ default: 1
359
+ },
360
+ selectType: {
361
+ type: String,
362
+ default: 'primary'
363
+ },
364
+ uploadType: {
365
+ type: String,
366
+ default: 'success'
367
+ },
368
+ host: {
369
+ type: String,
370
+ default() {
371
+ return util.getStorage('host') ? util.getStorage('host') : '';
372
+ }
373
+ },
374
+ message: Object,
375
+ btnSize: {
376
+ type: String,
377
+ default: 'medium'
378
+ },
379
+ // 只读
380
+ readonly: {
381
+ type: Boolean,
382
+ default: false
383
+ },
384
+ disabled: Boolean,
385
+ preview: { type: [Boolean, String], default: true },
386
+ exclude: String,
387
+ download: { type: [Boolean, String], default: true },
388
+ deleted: { type: [Boolean, String], default: true },
389
+ remove: { type: [Boolean, String], default: true },
390
+ beforeUpload: Function,
391
+ onPreview: Function,
392
+ // 文件列表排序
393
+ onSort: Function,
394
+ beforeRemove: Function,
395
+ onDownloads: Function,
396
+ onDownload: Function,
397
+ onSort: Function,
398
+ onSuccess: Function,
399
+ onChange: Function,
400
+ onRemove: Function,
401
+ onError: Function,
402
+ dangerouslyUseHTMLString: Boolean,
403
+ fileCount: Number,
404
+ listHeight: String,
405
+ display: Boolean,
406
+ resultFile: {
407
+ type: Boolean,
408
+ default: true
409
+ },
410
+ properties: {
411
+ type: String,
412
+ default() {
413
+ return getAdjunctProperties;
414
+ }
415
+ },
416
+ fileIcons: {
417
+ type: Object,
418
+ default() {
419
+ return {};
420
+ }
421
+ },
422
+ picture: {
423
+ type: Object,
424
+ default() {
425
+ return {};
426
+ }
427
+ },
428
+ previewScale: {
429
+ type: Boolean,
430
+ default: true
431
+ },
432
+ useCaseCode: String
433
+ },
434
+ data() {
435
+ return {
436
+ uploadUrl: this.action,
437
+ lists: [],
438
+ image: '',
439
+ fileAccept: '',
440
+ fileSize: 0,
441
+ fileTotalSize: 0,
442
+ filesTotalSize: 0,
443
+ showImg: false,
444
+ imgChange: false,
445
+ title: '',
446
+ imgUrl: '',
447
+ styles: {},
448
+ showVideo: false,
449
+ videoChange: false,
450
+ source: '',
451
+ excludeNames: undefined,
452
+ boxHeight: this.listHeight,
453
+ previewAdjunct: previewAdjunct,
454
+ kkfileview: null,
455
+ dochubConfig: {},
456
+ // 文档中台桶配置下发的分片上传配置(baseBucketConfig),null 表示未下发 → 回退组件 props
457
+ bucketChunkConfig: null
458
+ };
459
+ },
460
+ computed: {
461
+ isRender() {
462
+ let useCaseCodes = util.getStorage('useCaseCodes');
463
+ if (useCaseCodes && this.useCaseCode) {
464
+ return useCaseCodes.indexOf(this.useCaseCode) > -1;
465
+ }
466
+ return true;
467
+ },
468
+ _icons() {
469
+ return {
470
+ ppt: 'es-icon-ppt',
471
+ png: 'es-icon-tupian',
472
+ gif: 'es-icon-tupian',
473
+ jpg: 'es-icon-tupian',
474
+ jpeg: 'es-icon-tupian',
475
+ xls: 'es-icon-Excel',
476
+ xlsx: 'es-icon-Excel',
477
+ docx: 'es-icon-word',
478
+ doc: 'es-icon-word',
479
+ rar: 'es-icon-yasuobao',
480
+ zip: 'es-icon-yasuobao',
481
+ txt: 'es-icon-txt',
482
+ ipa: 'es-icon-ios',
483
+ apk: 'es-icon-android',
484
+ mp4: 'es-icon-kebofangwenjian',
485
+ avi: 'es-icon-kebofangwenjian',
486
+ mp3: 'es-icon-shengyinwenjian',
487
+ wma: 'es-icon-shengyinwenjian',
488
+ pdf: 'es-icon-pdf',
489
+ ...this.fileIcons
490
+ };
491
+ },
492
+ _picture() {
493
+ return {
494
+ ...picture,
495
+ ...this.picture
496
+ };
497
+ },
498
+ show() {
499
+ if ((this.photo && typeof this.photo === 'string') || this.value) {
500
+ return false;
501
+ }
502
+ return this.showFileList;
503
+ },
504
+ excludes() {
505
+ return this.exclude ? this.exclude : this.excludeNames;
506
+ },
507
+ accepts() {
508
+ return this.accept
509
+ ? this.accept
510
+ : this.fileAccept
511
+ ? this.fileAccept
512
+ : this.portrait
513
+ ? 'image/*'
514
+ : undefined;
515
+ },
516
+ _size() {
517
+ return this.size ? this.size : this.fileSize ? this.fileSize : 0;
518
+ },
519
+ _totalSize() {
520
+ return this.totalSize
521
+ ? this.totalSize
522
+ : this.fileTotalSize
523
+ ? this.fileTotalSize
524
+ : 0;
525
+ },
526
+ getAdjunctFileInfos() {
527
+ return typeof this.fileList === 'string'
528
+ ? this.fileList
529
+ : getAdjunctFileInfos;
530
+ },
531
+ showList() {
532
+ return this.portrait ? false : this.showFileList;
533
+ },
534
+ isDownloads() {
535
+ return this.downloads;
536
+ },
537
+ isDownload() {
538
+ if (this.download) {
539
+ return true;
540
+ }
541
+ return false;
542
+ },
543
+ isDisabled() {
544
+ return this.disabled || (this.elForm || {}).disabled;
545
+ },
546
+ isReadonly() {
547
+ return this.readonly || (this.elForm || {}).readonly;
548
+ },
549
+ isRemove() {
550
+ if (this.remove == false || this.deleted == false) {
551
+ return false;
552
+ }
553
+ return true;
554
+ },
555
+ isSort() {
556
+ // return this.dragSort && !this.isDisabled && this.isReadonly;
557
+ return this.dragSort && !this.isDisabled;
558
+ },
559
+ datas() {
560
+ if (this.data) {
561
+ return util.extend({}, this.data, this.params);
562
+ }
563
+ return this.params;
564
+ },
565
+ params() {
566
+ let params = { userId: util.getStorage('userId'), ...this.param };
567
+ if (this.code !== undefined) {
568
+ params.code = this.code;
569
+ params.bucketCode = this.code;
570
+ }
571
+ if (this.ownId !== undefined) {
572
+ params.ownId = this.ownId;
573
+ params.businessId = this.ownId;
574
+ }
575
+ return params;
576
+ },
577
+ showMessage() {
578
+ if (this.message !== undefined) {
579
+ return util.extend({}, { success: true, error: true }, this.message);
580
+ }
581
+ return { success: true, error: true };
582
+ },
583
+ profile() {
584
+ const previewAdjunct =
585
+ this.dochubConfig.downloadDocumentUrl || this.previewAdjunct;
586
+ if (this.image) {
587
+ if (typeof this.image === 'string') {
588
+ return this.image;
589
+ }
590
+ return `${this.host}${previewAdjunct}?documentId=${
591
+ this.image.adjunctId
592
+ }&adjunctId=${this.image.adjunctId}&_tt=${new Date().getTime()}`;
593
+ }
594
+ if (this.value) {
595
+ if (typeof this.value === 'string') {
596
+ if (this.value.indexOf('data:image/') === 0) {
597
+ return this.value;
598
+ } else if (this.value.indexOf(previewAdjunct) > -1) {
599
+ let val =
600
+ this.host +
601
+ util.jointUrl({
602
+ url: this.value,
603
+ reg: '/main2'
604
+ });
605
+ return val;
606
+ } else if (this.value.indexOf('/') === -1) {
607
+ return `${this.host}${previewAdjunct}?documentId=${
608
+ this.value
609
+ }&adjunctId=${this.value}&_tt=${new Date().getTime()}`;
610
+ }
611
+ return this.value;
612
+ } else if (Array.isArray(this.value)) {
613
+ let adjunctId = this.value[0].response
614
+ ? this.value[0].response.adjunctId
615
+ : this.value[0].adjunctId;
616
+ return `${
617
+ this.host
618
+ }${previewAdjunct}?documentId=${adjunctId}&adjunctId=${adjunctId}&_tt=${new Date().getTime()}`;
619
+ } else {
620
+ let adjunctId = this.value.response
621
+ ? this.value.response.adjunctId
622
+ : this.value.adjunctId;
623
+ return `${
624
+ this.host
625
+ }${previewAdjunct}?documentId=${adjunctId}&adjunctId=${adjunctId}&_tt=${new Date().getTime()}`;
626
+ }
627
+ }
628
+ if (this.photo && typeof this.photo === 'string') {
629
+ if (this.photo.indexOf('data:image/') === 0) {
630
+ return this.photo;
631
+ } else if (this.photo.indexOf(previewAdjunct) > -1) {
632
+ let val =
633
+ this.host +
634
+ util.jointUrl({
635
+ url: this.value,
636
+ reg: '/main2'
637
+ });
638
+ return val;
639
+ } else if (this.photo.indexOf('/') === -1) {
640
+ return `${this.host}${previewAdjunct}?documentId=${
641
+ this.photo
642
+ }&adjunctId=${this.photo}&_tt=${new Date().getTime()}`;
643
+ }
644
+ }
645
+ return this.photo;
646
+ },
647
+ // 是否走分片上传:完全由后台桶配置(/v2/chunk/config)的开关决定,且具备桶编码(code)与业务id(ownId)
648
+ isChunk() {
649
+ const cfg = this.bucketChunkConfig;
650
+ return !!(cfg && cfg.enableChunkUpload) && !!this.code && !!this.ownId;
651
+ },
652
+ // 生效的分片大小(字节):桶配置 > 默认 5MB
653
+ _chunkSize() {
654
+ const cfg = this.bucketChunkConfig;
655
+ return (
656
+ (cfg && Number(cfg.chunkSize) > 0 && Number(cfg.chunkSize)) ||
657
+ 5 * 1024 * 1024
658
+ );
659
+ },
660
+ // 生效的分片并发数:桶配置 > 默认 3
661
+ _chunkConcurrent() {
662
+ const cfg = this.bucketChunkConfig;
663
+ return (
664
+ (cfg &&
665
+ Number(cfg.chunkConcurrent) > 0 &&
666
+ Number(cfg.chunkConcurrent)) ||
667
+ 3
668
+ );
669
+ },
670
+ // 分片三接口地址:优先 dochubConfig 下发,否则回退 api.js 常量(与本组件 dochubConfig.x || 常量 的写法一致)
671
+ chunkUrls() {
672
+ const cfg = this.dochubConfig || {};
673
+ return {
674
+ check: cfg.chunkCheckUrl || chunkCheckFile,
675
+ upload: cfg.chunkUploadUrl || chunkUploadChunk,
676
+ merge: cfg.chunkMergeUrl || chunkMergeChunk
677
+ };
678
+ },
679
+ // 传给 el-upload 的自定义上传实现;非分片模式返回 undefined 以走默认上传
680
+ chunkHttpRequest() {
681
+ return this.isChunk ? this.chunkUpload : undefined;
682
+ },
683
+ // 操作列按钮:非分片模式回退用户经 $attrs 传入的 handles(或 undefined → el-upload 默认),
684
+ // 分片模式注入"暂停/继续"并保留预览/下载/删除(emit 复用既有事件链)。仅分片模式改变渲染,不影响存量业务。
685
+ chunkHandles() {
686
+ if (!this.isChunk) return this.$attrs.handles;
687
+ const done = (f) => f.status === 'success' || f.status == 0;
688
+ return [
689
+ {
690
+ icon: 'el-icon-video-pause',
691
+ title: '暂停',
692
+ rules: (f) => this.isChunkUploading(f) && !f.chunkPaused,
693
+ event: (f) => this.pauseUpload(f)
694
+ },
695
+ {
696
+ icon: 'el-icon-video-play',
697
+ title: '继续',
698
+ rules: (f) => this.isChunkUploading(f) && !!f.chunkPaused,
699
+ event: (f) => this.resumeUpload(f)
700
+ },
701
+ {
702
+ icon: 'el-icon-document-copy',
703
+ title: '预览',
704
+ rules: (f) => !!this.preview && done(f),
705
+ emit: 'preview'
706
+ },
707
+ {
708
+ icon: 'el-icon-download',
709
+ title: '下载',
710
+ rules: (f) => this.isDownload && done(f),
711
+ emit: 'download'
712
+ },
713
+ {
714
+ icon: 'el-icon-delete',
715
+ title: '删除',
716
+ rules: (f) => this.isRemove,
717
+ emit: 'remove'
718
+ }
719
+ ];
720
+ }
721
+ },
722
+ watch: {
723
+ fileList: {
724
+ immediate: true,
725
+ deep: true,
726
+ handler(val) {
727
+ if (Array.isArray(val) && val.length > 0) {
728
+ this.lists = val;
729
+ let filesTotalSize = 0;
730
+ val.forEach((item) => {
731
+ if (
732
+ Object.prototype.hasOwnProperty.call(item, 'fileSize') &&
733
+ item.fileSize
734
+ ) {
735
+ filesTotalSize += parseFloat(item.fileSize, 10);
736
+ } else {
737
+ filesTotalSize += item.size
738
+ ? Math.round((item.size / 1024) * 10) / 10
739
+ : 0;
740
+ }
741
+ });
742
+ this.filesTotalSize = filesTotalSize;
743
+ this.$emit('input', val);
744
+ }
745
+ }
746
+ },
747
+ value: {
748
+ immediate: true,
749
+ deep: true,
750
+ handler(val) {
751
+ if (val && typeof val === 'object' && this.showFileList) {
752
+ if (Array.isArray(val)) {
753
+ this.lists = val;
754
+ } else {
755
+ this.lists = [val];
756
+ }
757
+ }
758
+ }
759
+ },
760
+ params: {
761
+ deep: true,
762
+ handler() {
763
+ this.getFiles();
764
+ }
765
+ },
766
+ lists: {
767
+ deep: true,
768
+ handler(val) {
769
+ this.$emit('update:fileCount', val.length);
770
+ this.$emit('getFilesAmount', val.length);
771
+ }
772
+ },
773
+ display(val) {
774
+ val && this.getHeight();
775
+ }
776
+ },
777
+ beforeCreate() {
778
+ this.getFiles = debounce(500, () => {
779
+ this.getFileLists();
780
+ });
781
+ this.getAdjunctPropertie = debounce(500, () => {
782
+ this.getAdjunctProperties();
783
+ });
784
+ },
785
+ created() {
786
+ // 分片上传的运行态(按 file.uid 索引,非响应式,仅用于流程控制)
787
+ this._chunkState = {};
788
+ const dochubConfig = sessionStorage.getItem('dochubConfig');
789
+ if (dochubConfig) {
790
+ this.dochubConfig = JSON.parse(dochubConfig);
791
+ }
792
+ this.getAdjunctPropertie();
793
+ },
794
+ mounted() {
795
+ this.$nextTick(() => {
796
+ this.getHeight();
797
+ });
798
+ },
799
+ methods: {
800
+ getHeight() {
801
+ if (this.height === 'auto') {
802
+ let pt =
803
+ parseInt(util.getStyle(this.$el.parentNode, 'padding-top'), 10) || 0;
804
+ let pb =
805
+ parseInt(util.getStyle(this.$el.parentNode, 'padding-bottom'), 10) ||
806
+ 0;
807
+ let btn = this.$refs.upload
808
+ ? this.$refs.upload.$children[0].$el.offsetHeight || 0
809
+ : 0;
810
+ this.boxHeight =
811
+ this.$el.parentNode.offsetHeight - pt - pb - btn + 'px';
812
+ }
813
+ },
814
+ //根据code获取附件参数配置
815
+ getAdjunctProperties() {
816
+ if (this.code !== undefined) {
817
+ let config = store.get(this.code);
818
+ if (config) {
819
+ this.fileAccept = config.accept;
820
+ this.fileSize = config.size;
821
+ this.fileTotalSize = config.totalSize;
822
+ if (config.dochubConfig) {
823
+ this.dochubConfig = config.dochubConfig;
824
+ if (config.bucketChunkConfig !== undefined) {
825
+ this.bucketChunkConfig = config.bucketChunkConfig;
826
+ } else {
827
+ this.getBucketChunkConfig();
828
+ }
829
+ }
830
+
831
+ let url =
832
+ this.portrait || this.documentId
833
+ ? this.dochubConfig.reuploadDocumentUrl || uploadOnlyOne
834
+ : this.dochubConfig.uploadDocumentUrl || uploads;
835
+ this.uploadUrl = url.indexOf(this.host) > -1 ? url : this.host + url;
836
+ this.requestFiles && this.getFiles();
837
+ } else {
838
+ util
839
+ .ajax({
840
+ method: this.method,
841
+ url: this.properties,
842
+ data: { code: this.code, ...this.param },
843
+ params: { code: this.code, ...this.param }
844
+ })
845
+ .then((res) => {
846
+ if (res.rCode === 0) {
847
+ if (res.results) {
848
+ this.excludeNames = res.results.excludeName;
849
+ if (res.results.fileTypeExtName) {
850
+ let fileTypeExtName =
851
+ res.results.fileTypeExtName.split(';');
852
+ this.fileAccept = fileTypeExtName
853
+ .filter((item) => {
854
+ return item;
855
+ })
856
+ .join(',');
857
+ }
858
+ if (res.results.dochubConfig) {
859
+ this.dochubConfig = res.results.dochubConfig;
860
+ sessionStorage.setItem(
861
+ 'dochubConfig',
862
+ JSON.stringify(res.results.dochubConfig)
863
+ );
864
+ // dochubConfig 有数据 → 走文档中台,再拉取该桶的桶配置(分片上传开关/参数)
865
+ this.getBucketChunkConfig();
866
+ }
867
+ let url = this.portrait
868
+ ? this.dochubConfig.reuploadDocumentUrl || uploadOnlyOne
869
+ : this.dochubConfig.uploadDocumentUrl || uploads;
870
+ this.uploadUrl =
871
+ url.indexOf(this.host) > -1 ? url : this.host + url;
872
+ if (res.results.kkViewRootPath) {
873
+ this.kkfileview = res.results.kkViewRootPath;
874
+ }
875
+ this.fileSize = res.results.limitFileSize
876
+ ? res.results.limitFileSize
877
+ : 0;
878
+ this.fileTotalSize = res.results.limitTotalSize;
879
+ store.set(this.code, {
880
+ accept: this.fileAccept,
881
+ size: this.fileSize,
882
+ totalSize: this.fileTotalSize,
883
+ dochubConfig: this.dochubConfig
884
+ });
885
+ }
886
+ this.requestFiles && this.getFiles();
887
+ }
888
+ })
889
+ .catch((err) => {
890
+ if (err.message && err.message !== 'canceled') {
891
+ this.$message.error(err.message);
892
+ }
893
+ });
894
+ }
895
+ }
896
+ },
897
+ // 根据桶编码调中台获取桶配置接口(/v2/getBucket),取 baseBucketConfig 中的分片上传开关与参数。
898
+ getBucketChunkConfig() {
899
+ if (!this.code) return;
900
+ let url = getDochubBucket;
901
+ util
902
+ .ajax({
903
+ method: this.method,
904
+ url: url,
905
+ data: { bucketCode: this.code },
906
+ params: { bucketCode: this.code }
907
+ })
908
+ .then((res) => {
909
+ if (res.rCode === 0 && res.results && res.results.baseBucketConfig) {
910
+ const cfg = res.results.baseBucketConfig;
911
+ this.bucketChunkConfig = {
912
+ enableChunkUpload: cfg.enableChunkUpload,
913
+ chunkSize: cfg.chunkSize,
914
+ chunkConcurrent: cfg.chunkConcurrent
915
+ };
916
+ } else {
917
+ this.bucketChunkConfig = null;
918
+ }
919
+ // 回写本地缓存,避免同 code 的其他实例重复请求
920
+ let cached = store.get(this.code);
921
+ if (cached) {
922
+ store.set(this.code, {
923
+ ...cached,
924
+ bucketChunkConfig: this.bucketChunkConfig
925
+ });
926
+ }
927
+ })
928
+ .catch(() => {
929
+ // 桶配置获取失败不阻断上传,维持 props 行为
930
+ this.bucketChunkConfig = null;
931
+ });
932
+ },
933
+ getFileLists() {
934
+ if (
935
+ !this.show ||
936
+ (this.fileList &&
937
+ Array.isArray(this.fileList) &&
938
+ this.fileList.length) ||
939
+ (this.requiredOwnId &&
940
+ !Object.prototype.hasOwnProperty.call(this.params, 'ownId'))
941
+ ) {
942
+ return false;
943
+ }
944
+ let url =
945
+ typeof this.fileList === 'string'
946
+ ? this.fileList
947
+ : this.dochubConfig.getDocumentList
948
+ ? this.dochubConfig.getDocumentList
949
+ : getAdjunctFileInfos;
950
+ util
951
+ .ajax({
952
+ method: this.method,
953
+ url: url,
954
+ data: this.params,
955
+ params: this.params,
956
+ format: false
957
+ })
958
+ .then((res) => {
959
+ if (res.rCode === 0) {
960
+ if (this.portrait === true) {
961
+ this.image = JSON.parse(JSON.stringify(res.results))[0];
962
+ } else {
963
+ this.lists = JSON.parse(JSON.stringify(res.results));
964
+ let filesTotalSize = 0;
965
+ this.lists.forEach((item) => {
966
+ if (
967
+ Object.prototype.hasOwnProperty.call(item, 'fileSize') &&
968
+ item.fileSize
969
+ ) {
970
+ filesTotalSize += parseFloat(item.fileSize, 10);
971
+ } else {
972
+ filesTotalSize += item.size
973
+ ? Math.round((item.size / 1024) * 10) / 10
974
+ : 0;
975
+ }
976
+ });
977
+ this.filesTotalSize = filesTotalSize;
978
+ if (this.lists.length) {
979
+ this.$emit('input', this.lists);
980
+ }
981
+ }
982
+ } else {
983
+ let msg = res.msg || '系统错误,请联系管理员!';
984
+ this.$message.error(msg);
985
+ }
986
+ })
987
+ .catch((err) => {
988
+ if (err.message && err.message !== 'canceled') {
989
+ this.$message.error(err.message);
990
+ }
991
+ });
992
+ },
993
+ handleUpload() {
994
+ this.$refs.upload.submit();
995
+ },
996
+ openKkfileview(file) {
997
+ let host = util.win.location.origin;
998
+ if (this.host) {
999
+ if (util.startWith(this.host, 'http')) {
1000
+ host = this.host;
1001
+ } else {
1002
+ host += this.host;
1003
+ }
1004
+ }
1005
+ let url = encodeURIComponent(
1006
+ Base64.encode(
1007
+ host +
1008
+ (typeof this.download === 'string'
1009
+ ? this.download
1010
+ : downloadByAdjunctId) +
1011
+ `?adjunctId=${
1012
+ file.adjunctId || file.response.adjunctId
1013
+ }&fullfilename=${file.originalName}`
1014
+ )
1015
+ );
1016
+ util.win.open(`${this.kkfileview}?url=${url}`);
1017
+ },
1018
+ handlePreview(res) {
1019
+ if (this.preview) {
1020
+ if (this.onPreview) {
1021
+ this.onPreview(res);
1022
+ } else {
1023
+ let file = res.response ? res.response : res;
1024
+ let url =
1025
+ typeof this.preview === 'string'
1026
+ ? this.preview
1027
+ : typeof this.preview === 'function'
1028
+ ? this.preview(file)
1029
+ : null;
1030
+ if (res.status === 'success' || res.status == 0) {
1031
+ let suffix = file.suffix;
1032
+ suffix = suffix.toLowerCase();
1033
+ if (this.dochubConfig.previewDocumentUrl) {
1034
+ if (
1035
+ suffix.includes('jpeg') ||
1036
+ suffix.includes('jpg') ||
1037
+ suffix.includes('gif') ||
1038
+ suffix.includes('png')
1039
+ ) {
1040
+ this.imgUrl =
1041
+ res.url && util.startWith(url, ['http', '/', true])
1042
+ ? res.url
1043
+ : this.host +
1044
+ (url
1045
+ ? url
1046
+ : `${this.dochubConfig.downloadDocumentUrl}?documentId=${file.adjunctId}`);
1047
+ this.title = file.originalName;
1048
+ this.showImg = true;
1049
+ this.$nextTick(() => {
1050
+ this.loadImage();
1051
+ });
1052
+ } else if (suffix.includes('mp4')) {
1053
+ this.source = {
1054
+ src:
1055
+ this.host +
1056
+ (url
1057
+ ? url
1058
+ : `${this.dochubConfig.downloadDocumentUrl}?documentId=${file.adjunctId}`)
1059
+ };
1060
+ this.title = file.originalName;
1061
+ this.showVideo = true;
1062
+ } else {
1063
+ util.win.open(
1064
+ this.host +
1065
+ (url
1066
+ ? url
1067
+ : `${this.dochubConfig.previewDocumentUrl}?documentId=${file.adjunctId}&action=附件预览`)
1068
+ );
1069
+ }
1070
+ } else {
1071
+ if (
1072
+ suffix.includes('doc') ||
1073
+ suffix.includes('docx') ||
1074
+ suffix.includes('xls') ||
1075
+ suffix.includes('xlsx') ||
1076
+ suffix.includes('ppt')
1077
+ ) {
1078
+ if (this.kkfileview) {
1079
+ this.openKkfileview(file);
1080
+ } else {
1081
+ util.win.open(
1082
+ this.host +
1083
+ (url ? url : previewAdjunctOffice) +
1084
+ '?cmd=view&bucketName=' +
1085
+ file.absolutePath +
1086
+ '&fileId=' +
1087
+ file.adjunctId +
1088
+ '&fileName=' +
1089
+ file.newName
1090
+ );
1091
+ }
1092
+ } else if (suffix.includes('pdf')) {
1093
+ if (this.kkfileview) {
1094
+ this.openKkfileview(file);
1095
+ } else {
1096
+ util.win.open(
1097
+ this.host +
1098
+ (url ? url : previewAdjunct2) +
1099
+ '/' +
1100
+ file.originalName +
1101
+ '?adjunctId=' +
1102
+ file.adjunctId
1103
+ );
1104
+ }
1105
+ } else if (
1106
+ suffix.includes('jpeg') ||
1107
+ suffix.includes('jpg') ||
1108
+ suffix.includes('gif') ||
1109
+ suffix.includes('png')
1110
+ ) {
1111
+ this.imgUrl =
1112
+ res.url && util.startWith(url, ['http', '/', true])
1113
+ ? res.url
1114
+ : this.host +
1115
+ (url ? url : this.previewAdjunct) +
1116
+ '?adjunctId=' +
1117
+ file.adjunctId;
1118
+ this.title = file.originalName;
1119
+ this.showImg = true;
1120
+ this.$nextTick(() => {
1121
+ this.loadImage();
1122
+ });
1123
+ } else if (suffix.includes('mp4')) {
1124
+ this.source = {
1125
+ src:
1126
+ this.host +
1127
+ (url ? url : this.previewAdjunct) +
1128
+ '?adjunctId=' +
1129
+ file.adjunctId
1130
+ };
1131
+ this.title = file.originalName;
1132
+ this.showVideo = true;
1133
+ } else {
1134
+ if (this.kkfileview) {
1135
+ this.openKkfileview(file);
1136
+ } else {
1137
+ util.win.open(
1138
+ this.host +
1139
+ (url ? url : this.previewAdjunct) +
1140
+ '?adjunctId=' +
1141
+ file.adjunctId
1142
+ );
1143
+ }
1144
+ }
1145
+ }
1146
+ } else {
1147
+ util.win.open(URL.createObjectURL(file.raw));
1148
+ }
1149
+ }
1150
+ }
1151
+ },
1152
+ closeViewer() {
1153
+ this.showImg = false;
1154
+ },
1155
+ loadImage() {
1156
+ this.$refs.showImg &&
1157
+ (this.$refs.showImg.onload = () => {
1158
+ this.imgChange = false;
1159
+ let w = this.$refs.showImg.naturalWidth;
1160
+ let h = this.$refs.showImg.naturalHeight;
1161
+ let pw = this.$refs.showImg.parentNode.offsetWidth;
1162
+ let ph = this.$refs.showImg.parentNode.offsetHeight;
1163
+ if (w / h > pw / ph) {
1164
+ this.styles = { 'max-width': '100%' };
1165
+ } else {
1166
+ this.styles = { 'max-height': '100%' };
1167
+ }
1168
+ if (w < pw) {
1169
+ this.styles.width = w + 'px';
1170
+ }
1171
+ if (h < ph) {
1172
+ this.styles.height = h + 'px';
1173
+ }
1174
+ });
1175
+ },
1176
+ isLt(file) {
1177
+ let flag = false;
1178
+ let flieArr = file.name.split('.');
1179
+ let suffix = '.' + flieArr[flieArr.length - 1].toLowerCase();
1180
+ if (this.accepts) {
1181
+ let accepts = this.accepts.toLowerCase().split(',');
1182
+ flag = accepts.includes(suffix);
1183
+ if (!flag) {
1184
+ this.$message.error(`文件类型错误,请上传${this.accepts}类型文件!`);
1185
+ return flag;
1186
+ }
1187
+ }
1188
+ let size = this._size;
1189
+ let text = '单个附件';
1190
+ let se = Math.round((file.size / 1024) * 10) / 10;
1191
+ if (this._size === 0 && this._totalSize === 0) {
1192
+ flag = true;
1193
+ } else if (this._size > 0 && this._totalSize === 0) {
1194
+ flag = se < this._size;
1195
+ size = this._size;
1196
+ text = '单个附件';
1197
+ } else if (this._size === 0 && this._totalSize > 0) {
1198
+ flag = this.filesTotalSize + se < this._totalSize;
1199
+ size = this._totalSize;
1200
+ text = '附件总';
1201
+ } else {
1202
+ flag = se < this._size && this.filesTotalSize + se < this._totalSize;
1203
+ if (se < this._size) {
1204
+ size = this._size;
1205
+ text = '单个附件';
1206
+ } else {
1207
+ size = this._totalSize;
1208
+ text = '附件总';
1209
+ }
1210
+ }
1211
+ if (!flag) {
1212
+ this.$message.error(`上传${text}大小不能超过${size}KB!`);
1213
+ return flag;
1214
+ }
1215
+ return flag;
1216
+ },
1217
+ handleBeforeUpload(file) {
1218
+ const flag = this.isLt(file);
1219
+ if (this.portrait) {
1220
+ if (flag && this.beforeUpload) {
1221
+ return this.beforeUpload(file);
1222
+ }
1223
+ return flag;
1224
+ }
1225
+ if (this.beforeUpload) {
1226
+ if (flag) {
1227
+ return this.beforeUpload(file);
1228
+ }
1229
+ return flag;
1230
+ }
1231
+ return flag;
1232
+ },
1233
+ handleBeforeRemove(file, fileList) {
1234
+ if (file && (file.status === 'success' || file.status == 0)) {
1235
+ return this.$confirm('确定删除文件吗?', '提示', {
1236
+ confirmButtonText: '确定',
1237
+ cancelButtonText: '取消',
1238
+ type: 'warning'
1239
+ })
1240
+ .then(() => {
1241
+ if (this.beforeRemove) {
1242
+ return this.beforeRemove(file, fileList);
1243
+ } else if (file.status === 'success' || file.status == 0) {
1244
+ let userName =
1245
+ file.userName ||
1246
+ (file.response && file.response.userName) ||
1247
+ util.getStorage('userName');
1248
+ // eslint-disable-next-line no-undef
1249
+ return new Promise((resolve, reject) => {
1250
+ let url =
1251
+ typeof this.deleted === 'string'
1252
+ ? this.deleted
1253
+ : this.remove === 'string'
1254
+ ? this.remove
1255
+ : this.dochubConfig.deleteDocumentUrl || delAdjunct;
1256
+ util
1257
+ .ajax({
1258
+ method: this.method,
1259
+ url: url,
1260
+ data: {
1261
+ userName: userName,
1262
+ documentId: file.adjunctId || file.response.adjunctId,
1263
+ id: file.adjunctId || file.response.adjunctId
1264
+ },
1265
+ params: {
1266
+ userName: userName,
1267
+ documentId: file.adjunctId || file.response.adjunctId,
1268
+ id: file.adjunctId || file.response.adjunctId
1269
+ }
1270
+ })
1271
+ .then((res) => {
1272
+ if (res.rCode === 0) {
1273
+ let se =
1274
+ this.filesTotalSize -
1275
+ Math.round((file.size / 1024) * 10) / 10;
1276
+ this.filesTotalSize = se;
1277
+ this.$message.success(res.msg);
1278
+ resolve();
1279
+ } else {
1280
+ let msg = res.msg || '系统错误,请联系管理员!';
1281
+ this.$message.error(msg);
1282
+ reject();
1283
+ }
1284
+ })
1285
+ .catch((err) => {
1286
+ if (err.message && err.message !== 'canceled') {
1287
+ this.$message.error(err.message);
1288
+ }
1289
+ });
1290
+ });
1291
+ }
1292
+ return true;
1293
+ })
1294
+ .catch(() => {
1295
+ return flag;
1296
+ });
1297
+ }
1298
+ },
1299
+ handleDownloads() {
1300
+ let url =
1301
+ typeof this.downloads === 'string'
1302
+ ? this.downloads
1303
+ : this.dochubConfig.compressDownloadDocumentUrl || uploadDownloads;
1304
+ util.win.open(
1305
+ this.host +
1306
+ url +
1307
+ '?ownId=' +
1308
+ this.ownId +
1309
+ '&code=' +
1310
+ this.code +
1311
+ (this.extendCode ? '&extendCode=' + this.extendCode : '') +
1312
+ '&businessId=' +
1313
+ this.ownId +
1314
+ '&fileName=文档.zip' +
1315
+ '&bucketCode=' +
1316
+ this.code
1317
+ );
1318
+ },
1319
+ handleDownload(file) {
1320
+ if (file.status === 'success' || file.status == 0) {
1321
+ let url =
1322
+ typeof this.download === 'string'
1323
+ ? this.download
1324
+ : this.dochubConfig.downloadDocumentUrl || downloadByAdjunctId;
1325
+ util.win.open(
1326
+ this.host +
1327
+ url +
1328
+ '?adjunctId=' +
1329
+ (file.adjunctId || file.response.adjunctId) +
1330
+ '&documentId=' +
1331
+ (file.adjunctId || file.response.adjunctId)
1332
+ );
1333
+ } else {
1334
+ this.aLinkDownload(file);
1335
+ }
1336
+ },
1337
+ handleClick(res) {
1338
+ if (res.event && typeof res.event === 'function') {
1339
+ res.event(res);
1340
+ } else {
1341
+ this.$emit('click', res);
1342
+ }
1343
+ },
1344
+ aLinkDownload(file) {
1345
+ var a = document.createElement('a');
1346
+ let event = new MouseEvent('click');
1347
+ a.href = URL.createObjectURL(file.raw);
1348
+ a.download = file.name;
1349
+ a.dispatchEvent(event);
1350
+ },
1351
+ handleSort(files) {
1352
+ if (this.isSort) {
1353
+ if (this.onSort) {
1354
+ this.onSort(files);
1355
+ } else {
1356
+ if (
1357
+ !this.ownId ||
1358
+ !this.code ||
1359
+ (this.fileList && this.fileList.length > 0)
1360
+ )
1361
+ return;
1362
+ let ids = files.map((item) => {
1363
+ if (item.status === 'success' || item.status == 0) {
1364
+ return item.adjunctId || item.response.adjunctId;
1365
+ }
1366
+ });
1367
+ ids = ids.join(',');
1368
+ util
1369
+ .ajax({
1370
+ method: this.method,
1371
+ url: this.dochubConfig.sortDocumentsUrl || uploadSort,
1372
+ data: {
1373
+ ids: ids,
1374
+ documentIds: ids,
1375
+ ownId: this.ownId,
1376
+ code: this.code,
1377
+ bucketCode: this.code,
1378
+ businessId: this.ownId
1379
+ },
1380
+ params: {
1381
+ ids: ids,
1382
+ documentIds: ids,
1383
+ ownId: this.ownId,
1384
+ code: this.code,
1385
+ bucketCode: this.code,
1386
+ businessId: this.ownId
1387
+ }
1388
+ })
1389
+ .then((res) => {
1390
+ if (res.rCode === 0) {
1391
+ this.$message.success(res.msg);
1392
+ } else {
1393
+ let msg = res.msg || '系统错误,请联系管理员!';
1394
+ this.$message.error(msg);
1395
+ }
1396
+ })
1397
+ .catch((err) => {
1398
+ if (err.message && err.message !== 'canceled') {
1399
+ this.$message.error(err.message);
1400
+ }
1401
+ });
1402
+ }
1403
+ }
1404
+ },
1405
+ handleSuccess(response, file, fileList) {
1406
+ if (this.portrait) {
1407
+ this.image = URL.createObjectURL(file.raw);
1408
+ }
1409
+ if (this.showMessage.success) {
1410
+ if (response.rCode) {
1411
+ if (this.dangerouslyUseHTMLString) {
1412
+ this.$alert(
1413
+ response.msg ? response.msg : '上传失败:请联系管理员',
1414
+ '提示',
1415
+ {
1416
+ dangerouslyUseHTMLString: true,
1417
+ setHeight: response.msg
1418
+ }
1419
+ ).catch();
1420
+ } else {
1421
+ this.$message({
1422
+ type: 'error',
1423
+ message: response.msg || '成功!'
1424
+ });
1425
+ }
1426
+ } else {
1427
+ this.$message.success(response.msg);
1428
+ }
1429
+ }
1430
+ let se =
1431
+ this.filesTotalSize +
1432
+ (this.resultFile ? Math.round((file.size / 1024) * 10) / 10 : 0);
1433
+ this.filesTotalSize = se;
1434
+ this.onSuccess &&
1435
+ this.onSuccess(response, file, fileList, this.dochubConfig);
1436
+ this.$emit('success', response, file, fileList, this.dochubConfig);
1437
+ if (this.resultFile) {
1438
+ if (this.portrait) {
1439
+ this.$emit('input', file);
1440
+ } else {
1441
+ this.$emit('input', fileList);
1442
+ }
1443
+ if (this.elForm) {
1444
+ this.elForm.clearValidate(this.name);
1445
+ }
1446
+ }
1447
+ },
1448
+ handleChange(file, fileList) {
1449
+ this.$emit('change', fileList);
1450
+ this.onChange && this.onChange(file, fileList);
1451
+ },
1452
+ handleRemove(file, fileList) {
1453
+ this.cleanupChunk(file);
1454
+ this.$emit('input', fileList.length ? fileList : '');
1455
+ this.$emit('remove', file, fileList);
1456
+ this.$emit('change', fileList);
1457
+ this.onRemove && this.onRemove(file, fileList);
1458
+ },
1459
+ handleError(err, file, fileList) {
1460
+ if (this.showMessage.error) {
1461
+ if (this.dangerouslyUseHTMLString) {
1462
+ this.$alert(err.msg ? err.msg : '上传失败:请联系管理员', '提示', {
1463
+ dangerouslyUseHTMLString: true,
1464
+ setHeight: err.msg
1465
+ }).catch();
1466
+ } else {
1467
+ this.$message({
1468
+ type: 'error',
1469
+ message: '上传失败:' + (err.msg ? err.msg : '请联系管理员')
1470
+ });
1471
+ }
1472
+ }
1473
+ this.$emit('error', err, file, fileList);
1474
+ this.onError && this.onError(err, file, fileList);
1475
+ },
1476
+ // el-upload 的自定义上传实现:建立可暂停的运行态并返回受控 Promise。
1477
+ // 暂停时不 resolve/reject(promise 维持 pending,文件保持"上传中"而非失败),继续时再次进入流程。
1478
+ chunkUpload(options) {
1479
+ const file = options.file;
1480
+ const uid = file.uid;
1481
+ const state = {
1482
+ paused: false,
1483
+ controller: null,
1484
+ fileMd5: '',
1485
+ totalChunks: Math.max(1, Math.ceil(file.size / this._chunkSize)),
1486
+ options: options
1487
+ };
1488
+ this._chunkState[uid] = state;
1489
+ return new Promise((resolve, reject) => {
1490
+ state.resolve = resolve;
1491
+ state.reject = reject;
1492
+ this.runChunkFlow(uid);
1493
+ });
1494
+ },
1495
+ // 分片主流程(可被"继续"重新进入):MD5(缓存) → 秒传/断点续传检测 → 并发上传缺失分片 → 合并入库。
1496
+ runChunkFlow(uid) {
1497
+ const state = this._chunkState[uid];
1498
+ if (!state) return;
1499
+ const options = state.options;
1500
+ const file = options.file;
1501
+ const onProgress = options.onProgress || function () {};
1502
+ const baseParams = () => ({
1503
+ ...this.param,
1504
+ fileMd5: state.fileMd5,
1505
+ fileName: file.name,
1506
+ fileSize: file.size,
1507
+ totalChunks: state.totalChunks,
1508
+ bucketCode: this.code,
1509
+ businessId: this.ownId
1510
+ });
1511
+ const isAbort = (e) =>
1512
+ !!e &&
1513
+ (e.__aborted === true ||
1514
+ e.name === 'CanceledError' ||
1515
+ e.name === 'AbortError' ||
1516
+ (typeof e.message === 'string' && /cancel|abort/i.test(e.message)));
1517
+
1518
+ Promise.resolve()
1519
+ .then(() => state.fileMd5 || this.computeFileMd5(file, onProgress))
1520
+ .then((md5) => {
1521
+ state.fileMd5 = md5;
1522
+ if (state.paused) return null;
1523
+ return util.ajax({
1524
+ method: 'post',
1525
+ url: this.chunkUrls.check,
1526
+ data: baseParams(),
1527
+ params: baseParams(),
1528
+ headers: options.headers
1529
+ });
1530
+ })
1531
+ .then((checkRes) => {
1532
+ if (!checkRes) return; // 暂停于 MD5/检测阶段,维持 pending
1533
+ if (checkRes.rCode !== 0) return Promise.reject(checkRes);
1534
+ const info = checkRes.results || {};
1535
+ // 秒传命中:该业务桶下已存在同 MD5 文件,直接视为成功
1536
+ if (info.instant) {
1537
+ onProgress({ percent: 100 });
1538
+ this.normalizeChunkDoc(info, file.name);
1539
+ this.finishChunk(uid, {
1540
+ rCode: 0,
1541
+ msg: checkRes.msg || '秒传成功',
1542
+ results: info
1543
+ });
1544
+ // 秒传文件已存在于服务端,刷新列表以展示其完整信息(含可下载/预览的 id)
1545
+ this.requestFiles && this.getFiles();
1546
+ return;
1547
+ }
1548
+ // 断点续传:跳过服务端已存在的分片
1549
+ const uploaded = new Set(info.uploadedChunks || []);
1550
+ const pending = [];
1551
+ for (let i = 0; i < state.totalChunks; i++) {
1552
+ if (!uploaded.has(i)) pending.push(i);
1553
+ }
1554
+ let finished = state.totalChunks - pending.length;
1555
+ // 上传阶段占 5~95%(MD5 校验已占 0~5%),保证进度单调不回退
1556
+ onProgress({
1557
+ percent: Math.min(
1558
+ 95,
1559
+ 5 + Math.round((finished / state.totalChunks) * 90)
1560
+ )
1561
+ });
1562
+ state.controller =
1563
+ typeof AbortController !== 'undefined'
1564
+ ? new AbortController()
1565
+ : null;
1566
+ const signal = state.controller ? state.controller.signal : undefined;
1567
+ return this.runChunkQueue(pending, this._chunkConcurrent, (index) => {
1568
+ if (state.paused) return Promise.reject({ __aborted: true });
1569
+ return this.uploadOneChunk(
1570
+ file,
1571
+ index,
1572
+ state.totalChunks,
1573
+ state.fileMd5,
1574
+ options.headers,
1575
+ signal
1576
+ ).then(() => {
1577
+ finished++;
1578
+ onProgress({
1579
+ percent: Math.min(
1580
+ 95,
1581
+ 5 + Math.round((finished / state.totalChunks) * 90)
1582
+ )
1583
+ });
1584
+ });
1585
+ }).then(() => {
1586
+ if (state.paused) return; // 暂停于上传阶段,维持 pending
1587
+ return util
1588
+ .ajax({
1589
+ method: 'post',
1590
+ url: this.chunkUrls.merge,
1591
+ data: baseParams(),
1592
+ params: baseParams(),
1593
+ headers: options.headers
1594
+ })
1595
+ .then((mergeRes) => {
1596
+ if (mergeRes.rCode !== 0) return Promise.reject(mergeRes);
1597
+ onProgress({ percent: 100 });
1598
+ this.normalizeChunkDoc(mergeRes.results, file.name);
1599
+ this.finishChunk(uid, mergeRes);
1600
+ // 刷新列表:让刚入库的文件以与其他行一致的结构展示(时间/大小/上传人等)
1601
+ this.requestFiles && this.getFiles();
1602
+ });
1603
+ });
1604
+ })
1605
+ .catch((e) => {
1606
+ // 暂停导致的中止:保持 pending 等待"继续";其余才算失败
1607
+ if (state.paused || isAbort(e)) return;
1608
+ this.failChunk(uid, e);
1609
+ });
1610
+ },
1611
+ // 暂停:置暂停标记并中止在传分片;promise 维持 pending,文件保持"上传中"
1612
+ pauseUpload(file) {
1613
+ const state = file && this._chunkState[file.uid];
1614
+ if (!state) return;
1615
+ state.paused = true;
1616
+ if (state.controller) {
1617
+ try {
1618
+ state.controller.abort();
1619
+ } catch (e) {
1620
+ /* ignore */
1621
+ }
1622
+ }
1623
+ this.$set(file, 'chunkPaused', true);
1624
+ this.$emit('chunk-pause', file);
1625
+ },
1626
+ // 继续:清暂停标记,重新进入流程(checkFile 会返回已传分片,自动续传)
1627
+ resumeUpload(file) {
1628
+ const state = file && this._chunkState[file.uid];
1629
+ if (!state || !state.paused) return; // 仅暂停态可续传,防止重复触发并发流程
1630
+ state.paused = false;
1631
+ this.$set(file, 'chunkPaused', false);
1632
+ this.$emit('chunk-resume', file);
1633
+ this.runChunkFlow(file.uid);
1634
+ },
1635
+ // 该文件是否处于分片上传运行态(用于操作列按钮显隐)
1636
+ isChunkUploading(file) {
1637
+ return !!(file && this._chunkState && this._chunkState[file.uid]);
1638
+ },
1639
+ // 归一化合并响应:dochub Document 用 docTagId 标识、且无 suffix 字段,
1640
+ // 而组件下载读 adjunctId、预览读 suffix/originalName,这里统一映射补齐,避免预览取 suffix 报错。
1641
+ normalizeChunkDoc(results, fileName) {
1642
+ if (!results || typeof results !== 'object') return results;
1643
+ const id =
1644
+ results.adjunctId ||
1645
+ results.docTagId ||
1646
+ results.documentId ||
1647
+ results.id;
1648
+ if (id) {
1649
+ if (!results.adjunctId) results.adjunctId = id;
1650
+ if (!results.documentId) results.documentId = id;
1651
+ }
1652
+ // 文件名:优先用上传时的原始名,其次后端 documentFile.docName
1653
+ const name =
1654
+ fileName ||
1655
+ (results.documentFile && results.documentFile.docName) ||
1656
+ results.originalName ||
1657
+ results.name ||
1658
+ '';
1659
+ if (!results.originalName && name) results.originalName = name;
1660
+ // 后缀:预览按后缀判断类型,缺省从文件名推导;始终保证为字符串
1661
+ if (results.suffix === undefined || results.suffix === null) {
1662
+ results.suffix =
1663
+ name && name.lastIndexOf('.') > -1
1664
+ ? name.slice(name.lastIndexOf('.') + 1)
1665
+ : '';
1666
+ }
1667
+ // 展示字段:dochub 的元数据在 documentFile 下且字段名不同,映射成 showInfo 默认读取的扁平字段
1668
+ const df = results.documentFile;
1669
+ if (df && typeof df === 'object') {
1670
+ if (!results.uploadTime) {
1671
+ results.uploadTime = df.createTime || df.lastModifyTime || '';
1672
+ }
1673
+ if (!results.userName) {
1674
+ results.userName = df.createUserName || df.lastModifyUserName || '';
1675
+ }
1676
+ if (!results.fileSize && (df.docSize || df.docSize === 0)) {
1677
+ results.fileSize = this.formatFileSize(df.docSize);
1678
+ }
1679
+ }
1680
+ return results;
1681
+ },
1682
+ // 字节数格式化为可读大小(与列表展示风格一致:1 位小数、无空格)
1683
+ formatFileSize(bytes) {
1684
+ const n = Number(bytes);
1685
+ if (!n || n <= 0) return '0B';
1686
+ if (n >= 1073741824) return (n / 1073741824).toFixed(1) + 'GB';
1687
+ if (n >= 1048576) return (n / 1048576).toFixed(1) + 'MB';
1688
+ if (n >= 1024) return (n / 1024).toFixed(1) + 'KB';
1689
+ return n + 'B';
1690
+ },
1691
+ // 完成:兑现 promise 并清理运行态
1692
+ finishChunk(uid, response) {
1693
+ const state = this._chunkState[uid];
1694
+ if (!state) return;
1695
+ state.resolve && state.resolve(response);
1696
+ delete this._chunkState[uid];
1697
+ },
1698
+ // 失败:拒绝 promise 并清理运行态
1699
+ failChunk(uid, err) {
1700
+ const state = this._chunkState[uid];
1701
+ if (!state) return;
1702
+ state.reject && state.reject(err);
1703
+ delete this._chunkState[uid];
1704
+ },
1705
+ // 移除文件时清理分片运行态(中止在传分片,孤立 pending promise 不再触发回调)
1706
+ cleanupChunk(file) {
1707
+ const uid = file && file.uid;
1708
+ const state = uid && this._chunkState && this._chunkState[uid];
1709
+ if (!state) return;
1710
+ state.paused = true;
1711
+ state.resolve = null;
1712
+ state.reject = null;
1713
+ if (state.controller) {
1714
+ try {
1715
+ state.controller.abort();
1716
+ } catch (e) {
1717
+ /* ignore */
1718
+ }
1719
+ }
1720
+ delete this._chunkState[uid];
1721
+ },
1722
+ // 上传单个分片(multipart/form-data);signal 用于暂停时中止
1723
+ uploadOneChunk(file, index, totalChunks, fileMd5, headers, signal) {
1724
+ const start = index * this._chunkSize;
1725
+ const end = Math.min(start + this._chunkSize, file.size);
1726
+ const formData = new FormData();
1727
+ formData.append('file', file.slice(start, end), file.name);
1728
+ formData.append('fileMd5', fileMd5);
1729
+ formData.append('fileName', file.name);
1730
+ formData.append('fileSize', file.size);
1731
+ formData.append('chunkIndex', index);
1732
+ formData.append('totalChunks', totalChunks);
1733
+ formData.append('bucketCode', this.code);
1734
+ formData.append('businessId', this.ownId);
1735
+ Object.keys(this.param || {}).forEach((key) => {
1736
+ formData.append(key, this.param[key]);
1737
+ });
1738
+ return util
1739
+ .ajax({
1740
+ method: 'post',
1741
+ url: this.chunkUrls.upload,
1742
+ data: formData,
1743
+ format: false,
1744
+ headers: headers,
1745
+ signal: signal
1746
+ })
1747
+ .then((res) => {
1748
+ if (res.rCode !== 0) return Promise.reject(res);
1749
+ return res;
1750
+ });
1751
+ },
1752
+ // 分块读取文件并增量计算 MD5(避免大文件一次性读入内存)
1753
+ computeFileMd5(file, onProgress) {
1754
+ return new Promise((resolve, reject) => {
1755
+ const size = this._chunkSize;
1756
+ const chunks = Math.max(1, Math.ceil(file.size / size));
1757
+ const spark = new SparkMD5.ArrayBuffer();
1758
+ const reader = new FileReader();
1759
+ let current = 0;
1760
+ const loadNext = () => {
1761
+ const start = current * size;
1762
+ reader.readAsArrayBuffer(
1763
+ file.slice(start, Math.min(start + size, file.size))
1764
+ );
1765
+ };
1766
+ reader.onload = (e) => {
1767
+ spark.append(e.target.result);
1768
+ current++;
1769
+ // 校验阶段占进度 0~5%,剩余留给上传与合并
1770
+ onProgress &&
1771
+ onProgress({ percent: Math.round((current / chunks) * 5) });
1772
+ if (current < chunks) {
1773
+ loadNext();
1774
+ } else {
1775
+ // 取标准 32 位 MD5 的中间 16 位,与后端 hutool digestHex16(即 doc_md5)口径一致,
1776
+ // 否则秒传按 md5 比对永远不命中
1777
+ resolve(spark.end().substring(8, 24));
1778
+ }
1779
+ };
1780
+ reader.onerror = () => reject({ msg: '文件读取失败,无法计算MD5' });
1781
+ loadNext();
1782
+ });
1783
+ },
1784
+ // 限制并发的分片任务队列
1785
+ runChunkQueue(indexes, concurrency, worker) {
1786
+ return new Promise((resolve, reject) => {
1787
+ if (!indexes.length) return resolve();
1788
+ let cursor = 0;
1789
+ let active = 0;
1790
+ let failed = false;
1791
+ const schedule = () => {
1792
+ if (failed) return;
1793
+ if (cursor >= indexes.length && active === 0) return resolve();
1794
+ while (active < concurrency && cursor < indexes.length) {
1795
+ const index = indexes[cursor++];
1796
+ active++;
1797
+ worker(index)
1798
+ .then(() => {
1799
+ active--;
1800
+ schedule();
1801
+ })
1802
+ .catch((err) => {
1803
+ failed = true;
1804
+ reject(err);
1805
+ });
1806
+ }
1807
+ };
1808
+ schedule();
1809
+ });
1810
+ }
1811
+ }
1812
+ };
1813
+ </script>