cool-admin-vue/cool/modules/upload/components/space.vue
2021-02-28 22:24:54 +08:00

722 lines
14 KiB
Vue

<template>
<div class="cl-upload-space__wrap">
<slot>
<el-button v-if="showButton" size="mini" @click="open">点击上传</el-button>
</slot>
<!-- 弹框 -->
<cl-dialog :visible.sync="visible" v-bind="props" :op-list="['close']">
<div class="cl-upload-space">
<!-- 类目 -->
<div class="cl-upload-space__category">
<div class="cl-upload-space__category-search">
<el-button type="primary" size="mini" @click="editCategory()"
>添加分类</el-button
>
<el-input
v-model="category.keyword"
placeholder="输入关键字过滤"
size="mini"
></el-input>
</div>
<div class="cl-upload-space__category-list">
<ul>
<li
v-for="(item, index) in categoryList"
:key="index"
:class="{
'is-active': item.id == category.current.id
}"
@click="selectCategory(item)"
@contextmenu.stop.prevent="openCategoryContextMenu($event, item)"
>
{{ item.name }}
</li>
</ul>
</div>
</div>
<!-- 内容 -->
<div class="cl-upload-space__content">
<!-- 操作栏 -->
<div class="cl-upload-space__opbar">
<el-button
type="success"
size="mini"
:disabled="selection.length === 0"
@click="confirmFile()"
>使用选中文件</el-button
>
<el-button
type="danger"
size="mini"
:disabled="selection.length === 0"
@click="deleteFile()"
>删除选中文件</el-button
>
<cl-upload
style="margin-left: 10px"
list-type="slot"
:action="action"
:accept="accept"
:limit-size="limitSize"
:show-file-list="false"
:headers="headers"
:data="data"
:disabled="disabled"
:rename="rename"
:on-success="onSuccess"
:on-progress="onProgress"
:before-upload="beforeUpload"
>
<el-button size="mini" type="primary">点击上传</el-button>
</cl-upload>
</div>
<!-- 文件区域 -->
<div
class="cl-upload-space__file"
v-loading="file.loading"
element-loading-text="拼命加载中"
>
<!-- 文件列表 -->
<el-row v-if="file.list.length > 0">
<el-col :span="6" v-for="item in file.list" :key="item.id">
<file-item
:value="item"
:element-loading-text="item.progress"
v-loading="item.loading"
></file-item>
</el-col>
</el-row>
<!-- 空态 -->
<div class="cl-upload-space__file-empty" v-else>
<cl-upload
drag
:action="action"
:accept="accept"
:limit-size="limitSize"
:headers="headers"
:data="data"
:disabled="disabled"
:rename="rename"
:on-success="onSuccess"
:on-progress="onProgress"
:before-upload="beforeUpload"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
</cl-upload>
</div>
</div>
<!-- 分页 -->
<el-pagination
background
:page-size="file.pagination.size"
:current-page="file.pagination.page"
:total="file.pagination.total"
@current-change="onCurrentChange"
></el-pagination>
</div>
</div>
</cl-dialog>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import { last, isEmpty } from "cl-admin/utils";
export default {
name: "cl-upload-space",
componentName: "UploadSpace",
props: {
// 上传的地址
action: String,
// 选择图片的长度
limit: {
type: Number,
default: 8
},
// 最大允许上传文件大小(MB)
limitSize: {
type: Number,
default: 10
},
// 是否禁用
disabled: Boolean,
// 是否以 uuid 重命名
rename: Boolean,
// 设置上传的请求头部
headers: Object,
// 上传时附带的额外参数
data: Object,
// 上传的文件类型
accept: String,
// 是否返回详细数据
detailData: Boolean,
// 是否显示按钮
showButton: {
type: Boolean,
default: true
}
},
components: {
fileItem: {
props: {
value: Object
},
computed: {
parent() {
let parent = this;
while (parent.$options.componentName != "UploadSpace") {
parent = parent.$parent;
}
return parent;
}
},
methods: {
onSelect() {
this.parent.selectFile(this.value);
},
onContextMenu(e) {
this.parent.openFileContextMenu(e, this.value);
e.stopPropagation();
e.preventDefault();
}
},
render() {
if (!this.value) {
return null;
}
let itemEl = null;
const { url, type, selected, id } = this.value;
const fileType = (type || "").split("/")[0];
switch (fileType) {
case "image":
itemEl = <el-image fit="cover" src={url} lazy></el-image>;
break;
case "video":
itemEl = (
<video
controls
src={url}
style={{
"max-height": "100%",
"max-width": "100%"
}}></video>
);
break;
default:
itemEl = <span>{url}</span>;
break;
}
return (
<div
class={["cl-upload-space__file-item", `is-${fileType}`]}
on-click={this.onSelect}
on-contextmenu={this.onContextMenu}>
{itemEl}
<div class="cl-upload-space__file-size"></div>
{selected && (
<div class="cl-upload-space__file-mask">
<i class="el-icon-success"></i>
</div>
)}
</div>
);
}
}
},
data() {
return {
visible: false,
props: {
title: "文件空间",
props: {
"close-on-click-modal": false,
"append-to-body": true,
width: "1000px"
}
},
category: {
list: [],
current: {},
keyword: ""
},
file: {
list: [],
pagination: {
page: 1,
size: 12,
total: 0
},
loading: false
}
};
},
computed: {
...mapGetters(["token"]),
categoryList() {
return this.category.list.filter((e) => e.name.includes(this.category.keyword));
},
selection() {
return this.file.list.filter((e) => e.selected);
}
},
filters: {
file_name(url) {
return last(url.split("."));
}
},
created() {
this.refreshCategory().then(() => {
this.category.current = this.category.list[0];
this.refreshFile();
});
},
methods: {
open(key) {
this.visible = true;
},
close() {
this.visible = false;
this.$nextTick(() => {
this.file.list.map((e) => {
this.$set(e, "selected", false);
});
});
},
// 上传成功
onSuccess(res, file) {
let item = this.file.list.find((e) => file.uid == e.uid);
if (item) {
item.url = res.data;
this.$service.space.info
.add({
url: res.data,
type: item.type,
classifyId: item.classifyId
})
.then((res) => {
item.loading = false;
item.id = res.id;
})
.catch((err) => {
this.$message.error(err);
});
}
},
// 上传前,添加文件
beforeUpload({ tempFilePath, type, uid }) {
this.file.list.unshift({
url: tempFilePath,
type,
uid,
classifyId: this.category.current.id,
loading: true,
progress: "0%"
});
},
// 上传进度
onProgress({ percent }, file) {
let item = this.file.list.find(({ uid }) => uid == file.uid);
if (item) {
item.progress = percent + "%";
}
},
// 刷新资源文件
refreshFile(params) {
this.file.loading = true;
this.$service.space.info
.page({
...this.file.pagination,
...params,
classifyId: this.category.current.id,
type: this.accept
})
.then((res) => {
this.file.pagination = res.pagination;
this.file.list = res.list;
})
.done(() => {
this.file.loading = false;
});
},
// 刷新分类
refreshCategory() {
return this.$service.space.type.list().then((res) => {
res.unshift({
name: "全部文件",
id: null
});
this.category.list = res;
});
},
// 编辑分类
editCategory(item = {}) {
this.$crud.openForm({
title: "添加分类",
width: "400px",
items: [
{
label: "分类名称",
prop: "name",
value: item.name,
component: {
name: "el-input",
attrs: {
placeholder: "请填写分类名称"
}
},
rules: {
required: true,
message: "分类名称不能为空"
}
}
],
on: {
submit: (data, { done, close }) => {
let next = null;
if (!item.id) {
next = this.$service.space.type.add(data);
} else {
next = this.$service.space.type.update({
...data,
id: item.id
});
}
next.then(() => {
this.refreshCategory();
close();
}).catch((err) => {
this.$message.error(err);
done();
});
}
}
});
},
// 选择类目
selectCategory(item) {
this.category.current = item;
this.file.pagination = {
page: 1,
size: 12,
total: 0
};
this.refreshFile({
classifyId: item.id
});
},
// 打开类目列表右键菜单
openCategoryContextMenu(e, { id, name }) {
if (!id) {
return false;
}
this.$crud.openContextMenu(e, {
list: [
{
label: "编辑",
"suffix-icon": "el-icon-edit",
callback: (item, done) => {
done();
this.editCategory({ id, name });
}
},
{
label: "删除",
"suffix-icon": "el-icon-delete",
callback: (item, done) => {
done();
this.$confirm(`此操作将删除【${name}】下的文件, 是否继续?`, "提示", {
type: "warning"
})
.then(() => {
this.$service.space.type
.delete({
ids: id
})
.then(() => {
this.$message.success("删除成功");
this.refreshCategory();
// 删除当前类目时,重置选择
if (id == this.category.current.id) {
this.category.current = this.category.list[0];
this.refreshFile();
}
})
.catch((err) => {
console.error(err);
this.$message.error(err);
});
})
.catch(() => {});
}
}
]
});
},
// 打开文件列表右键菜单
openFileContextMenu(e, data) {
this.$crud.openContextMenu(e, {
list: [
{
label: data.selected ? "取消选中" : "选中",
"suffix-icon": data.selected ? "el-icon-close" : "el-icon-check",
callback: (item, done) => {
this.selectFile(data);
done();
}
},
{
label: "删除",
"suffix-icon": "el-icon-delete",
callback: (item, done) => {
this.deleteFile(data);
done();
}
}
]
});
},
// 确认选中文件
confirmFile() {
const selection = this.selection.filter((e, i) => i < this.limit);
const urls = selection.map((e) => e.url).join(",");
this.$emit("input", urls);
this.$emit("confirm", this.detailData ? selection : urls);
this.close();
},
// 选择文件
selectFile(item) {
this.$set(item, "selected", !item.selected);
},
// 删除选中文件
deleteFile(...selection) {
if (isEmpty(selection)) {
selection = this.selection;
}
this.$confirm("此操作将删除文件, 是否继续?", "提示", {
type: "warning"
})
.then(() => {
this.$message.success("删除成功");
this.file.list = this.file.list.filter(
(e) => !selection.map((e) => e.id).includes(e.id)
);
this.$service.space.info
.delete({
ids: selection.map((e) => e.id).join(",")
})
.catch((err) => {
this.$message.error(err);
});
})
.catch(() => {});
},
// 选择页
onCurrentChange(i) {
this.refreshFile({
page: i
});
}
}
};
</script>
<style lang="scss" scoped>
.cl-upload-space {
display: flex;
min-height: 520px;
&__category {
width: 250px;
margin-right: 20px;
&-search {
display: flex;
align-items: center;
margin-bottom: 5px;
.el-button {
margin-right: 10px;
}
}
&-list {
overflow: hidden auto;
ul {
li {
list-style: none;
font-size: 14px;
height: 40px;
line-height: 40px;
border-bottom: 1px dashed #eee;
padding: 0 5px;
cursor: pointer;
&.is-active {
color: #409eff;
}
}
}
}
}
&__content {
flex: 1;
}
&__opbar {
display: flex;
align-items: center;
margin-bottom: 10px;
}
&__file {
height: calc(100% - 80px);
overflow: hidden auto;
margin-bottom: 10px;
/deep/.cl-upload-space__file-item {
display: flex;
align-items: center;
justify-content: center;
height: 160px;
width: 160px;
cursor: pointer;
position: relative;
border-radius: 3px;
box-sizing: border-box;
border: 1px solid #eee;
margin: 5px 0;
&.is-image {
overflow: hidden;
img {
height: 100%;
width: 100%;
}
}
&.is-video {
video {
max-height: 100%;
width: 100%;
}
}
.cl-upload-space__file-size {
position: absolute;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
}
.cl-upload-space__file-mask {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
i {
font-size: 30px;
color: #67c23a;
}
}
}
&-empty {
display: flex;
align-items: center;
justify-content: center;
margin-top: 100px;
& > div {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border-radius: 6px;
cursor: pointer;
height: 180px;
width: 360px;
i {
font-size: 67px;
color: #c0c4cc;
}
}
}
}
}
</style>