song-ui-u
Version:
vue3 + js的PC前端组件库
618 lines (559 loc) • 19.7 kB
JavaScript
'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