feat: 文件支持拖动到列表上传

This commit is contained in:
kuaifan 2022-02-25 22:48:30 +08:00
parent 123ffd4e66
commit 356d40e640
14 changed files with 399 additions and 266 deletions

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.9.70",
"version": "0.9.73",
"description": "DooTask is task management system.",
"main": "electron.js",
"license": "MIT",

View File

@ -1,6 +1,6 @@
{
"name": "DooTask",
"version": "0.9.70",
"version": "0.9.73",
"description": "DooTask is task management system.",
"scripts": {
"start": "./cmd dev",

2
public/css/app.css vendored

File diff suppressed because one or more lines are too long

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/js/build/510.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -57,73 +57,82 @@
</div>
</div>
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
<Table
:columns="columns"
:data="fileList"
:height="tableHeight"
:no-data-text="$L('没有任何文件')"
@on-cell-click="clickRow"
@on-contextmenu="handleContextMenu"
@on-select="handleTableSelect"
@on-select-cancel="handleTableSelect"
@on-select-all-cancel="handleTableSelect"
@on-select-all="handleTableSelect"
context-menu
stripe/>
</div>
<template v-else>
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
<i class="taskfont">&#xe60b;</i>
<p>{{$L('没有任何文件')}}</p>
<div
class="file-drag"
@drop.prevent="filePasteDrag($event, 'drag')"
@dragover.prevent="fileDragOver(true, $event)"
@dragleave.prevent="fileDragOver(false, $event)">
<div v-if="tableMode" class="file-table" @contextmenu.prevent="handleRightClick">
<Table
:columns="columns"
:data="fileList"
:height="tableHeight"
:no-data-text="$L('没有任何文件')"
@on-cell-click="clickRow"
@on-contextmenu="handleContextMenu"
@on-select="handleTableSelect"
@on-select-cancel="handleTableSelect"
@on-select-all-cancel="handleTableSelect"
@on-select-all="handleTableSelect"
context-menu
stripe/>
</div>
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
<ul class="clearfix">
<li
v-for="item in fileList"
:class="{
<template v-else>
<div v-if="fileList.length == 0 && loadIng == 0" class="file-no" @contextmenu.prevent="handleRightClick">
<i class="taskfont">&#xe60b;</i>
<p>{{$L('没有任何文件')}}</p>
</div>
<div v-else class="file-list" @contextmenu.prevent="handleRightClick">
<ul class="clearfix">
<li
v-for="item in fileList"
:class="{
shear: shearIds.includes(item.id),
highlight: selectIds.includes(item.id),
}"
@contextmenu.prevent.stop="handleRightClick($event, item)"
@click="openFile(item)">
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
<Checkbox :value="selectIds.includes(item.id)"/>
</div>
<div class="file-menu" @click.stop="handleRightClick($event, item)">
<Icon type="ios-more" />
</div>
<div :class="`no-dark-mode-before file-icon ${item.type}`">
<template v-if="item.share">
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
</UserAvatar>
<div v-else class="share-icon no-dark-mode">
<i class="taskfont">&#xe757;</i>
</div>
</template>
<template v-else-if="isParentShare">
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
</UserAvatar>
</template>
</div>
<div v-if="item._edit" class="file-input">
<Input
:ref="'input_' + item.id"
v-model="item.newname"
size="small"
:disabled="!!item._load"
@on-blur="onBlur(item)"
@on-enter="onEnter(item)"/>
<div v-if="item._load" class="file-load"><Loading/></div>
</div>
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
</li>
</ul>
@contextmenu.prevent.stop="handleRightClick($event, item)"
@click="openFile(item)">
<div class="file-check" :class="{'file-checked':selectIds.includes(item.id)}" @click.stop="dropFile(item, 'select')">
<Checkbox :value="selectIds.includes(item.id)"/>
</div>
<div class="file-menu" @click.stop="handleRightClick($event, item)">
<Icon type="ios-more" />
</div>
<div :class="`no-dark-mode-before file-icon ${item.type}`">
<template v-if="item.share">
<UserAvatar v-if="item.userid != userId" :userid="item.userid" class="share-avatar" :size="20">
<p>{{$L('共享权限')}}: {{$L(item.permission == 1 ? '读/写' : '只读')}}</p>
</UserAvatar>
<div v-else class="share-icon no-dark-mode">
<i class="taskfont">&#xe757;</i>
</div>
</template>
<template v-else-if="isParentShare">
<UserAvatar :userid="item.created_id" class="share-avatar" :size="20">
<p v-if="item.created_id != item.userid"><strong>{{$L('成员创建于')}}: {{item.created_at}}</strong></p>
<p v-else>{{$L('所有者创建于')}}: {{item.created_at}}</p>
</UserAvatar>
</template>
</div>
<div v-if="item._edit" class="file-input">
<Input
:ref="'input_' + item.id"
v-model="item.newname"
size="small"
:disabled="!!item._load"
@on-blur="onBlur(item)"
@on-enter="onEnter(item)"/>
<div v-if="item._load" class="file-load"><Loading/></div>
</div>
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
</li>
</ul>
</div>
</template>
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
</div>
</template>
</div>
<div class="file-menu" :style="contextMenuStyles">
<Dropdown
@ -323,6 +332,21 @@
<FileContent v-else v-model="fileShow" :file="fileInfo"/>
</DrawerOverlay>
<!--拖动上传提示-->
<Modal
v-model="pasteShow"
:title="$L(pasteTitle)"
:cancel-text="$L('取消')"
:ok-text="$L('发送')"
:enter-ok="true"
@on-ok="pasteSend">
<div class="dialog-wrapper-paste">
<template v-for="item in pasteItem">
<img v-if="item.type == 'image'" :src="item.result"/>
<div v-else>{{$L('文件')}}: {{item.name}} ({{$A.bytesToSize(item.size)}})</div>
</template>
</div>
</Modal>
</div>
</template>
@ -457,6 +481,11 @@ export default {
shearIds: [],
selectIds: [],
dialogDrag: false,
pasteShow: false,
pasteFile: [],
pasteItem: [],
}
},
@ -537,6 +566,18 @@ export default {
const {navigator} = this;
return !!navigator.find(({share}) => share);
},
pasteTitle() {
const {pasteItem} = this;
let hasImage = pasteItem.find(({type}) => type == 'image')
let hasFile = pasteItem.find(({type}) => type != 'image')
if (hasImage && hasFile) {
return '上传文件/图片'
} else if (hasImage) {
return '上传图片'
}
return '上传文件'
}
},
watch: {
@ -1281,6 +1322,61 @@ export default {
this.selectIds = [];
},
/********************拖动上传部分************************/
pasteDragNext(e, type) {
let files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
files = Array.prototype.slice.call(files);
if (files.length > 0) {
e.preventDefault();
if (files.length > 0) {
this.pasteFile = [];
this.pasteItem = [];
files.some(file => {
let reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = ({target}) => {
this.pasteFile.push(file)
this.pasteItem.push({
type: $A.getMiddle(file.type, null, '/'),
name: file.name,
size: file.size,
result: target.result
})
this.pasteShow = true
}
});
}
}
},
filePasteDrag(e, type) {
this.dialogDrag = false;
this.pasteDragNext(e, type);
},
fileDragOver(show, e) {
let random = (this.__dialogDrag = $A.randomString(8));
if (!show) {
setTimeout(() => {
if (random === this.__dialogDrag) {
this.dialogDrag = show;
}
}, 150);
} else {
if (e.dataTransfer.effectAllowed === 'move') {
return;
}
this.dialogDrag = true;
}
},
pasteSend() {
this.pasteFile.some(file => {
this.$refs.fileUpload.upload(file)
});
},
/********************文件上传部分************************/
uploadUpdate(fileList) {

View File

@ -90,24 +90,6 @@
}
}
}
.file-no {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-bottom: 5%;
opacity: 0.8;
> i {
font-size: 64px;
}
> p {
margin-top: 18px;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
}
.file-navigator {
display: flex;
align-items: center;
@ -254,192 +236,245 @@
}
}
}
.file-table {
.file-drag {
flex: 1;
cursor: default;
margin: 16px 32px 32px;
.ivu-table {
&:before {
display: none;
}
.ivu-table-tip {
opacity: 0.8;
span {
font-size: 14px;
font-weight: 500;
line-height: 1.8;
&:before {
display: block;
content: "\e60b";
font-family: "taskfont", "serif" !important;
font-size: 64px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
}
}
}
}
.file-nbox {
height: 0;
display: flex;
flex-direction: column;
position: relative;
.file-no {
flex: 1;
display: flex;
align-items: center;
position: relative;
&.shear {
opacity: 0.38;
justify-content: center;
flex-direction: column;
margin-bottom: 5%;
opacity: 0.8;
> i {
font-size: 64px;
}
.file-name {
flex: 1;
width: 0;
display: flex;
align-items: center;
position: relative;
margin-right: 46px;
&:before {
flex-shrink: 0;
content: "";
width: 22px;
height: 22px;
margin-right: 8px;
}
}
.permission {
padding-left: 6px;
font-size: 13px;
}
.taskfont {
color: #aaaaaa;
font-size: 16px;
margin: 0 3px;
> p {
margin-top: 18px;
font-size: 14px;
font-weight: 500;
line-height: 1;
}
}
}
.file-list {
flex: 1;
padding: 0 20px 20px;
margin-top: 16px;
overflow: auto;
> ul {
margin-top: -12px;
> li {
list-style: none;
float: left;
margin: 12px;
width: 100px;
height: 110px;
position: relative;
border-radius: 5px;
.file-table {
flex: 1;
cursor: default;
margin: 16px 32px 32px;
.ivu-table {
&:before {
display: none;
}
.ivu-table-tip {
opacity: 0.8;
span {
font-size: 14px;
font-weight: 500;
line-height: 1.8;
&:before {
display: block;
content: "\e60b";
font-family: "taskfont", "serif" !important;
font-size: 64px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke-width: 0.2px;
}
}
}
}
.file-nbox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
cursor: pointer;
.file-input {
margin: 0 4px 4px;
position: relative;
input {
margin: 0;
padding: 1px 5px;
font-size: 13px;
}
.file-load {
position: absolute;
top: 0;
right: 6px;
bottom: 0;
display: flex;
.common-loading {
width: 10px;
height: 10px;
}
}
}
.file-name {
display: block;
width: 100%;
height: 20px;
line-height: 20px;
color: $primary-text-color;
font-size: 12px;
text-align: center;
margin-bottom: 5px;
padding: 0 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.file-check {
opacity: 0;
position: absolute;
top: 1px;
left: 4px;
transition: opacity 0.2s;
&.file-checked {
opacity: 1;
}
}
.file-menu {
opacity: 0;
position: absolute;
top: 2px;
right: 2px;
transition: opacity 0.2s;
display: flex;
.ivu-icon {
font-size: 16px;
color: #aaaaaa;
transition: color 0.2s;
padding: 2px 5px;
&:hover {
color: $primary-text-color;
}
}
}
.file-icon {
display: inline-block;
width: 64px;
height: 64px;
margin-top: 12px;
position: relative;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.share-icon,
.share-avatar {
position: absolute;
right: 0;
bottom: 0;
background-color: #9ACD7B;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0.9);
.taskfont {
font-size: 18px;
color: #ffffff;
}
}
}
position: relative;
&.shear {
opacity: 0.38;
}
&.highlight {
background-color: #f4f5f7;
}
&:hover {
background-color: #f4f5f7;
.file-menu,
.file-check {
opacity: 1;
.file-name {
flex: 1;
width: 0;
display: flex;
align-items: center;
position: relative;
margin-right: 46px;
&:before {
flex-shrink: 0;
content: "";
width: 22px;
height: 22px;
margin-right: 8px;
}
}
.permission {
padding-left: 6px;
font-size: 13px;
}
.taskfont {
color: #aaaaaa;
font-size: 16px;
margin: 0 3px;
}
}
}
.file-list {
flex: 1;
padding: 0 20px 20px;
margin-top: 16px;
overflow: auto;
> ul {
margin-top: -12px;
> li {
list-style: none;
float: left;
margin: 12px;
width: 100px;
height: 110px;
position: relative;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
cursor: pointer;
.file-input {
margin: 0 4px 4px;
position: relative;
input {
margin: 0;
padding: 1px 5px;
font-size: 13px;
}
.file-load {
position: absolute;
top: 0;
right: 6px;
bottom: 0;
display: flex;
.common-loading {
width: 10px;
height: 10px;
}
}
}
.file-name {
display: block;
width: 100%;
height: 20px;
line-height: 20px;
color: $primary-text-color;
font-size: 12px;
text-align: center;
margin-bottom: 5px;
padding: 0 6px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.file-check {
opacity: 0;
position: absolute;
top: 1px;
left: 4px;
transition: opacity 0.2s;
&.file-checked {
opacity: 1;
}
}
.file-menu {
opacity: 0;
position: absolute;
top: 2px;
right: 2px;
transition: opacity 0.2s;
display: flex;
.ivu-icon {
font-size: 16px;
color: #aaaaaa;
transition: color 0.2s;
padding: 2px 5px;
&:hover {
color: $primary-text-color;
}
}
}
.file-icon {
display: inline-block;
width: 64px;
height: 64px;
margin-top: 12px;
position: relative;
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.share-icon,
.share-avatar {
position: absolute;
right: 0;
bottom: 0;
background-color: #9ACD7B;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transform: scale(0.9);
.taskfont {
font-size: 18px;
color: #ffffff;
}
}
}
&.shear {
opacity: 0.38;
}
&.highlight {
background-color: #f4f5f7;
}
&:hover {
background-color: #f4f5f7;
.file-menu,
.file-check {
opacity: 1;
}
}
}
}
}
.drag-over {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 3;
background-color: rgba(255, 255, 255, 0.78);
display: flex;
align-items: center;
justify-content: center;
margin: 16px 32px 32px;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 2px dashed #7b7b7b;
border-radius: 12px;
}
.drag-text {
padding: 12px;
font-size: 18px;
color: #666666;
}
}
}
@ -620,8 +655,10 @@
.file-navigator {
margin: 0 24px 0;
}
.file-table {
margin: 16px 24px 24px;
.file-drag {
.file-table {
margin: 16px 24px 24px;
}
}
}
}