hcui-package 1.9.43 → 1.9.45
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.
- package/dist/commonjs/Upload.js +384 -0
- package/package.json +1 -1
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将文件对象转换为 Base64 编码的字符串
|
|
3
|
+
*
|
|
4
|
+
* @param {File} file - 要转换的文件对象
|
|
5
|
+
* @return {Promise} 当转换完成时,resolve 包含文件内容作为 Base64 编码字符串,否则 reject 包含错误对象
|
|
6
|
+
*/
|
|
7
|
+
export function fileToBase64(file) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const reader = new FileReader()
|
|
10
|
+
reader.onload = () => resolve(reader.result)
|
|
11
|
+
reader.onerror = error => reject(error)
|
|
12
|
+
reader.readAsDataURL(file)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 将 dataURL 转换为文件对象
|
|
18
|
+
*
|
|
19
|
+
* @param {string} dataurl - 要转换的 dataURL 字符串
|
|
20
|
+
* @param {string} filename - 文件的名称
|
|
21
|
+
* @return {File} 一个 File 对象,包含了从 dataURL 解码后的数据
|
|
22
|
+
*/
|
|
23
|
+
export function dataURLtoFile(dataurl, filename) {
|
|
24
|
+
var arr = dataurl.split(','); var mime = arr[0].match(/:(.*?);/)[1]
|
|
25
|
+
var bstr = atob(arr[1]); var n = bstr.length; var u8arr = new Uint8Array(n)
|
|
26
|
+
while (n--) {
|
|
27
|
+
u8arr[n] = bstr.charCodeAt(n)
|
|
28
|
+
}
|
|
29
|
+
var blob = new Blob([u8arr], { type: mime })
|
|
30
|
+
return new File([blob], filename, { type: mime })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 异步压缩文件。
|
|
35
|
+
* @param {File} file - 需要压缩的文件对象。
|
|
36
|
+
* @param {number} size - 压缩后文件大小的上限,单位为KB。
|
|
37
|
+
* @returns {Promise<File>} 返回一个Promise,解析为压缩后的文件对象,如果文件不需要压缩或传入的文件为空,则解析为null。
|
|
38
|
+
*/
|
|
39
|
+
export const compressFile = async(file, size) => {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
if (!file) resolve(null)
|
|
42
|
+
if ((file.size / 1024) <= size) resolve(file)
|
|
43
|
+
|
|
44
|
+
const img = new Image()
|
|
45
|
+
img.src = URL.createObjectURL(file)
|
|
46
|
+
img.onload = function() {
|
|
47
|
+
let quality = 0.8
|
|
48
|
+
let dataURL
|
|
49
|
+
const canvas = document.createElement('canvas')
|
|
50
|
+
canvas.width = this.naturalWidth
|
|
51
|
+
canvas.height = this.naturalHeight
|
|
52
|
+
const ctx = canvas.getContext('2d')
|
|
53
|
+
// 将图片画到canvas上
|
|
54
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height)
|
|
55
|
+
// 尝试不同的质量参数,直到达到目标大小
|
|
56
|
+
do {
|
|
57
|
+
dataURL = canvas.toDataURL('image/jpeg', quality)
|
|
58
|
+
const sizeInBytes = atob(dataURL.split(',')[1]).length
|
|
59
|
+
const sizeInKB = sizeInBytes / 1024
|
|
60
|
+
if (sizeInKB > size) {
|
|
61
|
+
quality -= 0.1
|
|
62
|
+
} else {
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
} while (quality > 0)
|
|
66
|
+
|
|
67
|
+
const newFile = dataURLtoFile(dataURL, file.name)
|
|
68
|
+
// 清除内存
|
|
69
|
+
URL.revokeObjectURL(img.src)
|
|
70
|
+
resolve(newFile)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
const parseHTML = (html) => {
|
|
75
|
+
const template = document.createElement('template')
|
|
76
|
+
template.innerHTML = html
|
|
77
|
+
return template.content
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const isMobile = () => {
|
|
81
|
+
return ('ontouchstart' in document.documentElement)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const defaultParams = {
|
|
85
|
+
// limit: 3,
|
|
86
|
+
addWidth: 120,
|
|
87
|
+
addHeight: 120,
|
|
88
|
+
maxSize: 5,
|
|
89
|
+
fileList: [],
|
|
90
|
+
needSlice: false, // 是否需要图片裁剪
|
|
91
|
+
ratio: 16 / 9, // 宽高比例
|
|
92
|
+
times: 5, // 放大倍数
|
|
93
|
+
compressSize: null, // 压缩图片大小
|
|
94
|
+
defaultWidth: 375, // 默认宽度
|
|
95
|
+
overRight: 0,
|
|
96
|
+
overBottom: 0,
|
|
97
|
+
themErrorColor: 'var(--color-danger)'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
class webUpload extends HTMLElement {
|
|
101
|
+
constructor() {
|
|
102
|
+
super() // 调用父类的构造函数
|
|
103
|
+
|
|
104
|
+
const _this = this
|
|
105
|
+
this.inputDom = document.createElement('input')
|
|
106
|
+
this.inputDom.type = 'file'
|
|
107
|
+
this.inputDom.multiple = false // 默认不可多选
|
|
108
|
+
Object.assign(this, defaultParams) // 合并默认参数
|
|
109
|
+
this.inputDom.onchange = function() { return _this.onChange(this.files) }
|
|
110
|
+
this.attachShadow({ mode: 'open' })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static get observedAttributes() {
|
|
114
|
+
return ['params']
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 处理配置
|
|
118
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
119
|
+
if (oldValue !== newValue) {
|
|
120
|
+
const obj = JSON.parse(newValue)
|
|
121
|
+
Object.assign(this, obj)
|
|
122
|
+
if (obj.accept) this.inputDom.accept = obj.accept
|
|
123
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
124
|
+
if (obj.hasOwnProperty('multiple')) this.inputDom.multiple = obj.multiple
|
|
125
|
+
}
|
|
126
|
+
// 插入初始模板
|
|
127
|
+
this.shadowRoot.innerHTML = `
|
|
128
|
+
<style>
|
|
129
|
+
* {margin:0;padding:0;box-sizing: border-box;}
|
|
130
|
+
.add { display: inline-block; position:relative; width: ${this.addWidth}px; height: ${this.addHeight}px; border: 1px dashed #ccc; border-radius: 5px; background-color: #eee; color: #333; margin-bottom: 20px;}
|
|
131
|
+
.one { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 30px; height:2px; background-color: #ccc;}
|
|
132
|
+
.two { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 2px; height:30px; background-color: #ccc;}
|
|
133
|
+
.error-words { color: ${this.themErrorColor}; position: absolute; bottom: 6px; font-size: 12px; }
|
|
134
|
+
.hideContent { width:0px;height:0px;overflow: hidden;position: relative;}
|
|
135
|
+
#canvas { position: absolute;transform: scale(1);background-color: #fff;left: -100000px; }
|
|
136
|
+
#copyImg { display: block; max-width:100%; max-heigh:100%; object-fit:cover; }
|
|
137
|
+
.modal-wraper {display: flex;justify-content: center;align-items: center;position: fixed;left: 0;right: 0;top: 0;bottom: 0;background-color: rgba(0,0,0,.2);z-index:99999;overflow: auto;}
|
|
138
|
+
.layout { max-width: 90%; max-height: 80%; overflow: auto; } .container { position: relative; }
|
|
139
|
+
.aim-tool { position: absolute; border: 2px dashed #1890ff;cursor: all-scroll;background:rgba(0,0,0,0.2) }
|
|
140
|
+
.entry { text-align: right;position: fixed;width:100%;bottom: 10px;right:10px; }
|
|
141
|
+
.width-input { border: none;line-height: 32px;padding: 0 10px;font-size: 12px;border-radius: 4px;outline-style: none; }
|
|
142
|
+
.width-input:focus-visible { outline: none; }
|
|
143
|
+
.btn { padding: 8px;border-radius: 4px;background: #1890ff;color: #fff;cursor: pointer;margin-left: 5px; font-size:12px;}
|
|
144
|
+
</style>
|
|
145
|
+
<div class="hideContent">
|
|
146
|
+
<canvas id="canvas">你的浏览器不支持 canvas,请升级你的浏览器</canvas>
|
|
147
|
+
</div>
|
|
148
|
+
`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
triggerUpload() {
|
|
152
|
+
this.inputDom.click()
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async onChange(files) {
|
|
156
|
+
this.removeError()
|
|
157
|
+
// 校验图片上传数量
|
|
158
|
+
// const remainNum = this.limit - this.fileList.length - files.length
|
|
159
|
+
// if (remainNum < 0) return this.showError('超出文件上传数量')
|
|
160
|
+
// 校验图片上传大小
|
|
161
|
+
let sizeLimit = false
|
|
162
|
+
for (let i = 0; i < files.length; i++) {
|
|
163
|
+
const file = files[i]
|
|
164
|
+
if (file.size / 1024 / 1024 > this.maxSize) {
|
|
165
|
+
sizeLimit = true
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (sizeLimit) return this.showError(`上传图片大小不能超过 ${this.maxSize}MB!`)
|
|
170
|
+
|
|
171
|
+
// 压缩图片
|
|
172
|
+
if (this.compressSize) {
|
|
173
|
+
const res = await compressFile(files[0], this.compressSize)
|
|
174
|
+
if (res) files = [res]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 是否裁剪图片
|
|
178
|
+
if (this.needSlice) {
|
|
179
|
+
this.initSliceArea(files)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.fileList = files // 更新fileList
|
|
184
|
+
// 并抛出去 相当于vue.emit('change', congif) 由外面的逻辑进行上传
|
|
185
|
+
this.emitChange()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
initSliceArea(files) {
|
|
189
|
+
const _this = this
|
|
190
|
+
if (!files.length) return
|
|
191
|
+
const file = files[0]
|
|
192
|
+
const fr = new FileReader()
|
|
193
|
+
fr.readAsDataURL(file)
|
|
194
|
+
fr.onload = function(e) {
|
|
195
|
+
_this.shadowRoot.appendChild(parseHTML(`
|
|
196
|
+
<div class="modal-wraper">
|
|
197
|
+
<div class="layout">
|
|
198
|
+
<div class="container">
|
|
199
|
+
<div draggable="false" class="aim-tool" style="width:${_this.defaultWidth}px;height:${_this.defaultWidth / _this.ratio}px;top: 5px;left:5px;"></div>
|
|
200
|
+
<img draggable="false" id="copyImg" src="${this.result}" crossorigin="anonymous" title="">
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<div class="entry">
|
|
204
|
+
<input class="width-input" placeholder="设置自定义截取宽度" />
|
|
205
|
+
<span class="btn cancel-slice">取消并上传</span>
|
|
206
|
+
<span class="btn save-slice">截取并上传</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>`))
|
|
209
|
+
const sliceContent = _this.shadowRoot.querySelector('.modal-wraper')
|
|
210
|
+
|
|
211
|
+
_this.shadowRoot.querySelector('#copyImg').onload = function(e) {
|
|
212
|
+
// 并且将图片画到隐藏的canvas上
|
|
213
|
+
_this.drawImgToCanvas()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 绑定拖拽事件
|
|
217
|
+
if (isMobile()) {
|
|
218
|
+
_this.shadowRoot.querySelector('.aim-tool').addEventListener('touchstart', function(e) {
|
|
219
|
+
e.preventDefault()
|
|
220
|
+
_this.touchX = e.changedTouches[0].pageX - this.offsetLeft
|
|
221
|
+
_this.touchY = e.changedTouches[0].pageY - this.offsetTop
|
|
222
|
+
/** 被点击元素本身的宽和高 */
|
|
223
|
+
const domWidth = this.offsetWidth
|
|
224
|
+
const domHeight = this.offsetHeight
|
|
225
|
+
/** 被点击元素的边界值 */
|
|
226
|
+
_this.overRight = _this.shadowRoot.querySelector('#copyImg').offsetWidth - domWidth
|
|
227
|
+
_this.overBottom = _this.shadowRoot.querySelector('#copyImg').offsetHeight - domHeight
|
|
228
|
+
|
|
229
|
+
document.addEventListener('touchmove', function(e) {
|
|
230
|
+
let left = e.changedTouches[0].pageX - _this.touchX
|
|
231
|
+
let top = e.changedTouches[0].pageY - _this.touchY
|
|
232
|
+
if (left < 0) {
|
|
233
|
+
left = 0
|
|
234
|
+
} else if (left > _this.overRight) {
|
|
235
|
+
left = _this.overRight
|
|
236
|
+
}
|
|
237
|
+
if (top < 0) {
|
|
238
|
+
top = 0
|
|
239
|
+
} else if (top > _this.overBottom) {
|
|
240
|
+
top = _this.overBottom
|
|
241
|
+
}
|
|
242
|
+
_this.shadowRoot.querySelector('.aim-tool').style.left = left + 'px'
|
|
243
|
+
_this.shadowRoot.querySelector('.aim-tool').style.top = top + 'px'
|
|
244
|
+
}, false)
|
|
245
|
+
}, false)
|
|
246
|
+
} else {
|
|
247
|
+
_this.shadowRoot.querySelector('.aim-tool').addEventListener('mousedown', function(e) {
|
|
248
|
+
/** 被点击元素本身的宽和高 */
|
|
249
|
+
const domWidth = this.offsetWidth
|
|
250
|
+
const domHeight = this.offsetHeight
|
|
251
|
+
/** 被点击元素的边界值 */
|
|
252
|
+
_this.overRight = _this.shadowRoot.querySelector('#copyImg').offsetWidth - domWidth
|
|
253
|
+
_this.overBottom = _this.shadowRoot.querySelector('#copyImg').offsetHeight - domHeight
|
|
254
|
+
document.addEventListener('mousemove', _this.handlerMove, false)
|
|
255
|
+
}, false)
|
|
256
|
+
document.addEventListener('mouseup', function() {
|
|
257
|
+
document.removeEventListener('mousemove', _this.handlerMove, false)
|
|
258
|
+
}, false)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 改变截图框区域
|
|
262
|
+
_this.shadowRoot.querySelector('.width-input').addEventListener('keyup', function(e) {
|
|
263
|
+
const changeWidth = parseInt(e.target.value)
|
|
264
|
+
if (changeWidth) {
|
|
265
|
+
_this.shadowRoot.querySelector('.aim-tool').style.width = changeWidth + 'px'
|
|
266
|
+
_this.shadowRoot.querySelector('.aim-tool').style.height = changeWidth / _this.ratio + 'px'
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
// 取消并上传
|
|
270
|
+
_this.shadowRoot.querySelector('.cancel-slice').addEventListener('click', function(e) {
|
|
271
|
+
_this.fileList = files
|
|
272
|
+
_this.emitChange()
|
|
273
|
+
sliceContent.remove()
|
|
274
|
+
})
|
|
275
|
+
// 截取并上传
|
|
276
|
+
_this.shadowRoot.querySelector('.save-slice').addEventListener('click', function(e) {
|
|
277
|
+
_this.startSlice(sliceContent, file)
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
handlerTouchmove(e) {
|
|
283
|
+
const _this = this
|
|
284
|
+
const toolDom = _this.shadowRoot.querySelector('.aim-tool')
|
|
285
|
+
let left = e.changedTouches[0].pageX - toolDom.touchX
|
|
286
|
+
let top = e.changedTouches[0].pageY - toolDom.touchY
|
|
287
|
+
if (left < 0) {
|
|
288
|
+
left = 0
|
|
289
|
+
} else if (left > toolDom.overRight) {
|
|
290
|
+
left = toolDom.overRight
|
|
291
|
+
}
|
|
292
|
+
if (top < 0) {
|
|
293
|
+
top = 0
|
|
294
|
+
} else if (top > toolDom.overBottom) {
|
|
295
|
+
top = toolDom.overBottom
|
|
296
|
+
}
|
|
297
|
+
toolDom.style.left = left + 'px'
|
|
298
|
+
toolDom.style.top = top + 'px'
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
handlerMove(e) {
|
|
302
|
+
const _this = e.target // 获取到当前组件的对象 此时的this指向的是document
|
|
303
|
+
const toolDom = _this.shadowRoot.querySelector('.aim-tool')
|
|
304
|
+
const currentLeft = parseInt(toolDom.style.left)
|
|
305
|
+
const currentTop = parseInt(toolDom.style.top)
|
|
306
|
+
const thenLeft = currentLeft + e.movementX
|
|
307
|
+
const thenTop = currentTop + e.movementY
|
|
308
|
+
if (thenLeft < 0 || thenLeft > _this.overRight) return
|
|
309
|
+
if (thenTop < 0 || thenTop > _this.overBottom) return
|
|
310
|
+
toolDom.style.left = thenLeft + 'px'
|
|
311
|
+
toolDom.style.top = thenTop + 'px'
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
drawImgToCanvas() {
|
|
315
|
+
const img = this.shadowRoot.querySelector('#copyImg')
|
|
316
|
+
img.crossOrigin = 'anonymous'
|
|
317
|
+
const canvas = this.shadowRoot.querySelector('#canvas')
|
|
318
|
+
canvas.width = img.width * this.times
|
|
319
|
+
canvas.height = img.height * this.times
|
|
320
|
+
const ctx = canvas.getContext('2d')
|
|
321
|
+
ctx.drawImage(
|
|
322
|
+
img, 0, 0, img.width * this.times, img.height * this.times
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
startSlice(sliceContent, file) {
|
|
327
|
+
// 最后的截图框坐标
|
|
328
|
+
const lastPosition = this.shadowRoot.querySelector('.aim-tool').getBoundingClientRect()
|
|
329
|
+
const imgPosition = this.shadowRoot.querySelector('#copyImg').getBoundingClientRect()
|
|
330
|
+
// 计算相对于于图片的 X Y
|
|
331
|
+
const _x = lastPosition.x - imgPosition.x
|
|
332
|
+
const _y = lastPosition.y - imgPosition.y
|
|
333
|
+
|
|
334
|
+
const initCanvas = this.shadowRoot.querySelector('#canvas')
|
|
335
|
+
const canvas = document.createElement('canvas')
|
|
336
|
+
canvas.width = lastPosition.width
|
|
337
|
+
canvas.height = lastPosition.height
|
|
338
|
+
const ctx = canvas.getContext('2d')
|
|
339
|
+
ctx.drawImage(
|
|
340
|
+
initCanvas, _x * this.times, _y * this.times, canvas.width * this.times, canvas.height * this.times, 0, 0, canvas.width, canvas.height
|
|
341
|
+
)
|
|
342
|
+
const url = canvas.toDataURL('image/png', 2.0)
|
|
343
|
+
this.fileList = [{ url, name: file.name, type: 'slice' }]
|
|
344
|
+
sliceContent.remove()
|
|
345
|
+
this.emitChange()
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
emitChange() {
|
|
349
|
+
this.dispatchEvent(new CustomEvent('change', {
|
|
350
|
+
bubbles: false, // 是否允许事件冒泡
|
|
351
|
+
composed: true, // 允许事件穿越 shadow dom
|
|
352
|
+
detail: this.fileList // 传递的数据
|
|
353
|
+
}))
|
|
354
|
+
this.clear()
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
showError(msg) {
|
|
358
|
+
this.shadowRoot.appendChild(parseHTML(`<div class="error-words">${msg}</div>`))
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
removeError() {
|
|
362
|
+
const errorDom = this.shadowRoot.querySelector('.error-words')
|
|
363
|
+
if (errorDom) errorDom.remove()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
clear() {
|
|
367
|
+
this.fileList = []
|
|
368
|
+
this.removeError()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 组件初始化
|
|
372
|
+
connectedCallback() {
|
|
373
|
+
if (this.children.length) {
|
|
374
|
+
this.shadowRoot.appendChild(this.children[0])
|
|
375
|
+
} else {
|
|
376
|
+
this.shadowRoot.appendChild(parseHTML('<span class="add"><i class="one"></i><i class="two"></i></span>'))
|
|
377
|
+
}
|
|
378
|
+
this.shadowRoot.querySelector('.add').addEventListener('click', () => { this.inputDom.click() })
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export default webUpload
|
|
383
|
+
|
|
384
|
+
|