DiKongGanZhiPingTai/src/views/demo/system/mediaLibrary/index.vue

902 lines
28 KiB
Vue

<template>
<PageWrapper dense contentFullHeight fixedHeight contentClass="flex">
<LeftTree ref="childRef" class="w-1/5 xl:w-1/6" @select="handleSelect" />
<div class="w-4/5 xl:w-5/6">
<BasicTable @register="registerTable" :searchInfo="searchInfo">
<template #toolbar>
<span v-for="f in floders" :key="f" class="floderTitle">
<span v-if="f != '全部文件'" style="margin-right: 10px"> / </span>
<span @click="getChildrenByProp(tableData, f)"> {{ f }}</span>
</span>
<div class="floderOtherButton">
<!-- <PermissionBtn @btnEvent="onBtnClicked"></PermissionBtn> -->
<a-button :icon="h(BorderHorizontalOutlined)" @click="openComparisonModal">
变化检测
</a-button>
<a-button :icon="h(PlusOutlined)" type="primary" @click="addFolder">
新建文件夹
</a-button>
<a-button :icon="h(ColumnHeightOutlined)" @click="moveFolderOrFile">移动</a-button>
<a-button :icon="h(DeleteOutlined)" @click="deleteFolderOrFile">删除 </a-button>
<a-button :icon="h(DownloadOutlined)" @click="compressFolderOrFile">压缩</a-button>
<a-radio-group v-model:value="tableType">
<a-radio-button value="table"><BarsOutlined /></a-radio-button>
<a-radio-button value="store"><AppstoreOutlined /></a-radio-button>
</a-radio-group>
</div>
</template>
<template #bodyCell="{ column, record }">
<div v-if="tableType == 'table'">
<template v-if="column.key === 'name'">
<FolderOpenOutlined v-if="record.type == 'folder'" style="font-size: 20px" />
<!-- <PlaySquareTwoTone v-if="record.type == 'video'" style="font-size: 20px" /> -->
<img v-if="record.type == 'img'" :src="record.url" :width="30" :height="20" />
<img
v-if="record.type == 'video'"
:src="record.gifZoomImage"
:width="30"
:height="20"
/>
<FileOutlined v-if="record.type.includes('model')" style="font-size: 20px" />
<div
v-if="record.type.includes('2D')"
style="
position: absolute;
top: 20px;
left: 11px;
font-size: 10px;
color: #ffffff;
background: #000000;
pointer-events: none;
"
>
2D
</div>
<div
v-if="record.type.includes('3D')"
style="
position: absolute;
top: 20px;
left: 11px;
font-size: 10px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
>
3D
</div>
<span
@click="lookRecord(record)"
@mouseover="record.isHovered = true"
@mouseout="record.isHovered = false"
:style="{
textDecoration: record.isHovered ? 'underline' : 'none',
marginLeft: '5px',
}"
>
{{ record.name }}
</span>
</template>
<template v-if="column.key === 'createtime'">
{{ record.createtime ? record.createtime : '-' }}
</template>
<template v-if="column.key === 'size'">
{{ record.size ? record.size : '-' }}
</template>
<template v-if="column.key === 'label'">
<div v-if="record.label && record.label.length > 0">
<a-tag color="success" v-for="la in record.label" :key="la">{{ la }}</a-tag>
</div>
</template>
<template v-if="column.key === 'action'">
<a-button type="text">
<EditOutlined @click="renameRecord(record)" />
</a-button>
<a-button type="text">
<BorderInnerOutlined @click="openPathModal(record)" />
</a-button>
</template>
</div>
</template>
</BasicTable>
<div v-if="tableType == 'store'" class="storeDivsAllChoose">
<a-checkbox v-model:checked="checkNameChecked" @change="changeStore($event, 'allChoose')">
全选
</a-checkbox>
</div>
<div v-if="tableType == 'store'" class="storeDivs">
<div
v-for="record in showTableData"
:key="record.id"
class="storeDiv"
:style="{ background: record.isHovered ? '#D5E8FC' : '#ffffff' }"
@mouseover="record.isHovered = true"
@mouseout="record.isHovered = false"
>
<div style="position: absolute; top: 0px; left: 5px">
<a-checkbox v-model:checked="record.checked" @change="changeStore($event, record)" />
</div>
<FolderOpenOutlined
v-if="record.type == 'folder'"
style="font-size: 40px"
@click="lookRecord(record)"
/>
<img
v-if="record.type == 'img'"
:src="record.url"
:width="60"
:height="40"
@click="lookRecord(record)"
/>
<img v-if="record.type == 'video'" :src="record.gifZoomImage" :width="60" :height="40" />
<FileOutlined
v-if="record.type.includes('model')"
style="font-size: 40px"
@click="lookRecord(record)"
/>
<div
v-if="record.type.includes('2D')"
style="
position: absolute;
top: 52px;
left: 60px;
font-size: 20px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
@click="lookRecord(record)"
>
2D
</div>
<div
v-if="record.type.includes('3D')"
style="
position: absolute;
top: 52px;
left: 60px;
font-size: 20px;
color: #ffffff;
pointer-events: none;
background: #000000;
"
@click="lookRecord(record)"
>
3D
</div>
<span
:style="{
textDecoration: record.isHovered ? 'underline' : 'none',
position: 'absolute',
bottom: '10px',
left: '0px',
width: '100%',
height: '30px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
}"
@click="lookRecord(record)"
>
{{ record.name }}
</span>
</div>
</div>
</div>
<!-- 新建文件夹弹窗 -->
<AddFolderModal @register="addFolderModal" @success="handleSuccess" />
<!-- 移动弹窗 -->
<MoveFileModal @register="moveFileModal" @success="handleSuccess" />
<!-- 压缩弹窗 -->
<CompressFileModal @register="compressFileModal" @success="handleSuccess" />
<!-- 重命名弹窗 -->
<RenameModal @register="renameModal" @success="handleSuccess" />
<!-- 预览弹窗 -->
<a-modal
v-model:open="open"
width="100%"
wrap-class-name="full-modal"
:centered="true"
:closable="false"
:footer="null"
:destroyOnClose="true"
:keyboard="false"
:mask="false"
:maskClosable="false"
>
<Preview
v-if="nowPreviewRecord.type == 'img' || nowPreviewRecord.type == 'video'"
:nowPreviewRecord="nowPreviewRecord"
:previewRecordList="previewRecordList"
@chooseNowPreviewRecord="chooseNowPreviewRecord"
@closeModal="closeModal"
@reloadTable="reload"
/>
</a-modal>
<!-- 变化检测弹窗 -->
<a-modal
v-model:open="comparisonOpen"
width="100%"
wrap-class-name="full-modal"
:centered="true"
:closable="false"
:footer="null"
:destroyOnClose="true"
:keyboard="false"
:mask="false"
:maskClosable="false"
>
<Comparison @closeComparisonModal="closeComparisonModal" />
</a-modal>
<!-- 路径地图弹窗 -->
<a-modal
v-model:open="pathOpen"
width="100%"
wrap-class-name="full-modal"
:centered="true"
:closable="false"
:footer="null"
:destroyOnClose="true"
:keyboard="false"
:mask="false"
:maskClosable="false"
>
<Path :pathRecord="pathRecord" @closePathModal="closePathModal" />
</a-modal>
</PageWrapper>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, h } from 'vue';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { getOrgList, deleteDept } from '@/api/demo/system';
import { PageWrapper } from '@/components/Page';
import { useModal } from '@/components/Modal';
import { useMessage } from '@/hooks/web/useMessage';
import {
EditOutlined,
DeleteOutlined,
BorderHorizontalOutlined,
PlusOutlined,
ColumnHeightOutlined,
DownloadOutlined,
AppstoreOutlined,
BarsOutlined,
FolderOutlined,
FolderOpenOutlined,
EyeOutlined,
PlaySquareTwoTone,
FileOutlined,
BorderInnerOutlined,
} from '@ant-design/icons-vue';
import LeftTree from './LeftTree.vue';
import Preview from './preview/index.vue';
import Comparison from './comparison/index.vue';
import Path from './path/index.vue';
import { AddFolderModal } from './modal/modal';
import { MoveFileModal } from './modal/modal';
import { CompressFileModal } from './modal/modal';
import { RenameModal } from './modal/modal';
import { PermissionBtn } from '@/components/PermissionBtn/index';
import { columns, searchFormSchema } from './modal.data';
import dayjs from 'dayjs';
import { cloneDeep } from 'lodash-es';
const { createConfirm, createMessage } = useMessage();
// 表格数据--------------------------------------------------------------------
const tableData = ref([
{
id: '2',
name: '图片',
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
{
id: '1-2',
name: '南山风景照.jpg',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://img2.baidu.com/it/u=257681495,312745373&fm=253&fmt=auto&app=138&f=JPEG?w=750&h=500',
imgtype: '原片',
taskname: '佛山大火救援项目',
airlineName: '火灾救援勘查航线',
width: 889,
height: 500,
size: '6.2M',
photographFeiji: '救援机1007',
photographNumber: 'GD610',
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lng: 118.033886,
},
{
id: '1-3',
name: '风景图.jpg',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
imgtype: '原片2',
taskname: '佛山大火救援项目2',
airlineName: '火灾救援勘查航线2',
width: 2000,
height: 1334,
size: '1.63M',
photographFeiji: '救援机1008',
photographNumber: 'GD610',
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362825,
lng: 118.033886,
},
{
id: '1-4',
name: '红外照片.jpg',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://pic.rmb.bdstatic.com/bjh/gallery/8c885a0e3cf0647b60548535e2e9ca39.jpeg',
imgtype: '红外照片',
taskname: '佛山大火救援项目-红外照片',
airlineName: '火灾救援勘查航线',
width: 640,
height: 480,
size: '6.2M',
photographFeiji: '救援机1007',
photographNumber: 'GD610',
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362925,
lng: 118.033886,
},
{
id: '1-5',
name: '广角照片.jpg',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://img2.baidu.com/it/u=2490853491,3226002419&fm=253&fmt=auto&app=138&f=JPEG?w=749&h=500',
imgtype: '广角照片',
taskname: '佛山大火救援项目-广角照片',
airlineName: '火灾救援勘查航线',
width: 749,
height: 500,
size: '6.2M',
photographFeiji: '救援机1007',
photographNumber: 'GD610',
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362625,
lng: 118.033286,
},
{
id: '1-6',
name: '变焦照片.jpg',
createtime: '2020-10-22 17:33:22',
type: 'img',
url: 'https://img2.baidu.com/it/u=3778652155,475195343&fm=253&fmt=auto&app=138&f=PNG?w=500&h=518',
imgtype: '变焦照片',
taskname: '佛山大火救援项目-变焦照片',
airlineName: '火灾救援勘查航线',
width: 500,
height: 518,
size: '6.2M',
photographFeiji: '救援机1007',
photographNumber: 'GD610',
photographMan: 'zachzhou',
photographTime: '2020-10-22 00:00:00',
label: ['人', '车'],
lat: 35.362725,
lng: 118.033086,
},
],
},
{
id: '3',
name: '视频',
createtime: '2025-03-24 18:13:17',
type: 'folder',
children: [
// {
// id: '3-4',
// name: 'XZD153狼窝沟西南',
// createtime: '',
// type: 'video',
// url: '74b95e6575d741489b9a9061bb646467',
// manufacturer: '海康',
// },
{
id: '3-5',
name: '费县马庄镇陈家鱼后村南斜坡后村',
createtime: '',
type: 'video',
url: 'http://111.36.45.20:18000/flv/hls/H-dcb1ea7388588111.flv',
gifZoomImage: 'https://img.soogif.com/mrGHcO3xjFJnJ986TeL9oAr2BYfPIaM7.gif',
manufacturer: '腾讯',
},
{
id: '3-8',
name: 'movie',
createtime: '',
type: 'video',
url: 'https://www.runoob.com/try/demo_source/mov_bbb.mp4',
gifZoomImage: 'https://img.soogif.com/mrGHcO3xjFJnJ986TeL9oAr2BYfPIaM7.gif',
manufacturer: '腾讯',
},
// {
// id: '3-6',
// name: '可落',
// createtime: '',
// type: 'video',
// url: '8H03AA1PAG8D9BF',
// manufacturer: '乐橙',
// },
// {
// id: '3-7',
// name: '费县薛庄镇东张林村村南可见光',
// createtime: '',
// type: 'video',
// url: '37130100181328000392',
// manufacturer: '青犀',
// },
],
},
// {
// id: '4',
// name: '模型',
// createtime: '2025-03-24 18:13:17',
// type: 'folder',
// children: [
// {
// id: '4-1',
// name: '天空之城 二维模型',
// createtime: '2025-03-24 18:13:17',
// type: 'model2D',
// url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
// boundary: {
// end_lat: 22.57965964566081,
// end_lng: 113.93899440765381,
// max_level: 23,
// min_level: 12,
// start_lat: 22.578193485606185,
// start_lng: 113.93697738647461,
// },
// },
// {
// id: '4-2',
// name: '天空之城 三维模型',
// createtime: '2025-03-24 18:13:17',
// type: 'model3D',
// url: 'https://m.tuniucdn.com/fb2/t1/G5/M00/44/52/Cii-s1soezyIF2UxABn76u-yKl8AAIwBgB34jAAGfwC3020871',
// boundary: {
// end_lat: 22.57965964566081,
// end_lng: 113.93899440765381,
// max_level: 23,
// min_level: 12,
// start_lat: 22.578193485606185,
// start_lng: 113.93697738647461,
// },
// },
// ],
// },
]);
// 展示数据
const showTableData = ref(cloneDeep(tableData.value));
// 目录
const floders = ref(['全部文件']);
// 表格还是文件夹
const tableType = ref('table');
const tableHeight: any = ref(0);
watch(
() => tableType.value,
(newval) => {
// 表格
const containers = document.querySelectorAll('.ant-table-container');
if (newval) {
tableHeight.value = containers[0] ? containers[0]?.scrollHeight : 0;
} else {
tableHeight.value = 0;
}
if (containers) {
containers.forEach((container) => {
container.style.display = tableType.value === 'table' ? 'block' : 'none';
});
}
// 分页
const paginations = document.querySelectorAll('.ant-pagination');
if (paginations) {
paginations.forEach((pagination) => {
pagination.style.display = tableType.value === 'table' ? 'block' : 'none';
});
}
// 选中
// 框架
if (tableType.value === 'store') {
let selectRowsIdArray = getSelectRows().map((item) => item.id);
showTableData.value.forEach((item) => {
if (selectRowsIdArray.includes(item.id)) {
item.checked = true;
changeStore(
{
target: {
checked: true,
},
},
item,
);
}
});
} else {
// 表格
// let showTableDataIds = showTableData.value.map((item) => item.id);
let selectRowsIdArray = showTableData.value.filter((item) => item.checked);
// selectRowsIdArray.forEach((item) => {
// if (showTableDataIds.includes(item.id)) {
// let filterTemp = showTableData.value.filter((f) => f.id == item.id);
// console.log(filterTemp);
// // filterTemp.forEach((temp) => {
// // if (temp.children) {
// // temp.children.forEach((t) => {
// // selectRowsIdArray.push(t);
// // });
// // }
// // });
// }
// });
setSelectedRows(selectRowsIdArray);
}
},
);
// 目录跳跃
function getChildrenByProp(children, name) {
if (name == '全部文件') {
showTableData.value = cloneDeep(tableData.value);
floders.value = ['全部文件'];
clearSelectedRowKeys();
return;
}
for (const node of children) {
if (node.name === name) {
showTableData.value = cloneDeep(node.children);
const index = floders.value.indexOf(name);
floders.value = index === -1 ? [] : floders.value.slice(0, index + 1);
clearSelectedRowKeys();
return node.children || [];
}
if (node.children && node.children.length > 0) {
const result = getChildrenByProp(node.children, node.name);
if (result) {
const index = floders.value.indexOf(name);
floders.value = index === -1 ? [] : floders.value.slice(0, index + 1);
clearSelectedRowKeys();
return result;
}
}
}
return null;
}
// 单选框选择
const checkNameChecked = ref(false);
// 表格or文件夹切换
function changeStore(e, record) {
if (typeof record == 'string') {
if (e.target.checked) {
checkNameChecked.value = true;
showTableData.value = showTableData.value.map((item) => ({
...item,
checked: true,
}));
} else {
checkNameChecked.value = false;
showTableData.value = showTableData.value.map((item) => ({
...item,
checked: false,
}));
}
} else {
if (showTableData.value.filter((item) => item.checked).length == showTableData.value.length) {
checkNameChecked.value = true;
} else {
checkNameChecked.value = false;
}
}
}
// 表格
const searchInfo = reactive<Recordable>({});
const searchParams = ref();
const [registerTable, { reload, getSelectRows, setSelectedRows, clearSelectedRowKeys }] =
useTable({
// api: getOrgList,
// title: '全部文件',
dataSource: showTableData,
rowKey: 'id',
columns,
formConfig: {
labelWidth: 120,
schemas: searchFormSchema,
},
rowSelection: {
type: 'checkbox',
},
isTreeTable: false,
striped: false,
bordered: false,
inset: false,
tableSetting: {
redo: false,
size: false,
setting: false,
},
useSearchForm: true,
showIndexColumn: false,
showTableSetting: true,
handleSearchInfoFn(info) {
console.log(info);
console.log(searchInfo.value);
searchParams.value = info;
return info;
},
beforeFetch: (data) => {
// 接口请求前 参数处理
var temp = {
startTime: dayjs(data.startTime).startOf('month').format('YYYY-MM-DD'),
endTime: dayjs(data.endTime).endOf('month').format('YYYY-MM-DD HH:mm:ss'),
};
return temp;
},
afterFetch: (res) => {
console.log(res);
},
});
// 弹窗----------------------------------------------------------------------
// 左侧目录选择
function handleSelect(orgId = '') {
searchInfo.orgId = orgId;
reload();
}
const childRef = ref<any>();
function handleSuccess() {
clearSelectedRowKeys();
childRef.value.fetch();
reload();
}
// 新建文件夹
const [addFolderModal, { openModal: openAddFolderModal }] = useModal();
// 移动
const [moveFileModal, { openModal: openMoveFileModal }] = useModal();
// 压缩
const [compressFileModal, { openModal: openCompressFileModal }] = useModal();
// 重命名
const [renameModal, { openModal: openRenameModal }] = useModal();
// 新建文件夹
function addFolder() {
let rows = getSelectRows();
let record: any = null;
if (rows.length == 1) {
if (rows[0].type == 'folder') {
record = rows[0];
} else {
record = findParentIdById(tableData.value, rows[0].id);
}
}
openAddFolderModal(true, {
record,
});
}
// 移动文件
function moveFolderOrFile() {
let rows = getSelectRows();
if (rows.length > 0) {
const record = rows;
openMoveFileModal(true, {
tableData: tableData.value,
record,
});
} else {
return createMessage.warn('请选择一个或者多个文件/文件夹进行移动');
}
}
// 删除
async function deleteFolderOrFile() {
let rows = getSelectRows();
if (rows.length == 0) {
return createMessage.warn('请选择一个或者多个文件/文件夹进行删除');
}
const query = rows.map((item) => item.id);
createConfirm({
iconType: 'info',
title: '删除',
content: '确定要删除当前部门吗',
onOk: async () => {
// const data = await deleteDept(query);
const data = null;
if (data) {
handleSuccess();
createMessage.success('删除成功');
} else {
createMessage.error('删除失败');
}
},
});
}
// 压缩
function compressFolderOrFile() {
let rows = getSelectRows();
if (rows.length > 0) {
const record = rows;
openCompressFileModal(true, {
tableData: tableData.value,
record,
});
} else {
return createMessage.warn('请选择一个或者多个文件/文件夹压缩');
}
}
// 重命名
function renameRecord(record) {
openRenameModal(true, {
record,
});
}
// 获取父元素
function findParentIdById(tree, targetId) {
function recurse(nodes) {
for (let node of nodes) {
if (node.children) {
for (let child of node.children) {
if (child.id === targetId) {
return node;
}
const found = recurse([child]);
if (found) return found;
}
}
}
return null;
}
return recurse(tree);
}
// 查看弹窗----------------------------------------------------------------------
const open = ref(false);
// 查看
// 目前查看项
const nowPreviewRecord: any = ref();
// 目前查看项的同级列表
const previewRecordList: any = ref([]);
// 查看
function lookRecord(record) {
if (record.type == 'folder') {
showTableData.value = record.children;
floders.value.push(record.name);
} else {
open.value = true;
nowPreviewRecord.value = record;
previewRecordList.value = findParentIdById(tableData.value, record.id)?.children;
if (!previewRecordList.value) {
previewRecordList.value = tableData.value;
}
}
}
// 图片选择
function chooseNowPreviewRecord(value) {
nowPreviewRecord.value = value;
}
// 关闭
function closeModal() {
document.body.style.cursor = 'auto';
open.value = false;
}
// 变化检测弹窗----------------------------------------------------------------------
const comparisonOpen = ref(false);
// 打开变化检测弹窗
function openComparisonModal() {
comparisonOpen.value = true;
}
// 关闭变化检测弹窗
function closeComparisonModal() {
comparisonOpen.value = false;
}
// 路径地图弹窗----------------------------------------------------------------------
const pathOpen = ref(false);
// 打开变化检测弹窗
const pathRecord = ref({});
function openPathModal(record) {
console.log('pathRecord',record)
pathRecord.value = record;
pathOpen.value = true;
}
// 关闭变化检测弹窗
function closePathModal() {
pathRecord.value = {};
pathOpen.value = false;
}
</script>
<style lang="less" scoped>
.floderTitle {
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
cursor: pointer;
}
::v-deep .vben-basic-table-header__toolbar {
display: flex !important;
justify-content: flex-start !important;
}
.floderOtherButton {
position: absolute;
right: 0px;
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
::v-deep .floderTitle .ant-btn {
padding-left: 0px !important;
padding-right: 0px !important;
}
::v-deep .floderTitle .ant-btn-text {
padding-left: 0px !important;
padding-right: 0px !important;
}
::v-deep .ant-table-row-expand-icon {
display: none !important;
}
::v-deep .ant-table-row-expand-icon-collapsed {
display: none !important;
}
.storeDivsAllChoose {
width: 100%;
height: 40px;
margin: 0px 0px 16px 16px;
padding-left: 16px;
background: #ffffff;
display: flex;
align-items: center;
justify-content: flex-start;
}
.storeDivs {
display: flex;
flex-wrap: wrap;
width: 100%;
height: 566px;
background: #ffffff;
margin: 0px 16px 16px 16px;
// gap: 20px;
.storeDiv {
position: relative;
width: 150px;
height: 120px;
outline: 1px solid #000000;
margin: 16px;
display: flex;
align-items: center;
justify-content: center;
}
}
::v-deep .vben-basic-table {
height: fit-content !important;
}
</style>