664 lines
16 KiB
Vue
664 lines
16 KiB
Vue
<template>
|
|
<el-dialog title="选择文件" v-model="visible" :width="900" destroy-on-close @closed="$emit('closed')"
|
|
append-to-body>
|
|
<div class="pi-file-select">
|
|
<div class="pi-file-select__side" v-loading="menuLoading">
|
|
<div class="pi-file-select__side-menu">
|
|
<el-tree ref="menuRef" class="menu" :data="menu" :node-key="treeProps.key" :props="treeProps"
|
|
:expand-on-click-node="false" check-strictly show-checkbox
|
|
:current-node-key="menu.length>0?menu[0][treeProps.key]:''" highlight-current
|
|
@node-click="groupClick" :check-on-click-leaf="false">
|
|
<template #default="{ node, data }">
|
|
<span class="custom-tree-node el-tree-node__label">
|
|
<span class="label">
|
|
{{ node.label }}
|
|
</span>
|
|
<span class="do">
|
|
<el-icon v-if="data.category_id != 0" @click.stop="editCategory(data)"><el-icon-edit/></el-icon>
|
|
</span>
|
|
</span>
|
|
</template>
|
|
</el-tree>
|
|
</div>
|
|
<div class="pi-file-select__side-msg">
|
|
<el-button type="primary" size="small" icon="el-icon-plus" @click="addCategory"></el-button>
|
|
<el-button type="danger" size="small" plain icon="el-icon-delete" @click="delCategory"></el-button>
|
|
</div>
|
|
</div>
|
|
<div class="pi-file-select__files" v-loading="listLoading">
|
|
<div class="pi-file-select__top">
|
|
<div class="upload" v-if="!hideUpload">
|
|
<el-upload class="pi-file-select__upload" action="" multiple :show-file-list="false"
|
|
:accept="accept" :on-change="uploadChange" :before-upload="uploadBefore"
|
|
:on-progress="uploadProcess" :on-success="uploadSuccess" :on-error="uploadError"
|
|
:http-request="uploadRequest">
|
|
<el-button type="primary" icon="el-icon-upload">本地上传</el-button>
|
|
</el-upload>
|
|
<el-button type="danger" icon="el-icon-delete" @click="delFile">删除</el-button>
|
|
<el-button type="primary" icon="el-icon-rank" @click="moveFile">移动</el-button>
|
|
</div>
|
|
<div class="keyword">
|
|
<el-input v-model="file_name" style="width: 220px;" prefix-icon="el-icon-search"
|
|
placeholder="文件名搜索" clearable @keyup.enter="search" @input="search"
|
|
@clear="search"></el-input>
|
|
</div>
|
|
</div>
|
|
<div class="pi-file-select__list">
|
|
<el-scrollbar ref="scrollbarRef">
|
|
<el-empty v-if="fileList.length==0 && data.length==0" description="无数据"
|
|
:image-size="80"></el-empty>
|
|
<div v-for="(file, index) in fileList" :key="index" class="pi-file-select__item">
|
|
<div class="pi-file-select__item__file">
|
|
<div class="pi-file-select__item__upload">
|
|
<el-progress type="circle" :percentage="file.progress" :width="70"></el-progress>
|
|
</div>
|
|
<el-image :src="file.tempImg" fit="contain"></el-image>
|
|
</div>
|
|
<p>{{ file.name }}</p>
|
|
</div>
|
|
<div v-for="item in data" :key="item[fileProps.id]" class="pi-file-select__item"
|
|
:class="{active: active(item) }" @click="select(item)">
|
|
<div class="pi-file-select__item__file">
|
|
<div class="pi-file-select__item__checkbox" v-if="multiple">
|
|
<el-icon>
|
|
<el-icon-check/>
|
|
</el-icon>
|
|
</div>
|
|
<div class="pi-file-select__item__select" v-else>
|
|
<el-icon>
|
|
<el-icon-check/>
|
|
</el-icon>
|
|
</div>
|
|
<div class="pi-file-select__item__box"></div>
|
|
<el-image v-if="obj.isImg(item[fileProps.url])"
|
|
:src="config.API_URL + '/' + item[fileProps.url]" fit="contain"
|
|
lazy></el-image>
|
|
<div v-else-if="obj.isVideo(item[fileProps.url])" class="item-video">
|
|
<el-icon size="32px">
|
|
<component :is="'pi-icon-video'"/>
|
|
</el-icon>
|
|
</div>
|
|
<div v-else class="item-file item-file-doc">
|
|
<el-icon size="32px">
|
|
<component :is="'pi-icon-task'"/>
|
|
</el-icon>
|
|
</div>
|
|
</div>
|
|
<p :title="item[fileProps.fileName]">{{ item[fileProps.fileName] }}</p>
|
|
</div>
|
|
</el-scrollbar>
|
|
</div>
|
|
<div class="pi-file-select__pagination">
|
|
<el-pagination size="small" background layout="prev, pager, next" :total="total"
|
|
:page-size="pageSize"
|
|
v-model:currentPage="currentPage" @current-change="reload"></el-pagination>
|
|
</div>
|
|
<div class="pi-file-select__do">
|
|
<div><span v-if="multiple">已选择 <b>{{ value.length }}</b> / <b>{{ max }}</b> 项</span></div>
|
|
<el-button type="primary" :disabled="value.length<=0" @click="submit">确 定</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<save-dialog v-if="dialog.save" ref="saveRef" @success="getMenu" @closed="dialog.save=false"></save-dialog>
|
|
<move-dialog v-if="dialog.move" ref="moveRef" @success="moveSuccess"
|
|
@closed="dialog.move=false"></move-dialog>
|
|
</el-dialog>
|
|
</template>
|
|
|
|
<script setup>
|
|
import assetConfig from "@/config/asset"
|
|
import saveDialog from "./save.vue";
|
|
import moveDialog from "./move.vue";
|
|
import * as imageConversion from 'image-conversion';
|
|
import {ref, onMounted, nextTick, getCurrentInstance} from "vue"
|
|
import tools from "@/utils/tools"
|
|
import config from "@/config"
|
|
|
|
defineExpose({
|
|
open
|
|
})
|
|
|
|
const emit = defineEmits(['success', 'closed'])
|
|
const props = defineProps({
|
|
hideUpload: {type: Boolean, default: false},
|
|
multiple: {type: Boolean, default: false},
|
|
max: {type: Number, default: assetConfig.max},
|
|
maxSize: {type: Number, default: assetConfig.maxSize},
|
|
type: {type: String, default: "image"}
|
|
})
|
|
const {proxy} = getCurrentInstance()
|
|
const saveRef = ref(null)
|
|
const moveRef = ref(null)
|
|
const scrollbarRef = ref(null)
|
|
const menuRef = ref(null)
|
|
|
|
let dialog = ref({
|
|
save: false,
|
|
move: false
|
|
})
|
|
let file_name = ref(null)
|
|
let pageSize = ref(20)
|
|
let total = ref(0)
|
|
let currentPage = ref(1)
|
|
let data = ref([])
|
|
let menu = ref([])
|
|
let category_id = ref(0)
|
|
let value = ref(props.multiple ? [] : '')
|
|
let fileList = ref([])
|
|
let accept = ref(assetConfig.fileType(props.type)['accept'])
|
|
let listLoading = ref(false)
|
|
let menuLoading = ref(false)
|
|
let treeProps = ref(assetConfig.menuProps)
|
|
let fileProps = ref(assetConfig.fileProps)
|
|
let visible = ref(false)
|
|
let obj = ref(assetConfig)
|
|
|
|
onMounted(() => {
|
|
getMenu()
|
|
getData()
|
|
})
|
|
|
|
async function open() {
|
|
visible.value = true;
|
|
}
|
|
|
|
async function getMenu() {
|
|
menuLoading.value = true
|
|
const res = await assetConfig.menuListObj({type: props.type});
|
|
menu.value = tools.makeTreeData(res.data, 0, "category_id")
|
|
menu.value.unshift({
|
|
'category_id': 0,
|
|
'category_name': '未分类',
|
|
'parent_id': 0
|
|
})
|
|
menuLoading.value = false
|
|
}
|
|
|
|
async function getData() {
|
|
listLoading.value = true
|
|
const reqData = {
|
|
[assetConfig.request.menuKey]: category_id.value,
|
|
[assetConfig.request.page]: currentPage.value,
|
|
[assetConfig.request.pageSize]: pageSize.value,
|
|
};
|
|
reqData.type = props.type
|
|
reqData.ori_name = file_name.value
|
|
const res = await assetConfig.fileListObj(reqData);
|
|
const parseData = assetConfig.listParseData(res);
|
|
data.value = parseData.rows
|
|
total.value = parseData.total
|
|
listLoading.value = false
|
|
}
|
|
|
|
function groupClick(data) {
|
|
category_id.value = data.category_id
|
|
currentPage.value = 1
|
|
file_name.value = null
|
|
getData()
|
|
}
|
|
|
|
function reload() {
|
|
getData()
|
|
}
|
|
|
|
function search() {
|
|
currentPage.value = 1
|
|
getData()
|
|
}
|
|
|
|
function active(item) {
|
|
if (props.multiple) {
|
|
let choice = false;
|
|
value.value.forEach(i => {
|
|
if (i[assetConfig.fileProps.id] === item[assetConfig.fileProps.id]) {
|
|
choice = true
|
|
}
|
|
})
|
|
return choice
|
|
} else {
|
|
return value.value[assetConfig.fileProps.id] === item[assetConfig.fileProps.id]
|
|
}
|
|
}
|
|
|
|
function select(item) {
|
|
if (props.multiple) {
|
|
if (value.value.length >= props.max) {
|
|
proxy.$message.error("选择文件过多")
|
|
return;
|
|
}
|
|
if (active(item)) {
|
|
value.value.splice(value.value.findIndex(f => f[assetConfig.fileProps.id] === item[assetConfig.fileProps.id]), 1)
|
|
} else {
|
|
value.value.push(item)
|
|
}
|
|
} else {
|
|
if (value.value[assetConfig.fileProps.id] === item[assetConfig.fileProps.id]) {
|
|
value.value = ''
|
|
} else {
|
|
value.value = item
|
|
}
|
|
}
|
|
}
|
|
|
|
function submit() {
|
|
if (props.multiple) {
|
|
emit('success', value.value.map(i => i[assetConfig.fileProps.url]));
|
|
} else {
|
|
emit('success', value.value[assetConfig.fileProps.url]);
|
|
}
|
|
visible.value = false
|
|
}
|
|
|
|
async function delFile() {
|
|
const _value = JSON.parse(JSON.stringify(value.value))
|
|
const ids = props.multiple ? _value.map(i => i[assetConfig.fileProps.id]) : [_value[assetConfig.fileProps.id]];
|
|
if (!ids) return;
|
|
const res = await assetConfig.delFileObj({ids: ids});
|
|
// 删除文件成功,取消选择
|
|
value.value = props.multiple ? [] : ''
|
|
proxy.$message.success(res.msg)
|
|
getData()
|
|
}
|
|
|
|
function moveFile() {
|
|
dialog.value.move = true
|
|
nextTick(() => {
|
|
moveRef.value.open(menu.value)
|
|
})
|
|
}
|
|
|
|
async function moveSuccess(category_id) {
|
|
const _value = JSON.parse(JSON.stringify(value.value))
|
|
const ids = props.multiple ? _value.map(i => i[assetConfig.fileProps.id]) : [_value[assetConfig.fileProps.id]];
|
|
if (!ids) return;
|
|
const res = await assetConfig.moveFileObj({ids: ids, category_id: category_id});
|
|
proxy.$message.success(res.msg)
|
|
// 清空选中值
|
|
value.value = props.multiple ? [] : ''
|
|
getData()
|
|
}
|
|
|
|
function uploadChange(file, _fileList) {
|
|
file.tempImg = URL.createObjectURL(file.raw);
|
|
fileList.value = _fileList
|
|
}
|
|
|
|
function uploadBefore(file) {
|
|
const fileSize = file.size / 1024;
|
|
if (assetConfig.isImg(file.name) && fileSize > 800) {
|
|
return new Promise((resolve) => {
|
|
imageConversion.compressAccurately(file, 800).then((res) => {
|
|
resolve(res);
|
|
});
|
|
});
|
|
} else {
|
|
if (fileSize > props.maxSize * 1024) {
|
|
proxy.$message.warning(`上传文件大小不能超过 ${props.maxSize}MB!`);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
function uploadRequest(param) {
|
|
// 获取文件类型
|
|
const _type = assetConfig.getType(param.file.name)
|
|
if (props.type !== "" && proxy.type !== _type) {
|
|
proxy.$message.warning(`不允许的文件类型!`);
|
|
clearFiles(param.file)
|
|
return false
|
|
}
|
|
const data = new FormData();
|
|
data.append("file", param.file);
|
|
if (category_id.value) {
|
|
data.append(assetConfig.request.menuKey, category_id.value);
|
|
}
|
|
assetConfig.uploadObj(data, {
|
|
onUploadProgress: e => {
|
|
param.onProgress(e)
|
|
}
|
|
}).then(res => {
|
|
param.onSuccess(res)
|
|
}).catch(err => {
|
|
param.onError(err)
|
|
})
|
|
}
|
|
|
|
function uploadProcess(event, file) {
|
|
file.progress = Number((event.loaded / event.total * 100).toFixed(2))
|
|
}
|
|
|
|
function clearFiles(file) {
|
|
fileList.value.splice(fileList.value.findIndex(f => f.uid == file.uid), 1)
|
|
}
|
|
|
|
function uploadSuccess(res, file) {
|
|
fileList.value.splice(fileList.value.findIndex(f => f.uid == file.uid), 1)
|
|
var response = assetConfig.uploadParseData(res);
|
|
data.value.unshift(response)
|
|
if (!props.multiple) {
|
|
value.value = response
|
|
}
|
|
}
|
|
|
|
function uploadError(err) {
|
|
proxy.$notify.error({
|
|
title: '上传文件错误',
|
|
message: err
|
|
})
|
|
}
|
|
|
|
function editCategory(data) {
|
|
dialog.value.save = true
|
|
nextTick(() => {
|
|
saveRef.value.setType(props.type).setMenu(menu.value).open('edit', data)
|
|
})
|
|
}
|
|
|
|
function addCategory() {
|
|
dialog.value.save = true
|
|
nextTick(() => {
|
|
saveRef.value.setType(props.type).setMenu(menu.value).open('add', null, menuRef.value.getCurrentKey())
|
|
})
|
|
}
|
|
|
|
async function delCategory() {
|
|
var CheckedNodes = menuRef.value.getCheckedNodes()
|
|
if (CheckedNodes.length === 0) {
|
|
proxy.$message.warning("请选择需要删除的项")
|
|
return false;
|
|
}
|
|
var confirm = await proxy.$confirm('确认删除已选择的菜单吗?', '提示', {
|
|
type: 'warning',
|
|
confirmButtonText: '删除',
|
|
cancelButtonText: '取消',
|
|
confirmButtonClass: 'el-button--danger'
|
|
}).catch(() => {
|
|
})
|
|
if (confirm !== 'confirm') {
|
|
return false
|
|
}
|
|
menuLoading.value = true
|
|
var res = await assetConfig.menuDelObj({ids: CheckedNodes.map(item => item.category_id)})
|
|
menuLoading.value = false
|
|
proxy.$message.success(res.msg)
|
|
CheckedNodes.forEach(item => {
|
|
var node = menuRef.value.getNode(item)
|
|
if (node == null) {
|
|
reload()
|
|
}
|
|
menuRef.value.remove(item)
|
|
})
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
.pi-file-select {
|
|
display: flex;
|
|
}
|
|
|
|
.pi-file-select__files {
|
|
flex: 1;
|
|
}
|
|
|
|
.pi-file-select__list {
|
|
height: 400px;
|
|
}
|
|
|
|
.pi-file-select__item {
|
|
display: inline-block;
|
|
float: left;
|
|
margin: 0 15px 25px 0;
|
|
width: 110px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.pi-file-select__item__file {
|
|
width: 110px;
|
|
height: 110px;
|
|
position: relative;
|
|
}
|
|
|
|
.pi-file-select__item__file .el-image {
|
|
width: 110px;
|
|
height: 110px;
|
|
}
|
|
|
|
.pi-file-select__item__box {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
border: 2px solid var(--el-color-success);
|
|
z-index: 1;
|
|
display: none;
|
|
}
|
|
|
|
.pi-file-select__item__box::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
background: var(--el-color-success);
|
|
opacity: 0.2;
|
|
display: none;
|
|
}
|
|
|
|
.pi-file-select__item:hover .pi-file-select__item__box {
|
|
display: block;
|
|
}
|
|
|
|
.pi-file-select__item.active .pi-file-select__item__box {
|
|
display: block;
|
|
}
|
|
|
|
.pi-file-select__item.active .pi-file-select__item__box::before {
|
|
display: block;
|
|
}
|
|
|
|
.pi-file-select__item p {
|
|
margin-top: 10px;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
overflow: hidden;
|
|
-webkit-text-overflow: ellipsis;
|
|
text-align: center;
|
|
}
|
|
|
|
.pi-file-select__item__checkbox {
|
|
position: absolute;
|
|
width: 20px;
|
|
height: 20px;
|
|
top: 7px;
|
|
right: 7px;
|
|
z-index: 2;
|
|
background: rgba(0, 0, 0, 0.2);
|
|
border: 1px solid #fff;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pi-file-select__item__checkbox i {
|
|
font-size: 14px;
|
|
color: #fff;
|
|
font-weight: bold;
|
|
display: none;
|
|
}
|
|
|
|
.pi-file-select__item__select {
|
|
position: absolute;
|
|
width: 20px;
|
|
height: 20px;
|
|
top: 0px;
|
|
right: 0px;
|
|
z-index: 2;
|
|
background: var(--el-color-success);
|
|
display: none;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pi-file-select__item__select i {
|
|
font-size: 14px;
|
|
color: #fff;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.pi-file-select__item.active .pi-file-select__item__checkbox {
|
|
background: var(--el-color-success);
|
|
}
|
|
|
|
.pi-file-select__item.active .pi-file-select__item__checkbox i {
|
|
display: block;
|
|
}
|
|
|
|
.pi-file-select__item.active .pi-file-select__item__select {
|
|
display: flex;
|
|
}
|
|
|
|
.pi-file-select__item__file .item-file {
|
|
width: 110px;
|
|
height: 110px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pi-file-select__item__file .item-file i {
|
|
font-size: 40px;
|
|
}
|
|
|
|
.pi-file-select__item__file .item-file.item-file-doc {
|
|
color: #409eff;
|
|
}
|
|
|
|
.pi-file-select__item .item-video {
|
|
width: 110px;
|
|
height: 110px;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pi-file-select__item .item-video video {
|
|
width: 110px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.pi-file-select__item__upload {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
z-index: 1;
|
|
background: rgba(255, 255, 255, 0.7);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.pi-file-select__side {
|
|
width: 200px;
|
|
margin-right: 15px;
|
|
border-right: 1px solid rgba(128, 128, 128, 0.2);
|
|
display: flex;
|
|
flex-flow: column;
|
|
}
|
|
|
|
.pi-file-select__side-menu {
|
|
flex: 1;
|
|
}
|
|
|
|
.pi-file-select__side-msg {
|
|
height: 32px;
|
|
line-height: 32px;
|
|
}
|
|
|
|
.pi-file-select__top {
|
|
margin-bottom: 15px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.pi-file-select__upload {
|
|
display: inline-block;
|
|
}
|
|
|
|
.pi-file-select__top .tips {
|
|
font-size: 12px;
|
|
margin-left: 10px;
|
|
color: #999;
|
|
}
|
|
|
|
.pi-file-select__top .tips i {
|
|
font-size: 14px;
|
|
margin-right: 5px;
|
|
position: relative;
|
|
bottom: -0.125em;
|
|
}
|
|
|
|
.pi-file-select__pagination {
|
|
margin: 15px 0;
|
|
}
|
|
|
|
.pi-file-select__do {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.custom-tree-node {
|
|
display: flex;
|
|
flex: 1;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
font-size: 14px;
|
|
padding-right: 24px;
|
|
height: 100%;
|
|
}
|
|
|
|
.custom-tree-node .label {
|
|
display: flex;
|
|
align-items: center;;
|
|
height: 100%;
|
|
}
|
|
|
|
.custom-tree-node .label .el-tag {
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.custom-tree-node .do {
|
|
display: none;
|
|
}
|
|
|
|
.custom-tree-node .do i {
|
|
margin-left: 5px;
|
|
color: #999;
|
|
}
|
|
|
|
.custom-tree-node .do i:hover {
|
|
color: #333;
|
|
}
|
|
|
|
.custom-tree-node:hover .do {
|
|
display: inline-block;
|
|
}
|
|
|
|
:deep(.el-upload) {
|
|
display: block;
|
|
margin-right: 12px;
|
|
}
|
|
</style>
|