UNPKG

song-ui-u

Version:

vue3 + js的PC前端组件库

618 lines (559 loc) 19.7 kB
'use strict'; var vue = require('vue'); var SparkMD5 = require('spark-md5'); require('../../button/index.cjs'); require('../../buttonGroup/index.cjs'); var index$2 = require('../../icon/index.cjs'); require('../../input/index.cjs'); require('../../textarea/index.cjs'); require('../../row/index.cjs'); require('../../col/index.cjs'); require('../../container/index.cjs'); require('../../checkbox/index.cjs'); require('../../switch/index.cjs'); require('../../form/index.cjs'); var index$1 = require('../../message/index.cjs'); require('../../mask/src/mask.cjs'); require('../../modal/index.cjs'); require('../../messageBox/index.cjs'); require('../../drawer/index.cjs'); require('../../badge/index.cjs'); require('../../space/index.cjs'); var index$3 = require('../../image/index.cjs'); require('../../radio/index.cjs'); require('../../divider/index.cjs'); require('../../chat/index.cjs'); var index = require('../../progress/index.cjs'); require('../index.cjs'); require('../../vTree/index.cjs'); require('../../table/index.cjs'); require('../../tabs/index.cjs'); require('../../menu/index.cjs'); require('../../steps/index.cjs'); require('../../header/index.cjs'); require('../../breadcrumble/index.cjs'); require('../../datePicker/index.cjs'); require('../../tooltip/index.cjs'); require('../../popover/index.cjs'); require('../../timePicker/index.cjs'); require('../../select/index.cjs'); require('../../collapse/index.cjs'); require('../../card/index.cjs'); require('../../timeline/index.cjs'); require('../../tag/index.cjs'); require('../../result/index.cjs'); require('../../sender/index.cjs'); var index$4 = require('../../../hook/use-namespace/index.cjs'); var icons = require('song-ui-pro-icon'); require('../../../hook/use-zindex/index.cjs'); var _pluginVue_exportHelper = require('../../../_virtual/_plugin-vue_export-helper.cjs'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var SparkMD5__namespace = /*#__PURE__*/_interopNamespaceDefault(SparkMD5); const _sfc_main = /*#__PURE__*/Object.assign({ name: "x-upload", }, { __name: 'index', props: /*#__PURE__*/vue.mergeModels({ // 切片大小 chunkSize: { type: Number, default: 1, }, // 文件验证地址 verifyUrl: { type: String, default: "", }, // 上传地址 uploadUrl: { type: String, default: "", }, // 合并地址 mergeUrl: { type: String, default: "", }, showProgress: { type: Boolean, default: true, }, formData: { type: Object, default: () => ({}), }, headers: { type: Object, default: () => ({}), }, // 手动上传 manualUpload: { type: Boolean, default: false, }, // type type: { type: String, default: "normal", // image, imageList }, imageSize: { type: String, default: "130", }, // 是否开启秒传 isVerify: { type: Boolean, default: true, }, }, { "imageList": { type: Array, default: () => [], }, "imageListModifiers": {}, "imgUrl": { type: String, default: "", }, "imgUrlModifiers": {}, "fileList": { type: Array, default: () => [], }, "fileListModifiers": {}, "fileUrl": { type: String, default: "", }, "fileUrlModifiers": {}, }), emits: /*#__PURE__*/vue.mergeModels([ "success", "error", "beforeUpload", "afterUpload", "changeFile", // 暂停 "pause", // 继续 "resume", // delete图片 "delete", ], ["update:imageList", "update:imgUrl", "update:fileList", "update:fileUrl"]), setup(__props, { expose: __expose, emit: __emit }) { const props = __props; const ns = index$4.useNamespace("upload"); const file = vue.ref(null); const chunkSize = vue.computed(() => { return props.chunkSize * 1024 * 1024; }); const imageList = vue.useModel(__props, "imageList"); const imgUrl = vue.useModel(__props, "imgUrl"); const fileList = vue.useModel(__props, "fileList"); const fileUrl = vue.useModel(__props, "fileUrl"); const uploadedChunks = vue.ref([]); // 已上传的分片索引 const isPaused = vue.ref(false); const uploadProgress = vue.ref(0); const fileHash = vue.ref(""); // 文件的哈希值(唯一标识) let currentChunkIndex = 0; const emits = __emit; // 验证文件是否已存在(秒传) const verifyUpload = async (fileHash, fileName) => { if (!props.isVerify) { return { exists: false }; } try { const response = await fetch(props.verifyUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ fileHash, fileName, }), }); const result = await response.json(); return result; } catch (error) { index$1.XMessage({ content: "验证失败", type: "warning", }); return { exists: false }; } }; // 处理文件选择 const handleFileChange = async (event, files) => { file.value = files || event.target.files[0]; uploadedChunks.value = []; currentChunkIndex = 0; uploadProgress.value = 0; // 计算文件哈希值 fileHash.value = await calculateFileHash(file.value); // 触发 changeFile 事件 emits("changeFile", { file: file.value, fileHash: fileHash.value }); // 验证文件是否已存在 const { exists } = await verifyUpload(fileHash.value, file.value.name); if (exists) { index$1.XMessage({ content: "文件已存在", type: "success", }); uploadProgress.value = 100; return; } if (!props.manualUpload) { startUpload(); } }; // 计算文件哈希值 const calculateFileHash = (file) => { return new Promise((resolve, reject) => { const spark = new SparkMD5__namespace.ArrayBuffer(); const reader = new FileReader(); const fileChunkSize = 1 * 1024 * 1024; let currentChunk = 0; const chunks = Math.ceil(file.size / fileChunkSize); const loadNextChunk = () => { const start = currentChunk * fileChunkSize; const end = Math.min(start + fileChunkSize, file.size); reader.readAsArrayBuffer(file.slice(start, end)); }; reader.onload = (e) => { spark.append(e.target.result); currentChunk++; if (currentChunk < chunks) { loadNextChunk(); } else { resolve(spark.end()); } }; reader.onerror = () => reject("文件哈希值计算失败"); loadNextChunk(); }); }; // 开始上传 const startUpload = async () => { emits("beforeUpload"); if (!file.value) { index$1.XMessage({ content: "请选择文件", type: "warning", }); return; } isPaused.value = false; await uploadFileInChunks(); }; // 暂停上传 const pauseUpload = () => { emits("pause"); isPaused.value = true; }; // 继续上传 const resumeUpload = async () => { isPaused.value = false; emits("resume"); await uploadFileInChunks(); }; // 分片上传逻辑 const uploadFileInChunks = async () => { while (currentChunkIndex < Math.ceil(file.value.size / chunkSize.value)) { if (isPaused.value) { return; } if (uploadedChunks.value.includes(currentChunkIndex)) { currentChunkIndex++; continue; } const chunk = file.value.slice( currentChunkIndex * chunkSize.value, (currentChunkIndex + 1) * chunkSize.value ); const formData = new FormData(); formData.append("file", chunk); formData.append("fileName", file.value.name); formData.append("fileHash", fileHash.value); formData.append("chunkIndex", currentChunkIndex); formData.append( "totalChunks", Math.ceil(file.value.size / chunkSize.value) ); props.formData && Object.keys(props.formData).forEach((key) => { formData.append(key, props.formData[key]); }); try { await fetch(props.uploadUrl, { method: "POST", body: formData, headers: props.headers, }); uploadedChunks.value.push(currentChunkIndex); currentChunkIndex++; uploadProgress.value = ( (uploadedChunks.value.length / Math.ceil(file.value.size / chunkSize.value)) * 100 ).toFixed(2); } catch (error) { console.error("上传失败:", error); emits("error", error); // 添加错误事件 break; } } if ( uploadedChunks.value.length === Math.ceil(file.value.size / chunkSize.value) ) { index$1.XMessage({ content: "上传完成", type: "success", }); await mergeChunks(); emits("afterUpload"); // 添加上传完成事件 } }; // 合并分片 const mergeChunks = async () => { try { const response = await fetch(props.mergeUrl, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ fileName: file.value.name, fileHash: fileHash.value, totalChunks: Math.ceil(file.value.size / chunkSize.value), }), }); const result = await response.json(); emits("success", result); // 添加成功事件 } catch (error) { console.error("文件合并失败:", error); emits("error", error); // 添加错误事件 } }; // const preImageList = computed(() => { // return imageList.value.map((item) => { // return item.url; // }); // }); // handleDelImg const handleDelImg = (index) => { imageList.value.splice(index, 1); emits("delete", index); }; const upload = (files) => { return handleFileChange(null, files); }; __expose({ upload, }); const __returned__ = { props, ns, file, chunkSize, imageList, imgUrl, fileList, fileUrl, uploadedChunks, isPaused, uploadProgress, fileHash, get currentChunkIndex() { return currentChunkIndex }, set currentChunkIndex(v) { currentChunkIndex = v; }, emits, verifyUpload, handleFileChange, calculateFileHash, startUpload, pauseUpload, resumeUpload, uploadFileInChunks, mergeChunks, handleDelImg, upload, ref: vue.ref, computed: vue.computed, watch: vue.watch, get SparkMD5() { return SparkMD5__namespace }, get XProgress() { return index.XProgress }, get XMessage() { return index$1.XMessage }, get XIcon() { return index$2.XIcon }, get XImage() { return index$3.XImage }, get useNamespace() { return index$4.useNamespace }, get Upload() { return icons.Upload }, get Pause() { return icons.Pause }, get Plus() { return icons.Plus }, get X() { return icons.X } }; Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true }); return __returned__ } }); const _hoisted_1 = ["accept"]; const _hoisted_2 = /*#__PURE__*/vue.createElementVNode("svg", { t: "1744352467807", class: "icon", viewBox: "0 0 1024 1024", version: "1.1", xmlns: "http://www.w3.org/2000/svg", "p-id": "3031", width: "200", height: "200" }, [ /*#__PURE__*/vue.createElementVNode("path", { d: "M899.117 526.883a30.04 30.04 0 0 1-3.976 5.365 29.922 29.922 0 0 1-1.548 1.58l-0.2 0.186a30.087 30.087 0 0 1-5.472 4.057L165.858 955.094c-0.047 0.027-0.1 0.046-0.143 0.073q-1.155 0.659-2.37 1.214c-0.145 0.066-0.289 0.132-0.434 0.2q-1.084 0.476-2.212 0.864c-0.238 0.083-0.475 0.165-0.713 0.241-0.645 0.205-1.3 0.386-1.963 0.548-0.348 0.085-0.7 0.168-1.044 0.24-0.56 0.116-1.126 0.213-1.7 0.3q-1.416 0.21-2.833 0.283h-0.016a29.977 29.977 0 0 1-4.5-0.107c-0.243-0.024-0.483-0.057-0.723-0.086a30.513 30.513 0 0 1-2.109-0.331 31.377 31.377 0 0 1-1.021-0.222c-0.6-0.139-1.191-0.293-1.78-0.469-0.331-0.1-0.658-0.2-0.984-0.315q-0.921-0.309-1.82-0.676c-0.241-0.1-0.479-0.2-0.716-0.307q-1.047-0.461-2.055-1c-0.108-0.058-0.215-0.118-0.322-0.177a29.753 29.753 0 0 1-8.652-7.174l-0.071-0.089a29.873 29.873 0 0 1-1.59-2.124c-0.3-0.439-0.589-0.885-0.866-1.34-0.108-0.179-0.233-0.341-0.338-0.524a29.859 29.859 0 0 1-4-16V95.886a30 30 0 0 1 44.981-26.984l722.265 417a30 30 0 0 1 10.988 40.981zM180.89 877.13l632.327-365.2L180.89 146.856V877.13z", fill: "#ffffff", "p-id": "3032" }) ], -1 /* HOISTED */); const _hoisted_3 = ["onClick"]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { const _component_x_button = vue.resolveComponent("x-button"); return (vue.openBlock(), vue.createElementBlock("div", { class: vue.normalizeClass([$setup.ns.b(), $setup.ns.is('image', $props.type != 'normal')]) }, [ vue.createElementVNode("div", { class: vue.normalizeClass([$setup.ns.e('wrapper')]) }, [ ($props.type !== 'normal') ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: vue.normalizeClass($setup.ns.e('image')) }, [ ($setup.imgUrl && $props.type == 'image') ? (vue.openBlock(), vue.createBlock($setup["XImage"], { key: 0, src: $setup.imgUrl, preview: true }, null, 8 /* PROPS */, ["src"])) : vue.createCommentVNode("v-if", true), vue.createElementVNode("div", { class: vue.normalizeClass($setup.ns.e('upload-image')), style: vue.normalizeStyle({ height: $props.imageSize + 'px', width: $props.imageSize + 'px', }) }, [ vue.createVNode($setup["XIcon"], { size: "50" }, { default: vue.withCtx(() => [ vue.createVNode($setup["Plus"]) ]), _: 1 /* STABLE */ }) ], 6 /* CLASS, STYLE */) ], 2 /* CLASS */)) : vue.createCommentVNode("v-if", true), ($props.type == 'normal') ? (vue.openBlock(), vue.createBlock(_component_x_button, { key: 1, icon: $setup.Upload, type: "primary" }, { default: vue.withCtx(() => [ vue.createTextVNode("选择文件") ]), _: 1 /* STABLE */ }, 8 /* PROPS */, ["icon"])) : vue.createCommentVNode("v-if", true), vue.createElementVNode("input", { type: "file", accept: $props.type != 'normal' ? 'image/*' : '', onChange: $setup.handleFileChange, class: vue.normalizeClass($setup.ns.e('input')) }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, _hoisted_1), ($props.type == 'normal') ? (vue.openBlock(), vue.createElementBlock("span", { key: 2, class: vue.normalizeClass($setup.ns.e('warp-text')) }, vue.toDisplayString($setup.file?.name), 3 /* TEXT, CLASS */)) : vue.createCommentVNode("v-if", true) ], 2 /* CLASS */), vue.createElementVNode("div", { class: vue.normalizeClass($setup.ns.e('control')) }, [ ($setup.file && $props.type == 'normal') ? (vue.openBlock(), vue.createElementBlock("div", { key: 0, class: vue.normalizeClass($setup.ns.e('button')) }, [ ($props.manualUpload) ? (vue.openBlock(), vue.createBlock(_component_x_button, { key: 0, size: "small", circle: "", type: "success", onClick: $setup.startUpload, icon: $setup.Upload }, null, 8 /* PROPS */, ["icon"])) : vue.createCommentVNode("v-if", true), (!$setup.isPaused) ? (vue.openBlock(), vue.createBlock(_component_x_button, { key: 1, size: "small", circle: "", type: "warning", onClick: $setup.pauseUpload }, { default: vue.withCtx(() => [ vue.createVNode($setup["XIcon"], null, { default: vue.withCtx(() => [ vue.createVNode($setup["Pause"]) ]), _: 1 /* STABLE */ }) ]), _: 1 /* STABLE */ })) : (vue.openBlock(), vue.createBlock(_component_x_button, { key: 2, size: "small", onClick: $setup.resumeUpload, circle: "", type: "success" }, { default: vue.withCtx(() => [ vue.createVNode($setup["XIcon"], null, { default: vue.withCtx(() => [ _hoisted_2 ]), _: 1 /* STABLE */ }) ]), _: 1 /* STABLE */ })) ], 2 /* CLASS */)) : vue.createCommentVNode("v-if", true), ($setup.imageList.length > 0 && $props.type == 'imageList') ? (vue.openBlock(), vue.createElementBlock("div", { key: 1, class: vue.normalizeClass($setup.ns.e('imageList')) }, [ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($setup.imageList, (item, index) => { return (vue.openBlock(), vue.createElementBlock("div", { class: vue.normalizeClass($setup.ns.e('img')) }, [ vue.createElementVNode("div", { class: vue.normalizeClass($setup.ns.e('del-img')), onClick: vue.withModifiers($event => ($setup.handleDelImg(index)), ["stop"]) }, [ vue.createVNode($setup["XIcon"], null, { default: vue.withCtx(() => [ vue.createVNode($setup["X"]) ]), _: 1 /* STABLE */ }) ], 10 /* CLASS, PROPS */, _hoisted_3), (vue.openBlock(), vue.createBlock($setup["XImage"], { fit: "cover", key: item.name, src: item.url, preview: true, width: $setup.props.imageSize, height: $setup.props.imageSize }, null, 8 /* PROPS */, ["src", "width", "height"])) ], 2 /* CLASS */)) }), 256 /* UNKEYED_FRAGMENT */)) ], 2 /* CLASS */)) : vue.createCommentVNode("v-if", true), ($props.showProgress && $props.type == 'normal') ? (vue.openBlock(), vue.createElementBlock("div", { key: 2, class: vue.normalizeClass($setup.ns.e('progress')) }, [ ($props.showProgress) ? (vue.openBlock(), vue.createBlock($setup["XProgress"], { key: 0, value: $setup.uploadProgress, height: "10" }, null, 8 /* PROPS */, ["value"])) : vue.createCommentVNode("v-if", true) ], 2 /* CLASS */)) : vue.createCommentVNode("v-if", true) ], 2 /* CLASS */) ], 2 /* CLASS */)) } var upload = /*#__PURE__*/_pluginVue_exportHelper(_sfc_main, [['render',_sfc_render],['__file',"E:\\code\\my-code\\song-ui-ultra\\packages\\components\\upload\\src\\index.vue"]]); module.exports = upload; //# sourceMappingURL=index.vue.cjs.map