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.
@@ -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
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hcui-package",
3
- "version": "1.9.43",
3
+ "version": "1.9.45",
4
4
  "main": "./dist/hcui-package.umd.js",
5
5
  "module": "./dist/hcui-package.es.js",
6
6
  "style": "./dist/hcui-package.css",