mirror of
https://github.com/cool-team-official/cool-admin-vue.git
synced 2024-11-02 06:33:40 +08:00
722 lines
14 KiB
Vue
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>
|