release-v1.0 #1

Merged
panchichun merged 57 commits from release-v1.0 into main 2024-09-13 17:04:18 +08:00
5 changed files with 1829 additions and 17 deletions
Showing only changes of commit b500c53218 - Show all commits

View File

@ -1,33 +1,62 @@
<template>
<div>
<el-upload class="upload-demo" ref="upload" action="#"
:before-upload="beforeUpload"
:on-change="onChange"
:on-remove="handleRemove"
:multiple="isMultiple"
:on-exceed="handleExceed"
:accept="acceptType"
:limit="limit"
:file-list="fileList"
:auto-upload="true"
:http-request="uploadFile"
show-file-list>
<el-button slot="trigger" size="small" type="primary">选取文件</el-button>
<div slot="tip" class="el-upload__tip">只能上传{{acceptType}}文件</div>
</el-upload>
<div v-show="progressFlag" class="head-img">
<el-progress :text-inside="true" :stroke-width="14" :percentage="progressPercent" status="success"></el-progress>
<div class="container">
<div class="item">
<el-upload action="#"
ref="uploadFolder"
:on-change="onChange"
:on-remove="handleRemove"
:show-file-list="false"
:headers="headers"
:accept="accept"
:file-list="fileList"
:before-upload="beforeUploadsg"
:on-success="uploadSuccess"
:on-error="uploadError"
:on-exceed="exceed"
:auto-upload="true"
:http-request="uploadFile"
:multiple="true">
<el-button slot="trigger" id="myButton" size="small" type="primary" @click="uploadBtn.click()">选取文件夹</el-button>
</el-upload>
</div>
<div></div>
<div class="item">
<el-upload class="upload-demo" ref="upload" action="#"
:before-upload="beforeUpload"
:on-change="onChange"
:on-remove="handleRemove"
:multiple="isMultiple"
:on-exceed="handleExceed"
:accept="acceptType"
:limit="limit"
:file-list="fileList"
:auto-upload="true"
:http-request="uploadFile"
show-file-list>
<el-button id="myButton" slot="trigger" size="small" type="primary">选取文件</el-button>
<div slot="tip" class="el-upload__tip">只能上传{{acceptType}}文件</div>
</el-upload>
<div v-show="progressFlag" class="head-img">
<el-progress :text-inside="true" :stroke-width="14" :percentage="progressPercent" status="success"></el-progress>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import sgUploadTray from "./sgUploadTray"
import {
getToken
} from "@/utils/auth";
export default {
components: {
sgUploadTray,
},
props: {
uploadUrl: {
type: String,
@ -75,8 +104,22 @@ export default {
progressFlag: false, //
progressPercent: 0, //
partSize: 5 * 1024 * 1024,
headers: { kkToken: localStorage.token, }, //token(tokentoken)
accept:`.${["png", "jpg", "jpeg", "bmp", "gif"].join(",.")}`,//
actionURL: process.env.VUE_APP_BASE_API + "/common/initUpload",
dur: 100,
percent: 100,
uploadBtn: null,//
uploadList: [],
showUploadTray: false,
};
},
mounted(d) {
this.$nextTick(() => {
this.uploadBtn = this.$refs.uploadFolder.$children[0].$refs.input;
this.uploadBtn.webkitdirectory = true;//el-upload
})
},
watch: {
dataFile: {
handler(newValue, oldValue) {
@ -90,6 +133,98 @@ export default {
}
},
methods: {
showFakeLoading(file) {
file = this.uploadList.find(v => v.uid == file.uid);
clearInterval(file.interval);
file.percent = 0;
file.interval = setInterval(() => {
file.percent++;
file.percent >= 99 && this.hideFakeLoading(file);
}, this.dur);
},
hideFakeLoading(file, { type, tip, color } = {}) {
console.log('file======>');
console.log(file)
file = this.uploadList.find(v => v.uid == file.uid);
clearInterval(file.interval);
switch (type) {
case 'error':
file.percent = 0;
break;
case 'success':
default:
file.percent = 100;
}
type && (file.type = type);
tip && (file.tip = tip);
color && (file.color = color);
},
exceed(file, fileList) {
this.$message.error("上传文件数量太大,分散上传吧!");
},
stopUpload(d) {
this.$refs.uploadFolder.abort();
//console.log(``, d);
},
beforeUploadsg(file, id) {
console.log(this.uploadList)
this.uploadList = []
console.info("this.uploadList==", JSON.stringify(this.uploadList))
this.uploadList.unshift({
interval: false,
uid: file.uid,
percent: 0,//
name: file.name,
size: file.size,
type: file.type,
webkitRelativePath: file.webkitRelativePath,
tip: '',
color: '',
});
this.showUploadTray = true;
this.accept = ["png", "jpg", "jpeg", "bmp", "gif", "txt", "json", "pdf"]
// ________________________
let isFile = this.accept.includes(file.name.toLocaleLowerCase().split(".").pop());
const maxSize = 50; //
const isAllowSize = file.size / 1024 / 1024 <= maxSize;
isFile || this.$message.error("上传文件只能是" + this.accept+ "格式");
isAllowSize || this.$message.error("上传文件大小不能超过" + maxSize + "MB");
let allowUpload = isFile && isAllowSize;
return allowUpload; //false
},
uploadSuccess(response, file, fileList) {
if (response.data && response.data.key) {
//
this.$d.customer_downloadImportCustomerExcel({ key: response.data.key }, {
s: (d) => {
this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
this.$g.downloadFile(d, `${file.name}-上传失败原因`, '.xls');
this.$message.error(`${file.name}-上传失败,请查看失败原因`);
// this.initList();//
//console.log('', response, file, fileList);
}
});
} else if (response.success) {
//
this.hideFakeLoading(file, { type: 'success', tip: "上传成功", color: "green" });
this.$message.success(`${file.name}上传成功`);
// this.initList();//
//console.log('', response, file, fileList);
} else {
//
this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
this.$message.error(response.msg);
//console.log('', response, file, fileList);
}
},
//
uploadError(err, file, fileList) {
this.hideFakeLoading(file, { type: 'error', tip: "上传失败", color: "red" });
this.$message.error("上传失败");
//console.log('', err, file, fileList);
},
//
beforeUpload(file) {
/* const fileType = file.type.toLowerCase()
@ -325,3 +460,12 @@ export default {
}
};
</script>
<style>
#myButton {
width: 90px; /* 设置按钮长度为200像素 */
}
.container {
display: flex; /* 设定为flex布局 */
}
</style>

View File

@ -0,0 +1,133 @@
<template>
<div :class="$options.name" @click="show = !show" :placement="placement">
<div class="collapse-btns">
<div class="collapse-btn" v-if="show">
<i class="el-icon-caret-top"></i>
<div class="label">折叠{{ collapseLabel || expandLabel || `` }}</div>
</div>
<div class="collapse-btn" v-else>
<i class="el-icon-caret-bottom"></i>
<div class="label">展开{{ expandLabel || collapseLabel || `` }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "sgCollapseBtn",
components: {},
data() {
return {
show: false,
};
},
props: ["data", "value", "collapseLabel", "expandLabel", "placement"],
watch: {
value: {
handler(d) {
this.show = d;
},
deep: true,
immediate: true,
},
show(d) {
this.$emit("input", d);
},
},
created() {},
mounted() {},
computed: {},
methods: {},
};
</script>
<style lang="scss" scoped>
.sgCollapseBtn {
position: relative;
z-index: 1;
/*禁止选中文本*/
user-select: none;
width: 100%;
height: 30px;
line-height: 30px;
background-color: white;
cursor: pointer;
&[placement="bottom"] {
position: absolute;
top: revert;
bottom: 0;
left: 0;
right: 0;
}
.collapse-btns {
width: 100%;
$side: 20%; //
/*左右渐变遮罩(兼容IOS)*/
-webkit-mask-image: linear-gradient(
to right,
transparent,
white $side,
white calc(100% - #{$side}),
transparent
);
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
background-color: white;
&::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
background: linear-gradient(white, #e2f0ff);
opacity: 0;
transition: 1s ease !important;
z-index: -1;
}
.collapse-btn {
transition: 0.4s !important;
display: flex;
align-items: center;
transform: translateX(40%);
i {
transition: 0.4s !important;
color: #d3dce6;
}
.label {
transition: 0.2s !important;
margin-left: 25px;
color: #409eff;
opacity: 0;
}
}
}
&:active,
&:hover {
.collapse-btns {
&::after {
opacity: 1;
}
.collapse-btn {
transform: translateX(0%);
i {
color: #409eff;
}
.label {
margin-left: 5px;
color: #409eff;
opacity: 1;
// width: 100%;
}
}
}
}
}
</style>

View File

@ -0,0 +1,255 @@
<template>
<div :class="$options.name"></div>
</template>
<script>
export default {
name: "sgDragMove",
data() {
return {
offset: { x: 0, y: 0 }, //
style: { top: "0px", left: "0px" },
canDragDom: null, //
moveDom: null, //
};
},
props: [
"data", //()
/* data
[
...
{
canDragDom: elementDOM,//or
moveDom: elementDOM,//or
},
...
]
*/
"disabled", //
"mousedownNearSide", //
"mousemoveNearSide", //
"mouseupNearSide", //
"nearPadding", //mousedownNearSide||mousemoveNearSide||mouseupNearSide=truenumberarray5[5,5,5,5],[,,,]
"stopBoundary", //mousedownNearSide||mousemoveNearSide||mouseupNearSide=truenumberarray5[5,5,5,5],[,,,]
"cursor", //
/*cursor{
grab:'default',//
grabbing:'default',//
} */
],
watch: {
data: {
handler(newValue, oldValue) {
newValue ? this.__addDragsEvents(newValue) : this.__removeDragsEvents(oldValue);
},
deep: true,
immediate: true,
},
disabled: {
handler(newValue, oldValue) {
newValue ? this.__removeAllEvents() : this.__addAllEvents();
},
deep: true,
immediate: true,
},
style: {
handler(newValue, oldValue) {
if (this.moveDom && newValue && Object.keys(newValue).length) {
let d = newValue;
Object.keys(d).forEach((k) => (this.moveDom.style[k] = d[k]));
this.moveDom.style.right = "revert";
this.moveDom.style.bottom = "revert";
this.$emit(`getStyle`, d); //DOM
}
},
deep: true, //
immediate: true, //
},
},
destroyed() {
this.__removeAllEvents();
},
mounted() {
this.$parent.$el.style.setProperty(
"--sgDragMove-grab",
(this.cursor || {}).grab || "grab"
); //jscss
this.$parent.$el.style.setProperty(
"--sgDragMove-grabbing",
(this.cursor || {}).grabbing || "grabbing"
); //jscss
},
methods: {
__addAllEvents() {
this.__addDragsEvents(this.data);
},
__removeAllEvents() {
this.__removeWindowEvents();
this.__removeDragsEvents(this.data);
},
__addWindowEvents() {
this.__removeWindowEvents();
addEventListener("mousemove", this.mousemove_window);
addEventListener("mouseup", this.mouseup_window);
},
__removeWindowEvents() {
removeEventListener("mousemove", this.mousemove_window);
removeEventListener("mouseup", this.mouseup_window);
},
// DIV
__addDragsEvents(doms) {
(doms || []).forEach((dom) => {
this.__removeDraggedEvents(dom.canDragDom);
this.__addDraggedEvents(dom.canDragDom);
});
},
__removeDragsEvents(doms) {
(doms || []).forEach((dom) => {
this.__removeDraggedEvents(dom.canDragDom);
});
},
__addDraggedEvents(dom) {
dom.setAttribute("sgDragMove_grab", "ready");
dom.addEventListener("dragstart", this.dragstart);
dom.addEventListener("mousedown", this.mousedown);
},
__removeDraggedEvents(dom) {
dom.removeEventListener("dragstart", this.dragstart);
dom.removeEventListener("mousedown", this.mousedown);
},
dragstart(e) {
e.stopPropagation();
e.preventDefault();
return false;
},
mousedown(e) {
if (this.disabled) return this.mouseup_window(e);
if (e.button === 2) return this.mouseup_window(e); //
this.canDragDom = e.currentTarget;
this.moveDom = this.data.find((v) => v.canDragDom == this.canDragDom).moveDom;
this.canDragDom.setAttribute("sgDragMove_grab", "down");
this.moveDom.setAttribute("sgDragMove_move", "ready");
let or = this.moveDom.getBoundingClientRect();
this.offset = { x: e.clientX - or.x, y: e.clientY - or.y };
(this.mousedownNearSide || this.mousedownNearSide === "") && this.nearSide();
this.$emit("dragStart", this.getResult(e));
this.__addWindowEvents();
},
setOffset(d) {
this.offset = {
...this.offset,
...d,
};
},
mousemove_window(e) {
this.canDragDom.setAttribute("sgDragMove_grab", "down");
this.moveDom.setAttribute("sgDragMove_move", "ing");
let x = e.clientX - this.offset.x;
let y = e.clientY - this.offset.y;
this.style = { left: x + "px", top: y + "px" };
this.style["transition-property"] = "left,top";
this.style["transition-duration"] = "0s,0s";
this.$nextTick(() => {
(this.mousemoveNearSide || this.mousemoveNearSide === "") && this.nearSide();
this.$emit("dragging", this.getResult(e));
});
},
mouseup_window(e) {
this.$emit("dragEnd", this.getResult(e));
(this.mouseupNearSide || this.mouseupNearSide === "") && this.nearSide();
this.offset = null;
this.style = null;
this.canDragDom.setAttribute("sgDragMove_grab", "ready");
this.moveDom.setAttribute("sgDragMove_move", "end");
setTimeout(() => {
this.moveDom && this.moveDom.removeAttribute("sgDragMove_move");
}, 100);
this.canDragDom = null;
this.moveDom = null;
this.__removeWindowEvents();
},
//
nearSide() {
let arr = this.nearPadding ? JSON.parse(JSON.stringify(this.nearPadding)) : 0;
Array.isArray(arr) || (arr = [...Array(4)].map((v) => arr));
let [dis_top, dis_right, dis_bottom, dis_left] = arr;
arr = this.stopBoundary ? JSON.parse(JSON.stringify(this.stopBoundary)) : 0;
Array.isArray(arr) || (arr = [...Array(4)].map((v) => arr));
let [
stopBoundary_top,
stopBoundary_right,
stopBoundary_bottom,
stopBoundary_left,
] = arr;
let x = parseFloat(this.moveDom.style.left);
let y = parseFloat(this.moveDom.style.top);
let rect = this.moveDom.getBoundingClientRect();
let min_side_x = 0,
min_x = min_side_x + dis_left,
min_left = min_side_x + stopBoundary_left;
let min_side_y = 0,
min_y = min_side_y + dis_top,
min_top = min_side_y + stopBoundary_top;
x < min_x && (this.moveDom.style.left = `${min_left}px`);
y < min_y && (this.moveDom.style.top = `${min_top}px`);
let max_side_x = innerWidth - rect.width,
max_x = max_side_x - dis_right,
max_right = max_side_x - stopBoundary_right;
let max_side_y = innerHeight - rect.height,
max_y = max_side_y - dis_bottom,
max_bottom = max_side_y - stopBoundary_bottom;
x > max_x && (this.moveDom.style.left = `${max_right}px`);
y > max_y && (this.moveDom.style.top = `${max_bottom}px`);
},
getResult(e) {
return {
$event: e,
canDragDom: this.canDragDom,
moveDom: this.moveDom,
canDragDomRect: this.canDragDom ? this.canDragDom.getBoundingClientRect() : null,
moveDomRect: this.moveDom ? this.moveDom.getBoundingClientRect() : null,
};
},
},
};
</script>
<style lang="scss">
[sgDragMove_grab="ready"] {
cursor: var(--sgDragMove-grab); //cssjs
* {
cursor: var(--sgDragMove-grab); //cssjs
}
&:hover {
opacity: 1;
}
&:active {
opacity: 0.9;
}
}
[sgDragMove_grab="down"] {
cursor: var(--sgDragMove-grabbing); //cssjs
* {
cursor: var(--sgDragMove-grabbing); //cssjs
}
}
[sgDragMove_move="ready"] {
opacity: 1;
}
[sgDragMove_move="ing"] {
opacity: 0.9;
}
[sgDragMove_move="end"] {
transition: 0.1s;
}
</style>

View File

@ -0,0 +1,281 @@
<template>
<div :class="$options.name" :disabled="disabled" draggable="false">
<div :class="`resize-handle resize-${a}`" draggable="false" @mousedown.stop="clickResizeHandle(a)"
@dblclick="dblclickResizeHandle(a, $event)" v-for="(a, i) in sizeIndexs" :key="i"></div>
</div>
</template>
<script>
export default {
name: 'sgDragSize',
data() {
return {
tbHeight: 0,
dragSizeIndex: '',
originRect: {},
dblclickOriginRect: {},
sizeIndexs: [
'top',
'right',
'bottom',
'left',
'top-left',
'top-right',
'bottom-left',
'bottom-right',
],
}
},
props: [
"disabled",//
"taskbarHeight",//
"minWidth",//
"minHeight",//
"maxWidth",//
"maxHeight",//
],
watch: {
disabled: {
handler(newValue, oldValue) {
newValue && this.__removeWindowEvents();
}, deep: true, immediate: true,
},
taskbarHeight: {
handler(d) {
this.tbHeight = d || 0;
}, deep: true, immediate: true,
},
},
destroyed() {
this.__removeWindowEvents();
},
methods: {
view_innerHeight() {
return innerHeight - this.tbHeight;
},
clickResizeHandle(d) {
this.dragSizeIndex = d;
this.mousedown(d);
},
dblclickResizeHandle(d, $event) {
let rect = this.$el.getBoundingClientRect();
rect.width < innerWidth && rect.height < this.view_innerHeight() && (this.dblclickOriginRect = rect);
this.dblResize(d, rect, $event);
},
__addWindowEvents() {
this.__removeWindowEvents();
addEventListener('mousemove', this.mousemove_window);
addEventListener('mouseup', this.mouseup_window);
},
__removeWindowEvents() {
removeEventListener('mousemove', this.mousemove_window);
removeEventListener('mouseup', this.mouseup_window);
},
mousedown(e) {
this.originRect = this.$el.getBoundingClientRect();
this.originRect.bottomRightX = this.originRect.x + this.originRect.width;//.x
this.originRect.bottomRightY = this.originRect.y + this.originRect.height;//.y
this.$emit('dragStart', e);
this.__addWindowEvents();
},
mousemove_window(e) {
let { x, y } = e;
let minWidth = this.minWidth || 50, minHeight = this.minHeight || 50, maxWidth = this.maxWidth || innerWidth, maxHeight = this.maxHeight || innerHeight;
x < 0 && (x = 0), y < 0 && (y = 0), x > innerWidth && (x = innerWidth), y > this.view_innerHeight() && (y = this.view_innerHeight());
let style = {};
switch (this.dragSizeIndex) {
case 'top-left':
style.left = x;
style.top = y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.bottomRightX - minWidth);
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.bottomRightY - minHeight);
break;
case 'top':
style.left = this.originRect.x;
style.top = y;
style.width = this.originRect.width;
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.bottomRightY - minHeight);
break;
case 'top-right':
style.left = this.originRect.x;
style.top = y;
style.width = x - this.originRect.x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.x);
style.height = this.originRect.bottomRightY - y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.bottomRightY - minHeight);
break;
case 'left':
style.left = x;
style.top = this.originRect.y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.bottomRightX - minWidth);
style.height = this.originRect.height;
break;
case 'right':
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = x - this.originRect.x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.x);
style.height = this.originRect.height;
break;
case 'bottom-left':
style.left = x;
style.top = this.originRect.y;
style.width = this.originRect.bottomRightX - x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.bottomRightX - minWidth);
style.height = y - this.originRect.y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.y);
break;
case 'bottom':
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = this.originRect.width;
style.height = y - this.originRect.y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.y);
break;
case 'bottom-right':
style.left = this.originRect.x;
style.top = this.originRect.y;
style.width = x - this.originRect.x;
style.width <= minWidth && (style.width = minWidth, style.left = this.originRect.x);
style.height = y - this.originRect.y;
style.height <= minHeight && (style.height = minHeight, style.top = this.originRect.y);
break;
default:
}
style.width > maxWidth && (style.width = maxWidth);
style.height > maxHeight && (style.height = maxHeight);
Object.keys(style).forEach(k => style[k] = `${style[k]}px`);
style['transition-property'] = 'width,height';
style['transition-duration'] = '0,0';
this.$emit('dragging', { e, style });
},
dblResize(d, rect, e) {
let style = {};
switch (d) {
case 'top-left':
break;
case 'top':
case 'bottom':
style.left = this.originRect.x;
style.top = rect.height >= this.view_innerHeight() ? this.dblclickOriginRect.y : 0;
style.width = this.originRect.width;
style.height = rect.height >= this.view_innerHeight() ? this.dblclickOriginRect.height : this.view_innerHeight();
break;
case 'top-right':
break;
case 'left':
case 'right':
style.left = rect.width >= innerWidth ? this.dblclickOriginRect.x : 0;
style.top = this.originRect.y;
style.width = rect.width >= innerWidth ? this.dblclickOriginRect.width : innerWidth;
style.height = this.originRect.height;
break;
case 'bottom-left':
break;
case 'bottom-right':
break;
default:
}
Object.keys(style).forEach(k => style[k] = `${style[k]}px`);
style['transition-property'] = 'width,height';
style['transition-duration'] = '0.1s,0.1s';
this.$emit('dragging', { e, style });
},
mouseup_window(e) {
this.$emit('dragEnd', e);
this.__removeWindowEvents();
},
}
};
</script>
<style lang="scss">
.sgDragSize {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
.resize-handle {
position: absolute;
z-index: 100;
display: block;
pointer-events: auto;
}
&[disabled] {
.resize-handle {
pointer-events: none;
}
}
.resize-top {
cursor: n-resize;
top: -3px;
left: 0px;
height: 7px;
width: 100%;
}
.resize-right {
cursor: e-resize;
right: -3px;
top: 0px;
width: 7px;
height: 100%;
}
.resize-bottom {
cursor: s-resize;
bottom: -3px;
left: 0px;
height: 7px;
width: 100%;
}
.resize-left {
cursor: w-resize;
left: -3px;
top: 0px;
width: 7px;
height: 100%;
}
.resize-top-right {
cursor: ne-resize;
width: 16px;
height: 16px;
right: -8px;
top: -8px;
}
.resize-bottom-right {
cursor: se-resize;
width: 20px;
height: 20px;
right: -8px;
bottom: -8px;
background: url('/static/img/desktop/Windows7/sgDragSize/resize_corner.png') no-repeat;
}
.resize-bottom-left {
cursor: sw-resize;
width: 16px;
height: 16px;
left: -8px;
bottom: -8px;
}
.resize-top-left {
cursor: nw-resize;
width: 16px;
height: 16px;
left: -8px;
top: -8px;
}
}
</style>

View File

@ -0,0 +1,999 @@
<template>
<div :class="$options.name" :show="show" :size="size" :style="style">
<div class="upload-list-tray">
<!-- 托盘头部 -->
<div class="header" ref="header" @dblclick.stop.prevent="dblclickHeader">
<div class="left">
<div class="title">
<span class="upload-count" slot="reference">上传队列</span>
<div class="upload-info" v-if="liveSpeed && liveSpeed > 0">
<el-divider :direction="`vertical`" />
<div class="info-item live-speed">
<label>速度</label>
<span>{{ $g.getSize(liveSpeed) }}/s</span>
</div>
<div class="info-item taken-time" v-if="takenTime && takenTime > 0">
<label>已耗时</label>
<span>{{
$g.date.toHourMinuteSecondByMillisecond(takenTime * 1000, {
zh: true,
hideMilliSecond: true,
hideZero: true,
})
}}</span>
</div>
<div class="info-item remain-time" v-if="remainTime && remainTime > 0">
<label>剩余</label
><span>{{
$g.date.toHourMinuteSecondByMillisecond(remainTime * 1000, {
zh: true,
hideMilliSecond: true,
hideZero: true,
})
}}</span>
</div>
</div>
</div>
</div>
<div class="right" @mousedown.stop>
<!-- 控制文件的图标按钮 -->
<div class="file-btns" v-if="showDelSuccessIconBtn || showErrorIconBtn">
<div
class="icon-btn"
v-if="showDelSuccessIconBtn"
@click.stop="clearAllSuccessFile"
title="清除所有已经成功的上传记录"
>
<i class="el-icon-delete" style="color: #67c23a"></i>
</div>
<div
class="icon-btn"
v-if="showErrorIconBtn"
@click.stop="clearAllErrorFile"
title="清除所有失败的上传记录"
>
<i class="el-icon-delete-solid" style="color: #f56c6c"></i>
</div>
<div
class="icon-btn"
v-if="showErrorIconBtn"
@click.stop="uploadAllErrorFile"
title="重新上传所有失败的文件"
>
<i class="el-icon-upload2" style="color: #409eff"></i>
</div>
<template v-if="uploadList.length > maxShowUploadFileCount">
<div
class="icon-btn"
v-if="expandAllUploadList"
@click.stop="expandAllUploadList = false"
title="折叠只显示前10条上传记录"
>
<i class="el-icon-folder" style="color: #409eff"></i>
</div>
<el-tooltip
v-else
:content="`请谨慎展开列表,这将导致您的网页很卡!`"
:effect="`dark`"
:enterable="false"
:placement="`top-start`"
:popper-class="`sg-el-tooltip`"
:transition="`none`"
:disabled="uploadList.length < 200"
>
<div
class="icon-btn"
@click.stop="expandAllUploadList = true"
title="展开所有上传记录"
>
<i class="el-icon-folder-opened" style="color: #409eff"></i>
</div>
</el-tooltip>
</template>
</div>
<!-- 控制托盘的图标按钮 -->
<div class="tray-btns">
<div
class="icon-btn"
v-if="size !== 'lg' && showRightBottomBtn"
@click.stop="toRightBottomPosition"
title="回到原来的位置"
>
<i class="el-icon-bottom-right"></i>
</div>
<div
class="icon-btn"
v-if="size !== 'mn'"
@click.stop="size = 'mn'"
title="最小化"
>
<i class="el-icon-minus"></i>
</div>
<div
class="icon-btn"
v-if="size !== 'md'"
@click.stop="size = 'md'"
title="还原"
>
<i :class="size === 'lg' ? 'el-icon-copy-document' : 'el-icon-d-caret'"></i>
</div>
<div
class="icon-btn"
v-if="size !== 'lg'"
@click.stop="size = 'lg'"
title="全屏"
>
<i class="el-icon-full-screen"></i>
</div>
<div class="icon-btn" @click.stop="close">
<i class="el-icon-close"></i>
</div>
</div>
</div>
</div>
<!-- 上传中的文件列表 -->
<div class="upload-file-list">
<ul>
<li
v-for="(a, i) in expandAllUploadList ? uploadList : uploadList.slice(0, 10)"
:key="i"
:title="
a.size > 1024 * 1024 * 500
? `超大文件上传中,请耐心等待,切勿关闭或刷新浏览器!`
: ''
"
>
<div class="left">
<div class="icon-btns">
<el-button
title="移出上传队列"
:show="a.status === 'error'"
class="remove-icon-btn icon-btn"
type="danger"
icon="el-icon-delete-solid"
size="mini"
plain
circle
@click.stop="removeUploadFile(a)"
></el-button>
</div>
<!-- 动画加载旋转 -->
<div
class="fileLoading"
v-loading="a.percent < 100"
v-if="a.percent < 100 && a.status !== 'error'"
></div>
<!-- 上传成功icon -->
<div class="loadingSuccessIcon" v-if="a.percent === 100">
<i class="el-icon-success" style="color: #67c23a"></i>
</div>
<!-- 上传失败icon -->
<div class="loadingEorrorIcon" v-if="a.status === 'error'">
<i class="el-icon-error" style="color: #f56c6c"></i>
</div>
<span class="name" :title="a.filePath || a.name">
{{ a.filePath || a.name }}
<!-- {{ a.filePath && a.filePath.includes(`/`) ? `[路径:${a.filePath}]` : "" }} -->
</span>
<el-tag class="size" size="mini"
>{{ $g.getSize(a.size * (a.percent / 100)) }}/{{
$g.getSize(a.size)
}}</el-tag
>
<!-- <el-progress class="progress" :percentage="a.percent"></el-progress> -->
<el-progress
class="progress"
style="width: 100%"
type="line"
:percentage="parseInt(a.percent)"
:show-text="true"
:stroke-width="10"
:text-inside="false"
:color="'#409EFF'"
:define-back-color="'#eee'"
/>
</div>
<div class="right">
<span class="tip" :color="a.color">{{ a.tip }}</span>
<div class="icon-btns">
<el-button
:show="a.status === 'error'"
title="重新上传"
class="upload-icon-btn icon-btn"
type="primary"
icon="el-icon-upload2"
size="mini"
plain
circle
@click.stop="startUploadFile(a)"
></el-button>
</div>
</div>
</li>
</ul>
</div>
<!-- 折叠按钮 -->
<sgCollapseBtn
style="z-index: 1"
:collapseLabel="`上传记录`"
v-model="expandAllUploadList"
v-if="uploadList.length > maxShowUploadFileCount"
/>
<div class="footer">
<div class="text" v-html="popoverContent"></div>
<div class="progress" v-if="uploadList.length > 1 && totalPercentage < 100">
<label>总进度</label>
<el-progress
style="width: 100%"
type="line"
:percentage="parseInt(totalPercentage)"
:show-text="true"
:stroke-width="10"
:text-inside="false"
:color="'#409EFF'"
:define-back-color="'#eee'"
/>
</div>
</div>
</div>
<!-- 拖拽移动窗体 -->
<sgDragMove
:data="dragMoveDoms"
:cursor="{
grab: 'default',
grabbing: 'default',
}"
nearPadding="10"
:disabled="size === 'lg' && disabledDragMove"
@dragStart="$emit(`dragStart`, dragMoveDoms)"
@dragging="
showRightBottomBtn = true;
$emit(`dragging`, dragMoveDoms);
"
@dragEnd="$emit(`dragEnd`, dragMoveDoms)"
mousemoveNearSide
/>
<!-- 拖拽改变窗体尺寸 -->
<sgDragSize
v-if="resizeable_"
:disabled="size === 'lg'"
@dragStart="disabledDragMove = true"
@dragging="draggingSize"
@dragEnd="disabledDragMove = false"
:minWidth="minWidth"
:minHeight="minHeight"
/>
</div>
</template>
<script>
import sgCollapseBtn from "./sgCollapseBtn";
import sgDragMove from "./sgDragMove";
import sgDragSize from "./sgDragSize";
export default {
name: "sgUploadTray",
components: {
sgCollapseBtn,
sgDragMove,
sgDragSize,
},
data() {
return {
maxShowUploadFileCount: 10, //
expandAllUploadList: false, //
minWidth: 800,
minHeight: 40,
style_bk: null,
style: {},
resizeable_: true,
disabledDragMove: false, //
show: false,
showRightBottomBtn: false,
size: "md", //lgmdmn
uploadList: [],
dragMoveDoms: [
/* {
canDragDom: elementDOM,//
moveDom: elementDOM,//
} */
], //
lastUploadedTotalSize: 0, //
liveSpeed: 0, //(B)
takenTime: 0, //
remainTime: 0, //
interval: null,
second: 1, //
successFileList: [], //
errorFileList: [], //
remainFileList: [], //
};
},
props: ["data", "value", "resizeable", "position"],
watch: {
value: {
handler(d) {
this.show = d;
},
deep: true,
immediate: true,
},
show: {
handler(d) {
this.$emit(`input`, d);
},
deep: true,
immediate: true,
},
data: {
handler(d) {
this.uploadList = d || [];
},
deep: true,
immediate: true,
},
uploadList: {
handler(newValue, oldValue) {
if (newValue && Object.keys(newValue).length) {
this.interval || this.startUploadCalcLiveSpeed();
this.successFileList = newValue.filter((v) => v.percent == 100);
this.errorFileList = newValue.filter((v) => v.status === "error");
this.remainFileList = newValue.filter(
(v) => v.status !== "error" && v.status !== "success"
);
} else {
this.successFileList = [];
this.errorFileList = [];
this.remainFileList = [];
}
},
deep: true, //
immediate: true, //
},
resizeable: {
handler(newValue, oldValue) {
this.resizeable_ = newValue === "" || newValue;
},
deep: true, //
immediate: true, //
},
size: {
handler(newValue, oldValue) {
switch (newValue) {
case "lg":
case "mn":
this.style_bk = JSON.parse(JSON.stringify(this.style));
delete this.style.width, delete this.style.height;
break;
case "md":
this.style_bk && (this.style = JSON.parse(JSON.stringify(this.style_bk)));
break;
}
},
deep: true, //
immediate: true, //
},
},
computed: {
showDelSuccessIconBtn(d) {
return this.uploadList.some((v) => v.percent == 100);
},
showErrorIconBtn(d) {
return this.uploadList.some((v) => v.status == "error");
},
popoverContent(d) {
let r = [];
this.successFileList.length &&
r.push(
`已上传成功<span style="color: #67C23A;">${this.successFileList.length}</span>个`
);
this.errorFileList.length &&
r.push(`失败<span style="color: #F56C6C;">${this.errorFileList.length}</span>个`);
this.remainFileList.length &&
r.push(
`剩余<span style="color: #409EFF;">${this.remainFileList.length}</span>个`
);
if (this.uploadList.length) {
return `共计${this.uploadList.length}个文件,${
r.length ? `${r.join("")}文件` : ``
}`;
} else {
return `暂无待上传文件`;
}
},
//
totalPercentage() {
if (this.uploadList.length) {
return parseFloat(
((this.successFileList.length / this.uploadList.length) * 100).toFixed(2)
);
} else {
return 0;
}
},
//
uploadingFiles(d) {
let uploadingFiles_ = (this.uploadList || []).filter(
(v) => v.percent < 100 && v.status !== "error" && v.status !== "success"
);
if (uploadingFiles_.length) {
this.$emit(`changeUploadingListClose`, {
path: this.position,
close: this.close,
});
} else {
this.$emit(`changeUploadingListClose`, null);
}
return uploadingFiles_;
},
},
mounted() {
this.$el.style.setProperty("--minWidth", `${this.minWidth}px`); //jscss
this.$el.style.setProperty("--minHeight", `${this.minHeight}px`); //jscss
this.dragMoveDoms = [
{
canDragDom: this.$refs.header, //
moveDom: this.$el, //
},
];
},
destroyed() {
clearInterval(this.interval);
},
methods: {
//
startUploadCalcLiveSpeed() {
clearInterval(this.interval);
this.interval = setInterval(() => {
this.calcLiveSpeed();
}, 1000 * this.second);
},
//
endUploadCalcLiveSpeed(d) {
clearInterval(this.interval);
this.interval = null;
this.liveSpeed = 0;
this.takenTime = 0;
this.remainTime = 0;
},
//
ifNoUploadingFile_EndCalcLiveSpeed(d) {
this.uploadingFiles.length || this.endUploadCalcLiveSpeed();
},
//
calcLiveSpeed(d) {
this.takenTime++;
let uploadList = this.uploadList;
if (uploadList.length) {
let totalSize = uploadList.reduce(
(prevResult, current) => prevResult + current.size,
0
); //
let uploadedTotalSize = uploadList.reduce(
(prevResult, current) => prevResult + current.size * (0.01 * current.percent),
0
); //
let remainTotalSize = totalSize - uploadedTotalSize; //
if (this.lastUploadedTotalSize) {
this.liveSpeed = (uploadedTotalSize - this.lastUploadedTotalSize) / this.second; //
this.remainTime = remainTotalSize / this.liveSpeed; //
} else {
this.liveSpeed = 0;
}
this.lastUploadedTotalSize = uploadedTotalSize; //
} else {
this.endUploadCalcLiveSpeed();
}
},
clearAllSuccessFile() {
let successFileList = this.uploadList.filter((v) => v.percent == 100);
if (successFileList.length === 0)
return this.$message(`暂无可以移除的成功记录,请稍后再试!`);
this.$emit(`clearAllSuccessFile`, successFileList);
this.$nextTick(() => {
successFileList.forEach((file) => {
file.removeFile(); //
});
this.uploadList = this.uploadList.filter((v) => v.percent < 100);
});
},
clearAllErrorFile() {
let errorFileList = this.uploadList.filter((v) => v.status == "error");
if (errorFileList.length === 0)
return this.$message(`暂无可以移除的失败记录,请稍后再试!`);
this.$emit(`clearAllErrorFile`, errorFileList);
this.$nextTick(() => {
errorFileList.forEach((file) => {
file.removeFile(); //
});
this.uploadList = this.uploadList.filter((v) => v.status !== "error");
});
},
uploadAllErrorFile(d) {
let errorFileList = this.uploadList.filter((v) => v.status == "error");
errorFileList.forEach((fileData) => this.startUploadFile(fileData));
if (errorFileList.length === 0) return this.$message(`暂无失败记录,请稍后再试!`);
this.$emit(`uploadAllErrorFile`, errorFileList);
},
draggingSize({ style }) {
this.disabledDragMove = true;
this.style = style;
},
toRightBottomPosition(d) {
this.showRightBottomBtn = false;
let rect = this.$el.getBoundingClientRect();
this.$el.style.left = `${innerWidth - rect.width}px`;
this.$el.style.top = `${innerHeight - rect.height}px`;
// setProperty
/* this.$el.style = {
left: innerWidth - rect.width + "px",
top: innerHeight - rect.height + "px",
}; */
},
dblclickHeader(d) {
switch (this.size) {
case "lg":
this.size = "md";
break;
case "md":
this.size = "mn";
break;
case "mn":
this.size = "md";
break;
default:
}
},
removeAllFilesFromList() {
this.uploadList.forEach((file) => {
file.removeFile(); //
});
this.uploadList = []; //
},
removeFileFromList(d) {
let file = this.uploadList.find((v) => v.uid == d.uid);
file.removeFile(); //
this.uploadList.splice(
this.uploadList.findIndex((v) => v.uid == d.uid),
1
); //
},
//
startUploadFile(fileData) {
fileData.startUpload({ handleTrigger: true });
},
//
removeUploadFile(d) {
if (d.status === "error" || d.status === "success") {
this.removeFileFromList(d);
} else if (d.percent < 100) {
this.$confirm(`${d.name}正在上传中,确定要取消吗?`, `提示`, {
dangerouslyUseHTMLString: true,
confirmButtonText: `确定`,
cancelButtonText: `取消`,
type: "warning",
})
.then(() => {
this.$emit(`stopUpload`, [d]);
this.$nextTick(() => {
this.removeFileFromList(d);
this.ifNoUploadingFile_EndCalcLiveSpeed();
});
})
.catch(() => {});
} else {
this.removeFileFromList(d);
}
},
//
close({ cb } = {}) {
let stopUploadList = this.uploadingFiles;
if (stopUploadList.length) {
this.$confirm(`您还有正在上传中的文件,确定要取消吗?`, `提示`, {
dangerouslyUseHTMLString: true,
confirmButtonText: `确定`,
cancelButtonText: `取消`,
type: "warning",
})
.then(() => {
this.show = false;
this.$emit(`stopUpload`, stopUploadList);
this.$nextTick(() => {
this.endUploadCalcLiveSpeed();
this.removeAllFilesFromList();
cb && cb(stopUploadList); //
});
})
.catch(() => {});
} else {
this.show = false;
}
},
},
};
</script>
<style lang="scss" scoped>
.sgUploadTray {
$headerHeight: 40px; //
$footerHeight: 40px; //
$collapseBtnHeight: 30px; //
$minWidth: var(--minWidth); //
$minHeight: var(--minHeight); //
$loadingWidth: 30px; //
$rightWidth: 200px; //
$sizeWidth: 200px; //
$progressWidth: 100px; //
$tipWidth: 100px; //
// ----------------------------------------
z-index: 2001; //(element)v-loading2000z-index
user-select: none;
position: fixed;
right: 10px;
bottom: 10px;
width: $minWidth;
background-color: white;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border-radius: 4px;
overflow: hidden;
border: 1px solid #eee;
font-size: 14px;
display: none;
&[show] {
display: block;
}
&[size="lg"] {
left: 0 !important;
top: 0 !important;
width: 100vw;
height: 100vh;
transition: none;
.upload-file-list {
max-height: calc(100vh - 60px) !important;
}
}
&[size="md"] {
width: $minWidth;
height: revert;
}
&[size="mn"] {
width: $minWidth;
height: $minHeight;
}
.upload-list-tray {
display: flex;
flex-direction: column;
box-sizing: border-box;
padding-bottom: 20px;
width: 100%;
height: 100%;
position: relative;
.header {
flex-shrink: 0;
font-size: 16px;
font-weight: bold;
width: 100%;
height: $headerHeight;
box-sizing: border-box;
padding: 10px 20px;
/*从上往下线性渐变背景*/
background: linear-gradient(#409eff11, white);
color: #409eff;
display: flex;
justify-content: space-between;
align-items: center;
.left {
display: flex;
align-items: center;
flex-grow: 1;
.title {
display: flex;
align-items: center;
flex-wrap: nowrap;
.upload-info {
display: flex;
align-items: center;
color: black;
flex-shrink: 0;
align-items: center;
font-weight: normal;
.info-item {
margin-right: 5px;
&:last-of-type {
margin-right: 0;
}
span {
font-family: DIN-Light;
color: #409eff;
}
&.live-speed {
span {
font-family: DIN-Black;
}
}
}
}
}
.icon-btns {
display: flex;
align-items: center;
flex-wrap: nowrap;
.icon-btn {
cursor: pointer;
margin-right: 5px;
&:last-of-type {
margin-right: 0;
}
i {
pointer-events: none;
}
&:hover {
opacity: 0.618;
}
}
}
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
flex-shrink: 0;
pointer-events: auto;
.icon-btn {
margin-left: 10px;
cursor: pointer;
i {
pointer-events: none;
}
&:hover {
opacity: 0.618;
}
&:first-of-type {
margin-left: 0;
}
}
.file-btns {
margin-left: 10px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
box-sizing: border-box;
padding: 0 10px;
border-right: 1px solid #eee;
}
.tray-btns {
margin-left: 10px;
display: flex;
flex-wrap: nowrap;
justify-content: flex-end;
}
}
}
.upload-file-list {
width: 100%;
flex-grow: 1;
overflow-y: auto;
height: max-content;
max-height: calc(
100vh - #{$headerHeight} - #{$footerHeight} - #{$collapseBtnHeight} - 40px
);
box-sizing: border-box;
padding: 0 20px;
ul {
width: 100%;
li {
line-height: 1.6;
box-sizing: border-box;
padding: 10px;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.left {
width: calc(100% - #{$rightWidth});
display: flex;
align-items: center;
flex-grow: 1;
flex-shrink: 0;
//
.icon-btns {
display: flex;
flex-wrap: nowrap;
.icon-btn {
display: none;
&[show] {
margin-right: 15px;
display: block;
}
}
}
.fileLoading {
flex-shrink: 0;
width: 30px;
margin-right: 5px;
height: 0;
transform: scale(0.5);
}
.loadingSuccessIcon,
.loadingEorrorIcon {
margin-right: 5px;
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
flex-shrink: 0;
}
.name {
text-align: left;
margin-right: 10px;
width: calc(
100% - #{$loadingWidth} - #{$sizeWidth} - #{$progressWidth} - #{$rightWidth} -
20px
);
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
flex-shrink: 0;
flex-grow: 1;
}
.size {
margin-right: 10px;
max-width: $sizeWidth;
/*单行省略号*/
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
flex-shrink: 0;
}
.progress {
max-width: $progressWidth;
display: flex;
align-items: center;
flex-wrap: nowrap;
flex-shrink: 0;
}
}
.right {
display: flex;
align-items: center;
justify-content: flex-end;
width: $rightWidth;
.tip {
width: $tipWidth;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
flex-shrink: 0;
text-align: right;
&[color="red"] {
color: #f56c6c;
}
&[color="green"] {
color: #67c23a;
}
&[color="blue"] {
color: #409eff;
}
}
//
.icon-btns {
display: flex;
flex-wrap: nowrap;
.icon-btn {
display: none;
&[show] {
margin-left: 15px;
display: block;
}
}
}
}
&:hover {
background-color: #409eff11;
color: #409eff;
.left {
//
.icon-btns {
.icon-btn {
display: block;
&:last-of-type {
margin-right: 10px;
}
}
}
}
.right {
.tip {
margin-right: 15px;
}
//
.icon-btns {
.icon-btn {
display: block;
&:first-of-type {
margin-left: 0;
}
}
}
}
}
}
}
}
.footer {
font-weight: normal;
flex-shrink: 0;
font-size: 14px;
font-weight: bold;
width: 100%;
height: $footerHeight;
box-sizing: border-box;
padding: 10px 20px;
margin-bottom: -20px;
background: linear-gradient(white, #eff2f7);
color: #909399;
display: flex;
align-items: center;
flex-wrap: nowrap;
white-space: nowrap;
* {
font-weight: normal;
}
.text {
white-space: nowrap;
}
.progress {
max-width: 200px;
flex-grow: 1;
white-space: nowrap;
display: flex;
align-items: center;
flex-wrap: nowrap;
label {
white-space: nowrap;
flex-shrink: 0;
margin-right: 5px;
}
>>> .el-progress {
white-space: nowrap;
.el-progress__text {
font-weight: normal;
font-size: 14px !important;
}
}
}
}
}
}
</style>