<script lang="ts" setup>
|
import { computed, onMounted, reactive, ref, toRefs, watch } from 'vue';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { createImgPreview, ScrollContainer } from '@jnpf/ui';
|
import { BasicForm, useForm } from '@jnpf/ui/form';
|
import { useModal } from '@jnpf/ui/modal';
|
import { BasicVxeTable, useVxeTable } from '@jnpf/ui/vxeTable';
|
import { downloadByUrl, toFileSize } from '@jnpf/utils';
|
|
import { Breadcrumb, BreadcrumbItem } from 'ant-design-vue';
|
|
import {
|
batchDeleteDocument,
|
download,
|
getAllList,
|
getShareOutList,
|
getShareTomeList,
|
getTrashList,
|
shareCancel,
|
trashDelete,
|
trashRecovery,
|
} from '#/api/teamwork/document';
|
import audioImg from '#/assets/images/document/audio.png';
|
import blankImg from '#/assets/images/document/blank.png';
|
import codeImg from '#/assets/images/document/code.png';
|
import excelImg from '#/assets/images/document/excel.png';
|
import folderImg from '#/assets/images/document/folder.png';
|
import imageImg from '#/assets/images/document/image.png';
|
import pdfImg from '#/assets/images/document/pdf.png';
|
import pptImg from '#/assets/images/document/ppt.png';
|
import rarImg from '#/assets/images/document/rar.png';
|
import txtImg from '#/assets/images/document/txt.png';
|
import wordImg from '#/assets/images/document/word.png';
|
import { $t } from '#/locales';
|
import { getAuthMediaUrl } from '#/utils/jnpf';
|
|
import FileUploader from './FileUploader.vue';
|
import FolderTree from './FolderTree.vue';
|
import Form from './Form.vue';
|
import Preview from './Preview.vue';
|
import UserBox from './UserBox.vue';
|
|
interface State {
|
activeKey: string;
|
levelList: any[];
|
showMode: number;
|
selectedRowKeys: string[];
|
searchInfo: any;
|
list: any[];
|
loading: boolean;
|
isIndeterminate: boolean;
|
checkAll: boolean;
|
}
|
|
defineOptions({ name: 'TeamworkDocument' });
|
|
const leftList = [
|
{ id: 'all', fullName: '我的文档', icon: 'icon-ym icon-ym-folder' },
|
{ id: 'shareOut', fullName: '我的共享', icon: 'icon-ym icon-ym-share-filled' },
|
{ id: 'shareTome', fullName: '共享给我', icon: 'icon-ym icon-ym-shareMe-filled' },
|
{ id: 'trash', fullName: '回收站', icon: 'icon-ym icon-ym-trash-filled' },
|
];
|
const allColumns = [
|
{ title: '文件名', dataIndex: 'fullName', minWidth: 100, slots: { default: 'fullName' } },
|
{ title: '', dataIndex: 'isShare', width: 35, slots: { default: 'isShare' } },
|
{ title: '大小', dataIndex: 'fileSize', width: 90, slots: { default: 'fileSize' } },
|
{ title: '创建日期', dataIndex: 'creatorTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
|
];
|
const shareOutColumns = [
|
{ title: '文件名', dataIndex: 'fullName', minWidth: 100, slots: { default: 'fullName' } },
|
{ title: '大小', dataIndex: 'fileSize', width: 90, slots: { default: 'fileSize' } },
|
{ title: '共享日期', dataIndex: 'shareTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
|
{ title: '操作', dataIndex: 'action', width: 50, slots: { default: 'action' } },
|
];
|
const shareTomeColumns = [
|
{ title: '文件名', dataIndex: 'fullName', minWidth: 100, slots: { default: 'fullName' } },
|
{ title: '大小', dataIndex: 'fileSize', width: 90, slots: { default: 'fileSize' } },
|
{ title: '共享日期', dataIndex: 'shareTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
|
{ title: '共享人员', dataIndex: 'creatorUserId', width: 120 },
|
];
|
const trashColumns = [
|
{ title: '文件名', dataIndex: 'fullName', minWidth: 100, slots: { default: 'fullName' } },
|
{ title: '大小', dataIndex: 'fileSize', width: 90, slots: { default: 'fileSize' } },
|
{ title: '删除日期', dataIndex: 'deleteTime', width: 150, format: 'date|YYYY-MM-DD HH:mm:ss' },
|
];
|
|
const wordTypeList = ['doc', 'docx'];
|
const excelTypeList = ['xls', 'xlsx'];
|
const pptTypeList = ['ppt', 'pptx'];
|
const pdfTypeList = ['pdf'];
|
const zipTypeList = new Set(['7z', 'arj', 'rar', 'z', 'zip']);
|
const txtTypeList = new Set(['log', 'txt']);
|
const codeTypeList = new Set(['cs', 'html', 'xml']);
|
const imgTypeList = new Set(['bmp', 'gif', 'jpeg', 'jpg', 'png']);
|
const videoTypeList = new Set(['avi', 'avi', 'flv', 'flv', 'mkv', 'mov', 'mp3', 'mp4', 'mpeg', 'mpg', 'mpg', 'ram', 'rm', 'rm', 'rmvb', 'swf', 'wma', 'wmv']);
|
const previewTypeList = new Set([...excelTypeList, ...pdfTypeList, ...pptTypeList, ...txtTypeList, ...wordTypeList]);
|
const { createMessage, createConfirm } = useMessage();
|
const fileUploaderRef = ref<any>(null);
|
const filePreviewRef = ref<any>(null);
|
const state = reactive<State>({
|
activeKey: 'all',
|
levelList: [],
|
showMode: 1, // 1 列表模式 2 缩略模式
|
selectedRowKeys: [],
|
searchInfo: {
|
keyword: '',
|
parentId: '0',
|
},
|
list: [],
|
loading: false,
|
isIndeterminate: false,
|
checkAll: false,
|
});
|
const { activeKey, levelList, showMode, selectedRowKeys, searchInfo, list, loading, isIndeterminate, checkAll } = toRefs(state);
|
|
const [registerFormModal, { openModal: openFormModal }] = useModal();
|
const [registerUserBox, { openModal: openUserBox }] = useModal();
|
const [registerFolderTree, { openModal: openFolderTree }] = useModal();
|
const [registerForm, { resetFields }] = useForm({
|
baseColProps: { span: 8 },
|
showActionButtonGroup: true,
|
showAdvancedButton: true,
|
compact: true,
|
schemas: [
|
{
|
field: 'keyword',
|
label: $t('common.keyword'),
|
component: 'Input',
|
componentProps: {
|
placeholder: $t('common.enterKeyword'),
|
submitOnPressEnter: true,
|
},
|
},
|
],
|
});
|
const [registerTable, { clearSelectedRowKeys }] = useVxeTable({
|
rowSelection: { type: 'checkbox' },
|
clickToRowSelect: false,
|
showTableSetting: false,
|
pagination: false,
|
immediate: false,
|
});
|
|
const getTableBindValue = computed(() => ({ loading: state.loading, onSelectionChange, dataSource: state.list, columns: getColumns() }));
|
|
watch(
|
() => state.activeKey,
|
() => init(),
|
);
|
|
function getApi() {
|
if (state.activeKey === 'shareOut') return getShareOutList;
|
if (state.activeKey === 'shareTome') return getShareTomeList;
|
if (state.activeKey === 'trash') return getTrashList;
|
return getAllList;
|
}
|
function getColumns() {
|
if (state.activeKey === 'shareOut') return shareOutColumns;
|
if (state.activeKey === 'shareTome') return shareTomeColumns;
|
if (state.activeKey === 'trash') return trashColumns;
|
return allColumns;
|
}
|
function handleJump(item, i) {
|
state.searchInfo.parentId = item.id;
|
state.levelList = state.levelList.slice(0, i + 1);
|
handleReset();
|
}
|
function handleSubmit(values) {
|
state.searchInfo.keyword = values?.keyword || '';
|
if (state.searchInfo.keyword) resetBreadcrumb();
|
initData();
|
}
|
function handleReset() {
|
state.searchInfo.keyword = '';
|
initData();
|
}
|
// 上传文件
|
function uploadFile() {
|
fileUploaderRef.value?.openUploader();
|
}
|
function addFolder() {
|
openFormModal(true, { parentId: state.searchInfo.parentId, id: '' });
|
}
|
function handleRename() {
|
openFormModal(true, { parentId: state.searchInfo.parentId, id: state.selectedRowKeys[0] });
|
}
|
function onRecordClick(record) {
|
if (state.activeKey === 'trash') return;
|
record.type ? handlePreview(record) : openFolder(record);
|
}
|
// 预览
|
function handlePreview(record) {
|
// 图片预览
|
if (imgTypeList.has(record.fileExtension)) {
|
const imageList = [getAuthMediaUrl(record.uploaderUrl)];
|
createImgPreview({ imageList });
|
return;
|
}
|
if (previewTypeList.has(record.fileExtension)) {
|
// 文件预览
|
const file = {
|
name: record.fullName,
|
fileId: record.filePath,
|
fileVersionId: null,
|
url: record.uploaderUrl,
|
};
|
filePreviewRef.value?.init(file);
|
return;
|
}
|
if (!record.id) return;
|
download([record.id]).then((res) => {
|
downloadByUrl({ url: res.data.url, fileName: res.data.name });
|
});
|
}
|
// 批量共享
|
function handleShare() {
|
openUserBox(true, { ids: state.selectedRowKeys, isBatch: true });
|
}
|
// 单个共享
|
function handleSingleShare(id) {
|
if (!id) return;
|
openUserBox(true, { ids: [id], isBatch: false });
|
}
|
// 移动到
|
function handleMoveTo() {
|
openFolderTree(true, { ids: state.selectedRowKeys, parentId: state.searchInfo.parentId });
|
}
|
function openFolder(record) {
|
state.searchInfo.parentId = record.id;
|
state.levelList.push(record);
|
state.selectedRowKeys = [];
|
handleReset();
|
}
|
function getRecordImg(ext) {
|
if (!ext) return folderImg;
|
if (ext) ext = ext.replace('.', '');
|
if (wordTypeList.includes(ext)) return wordImg;
|
if (excelTypeList.includes(ext)) return excelImg;
|
if (pptTypeList.includes(ext)) return pptImg;
|
if (pdfTypeList.includes(ext)) return pdfImg;
|
if (zipTypeList.has(ext)) return rarImg;
|
if (txtTypeList.has(ext)) return txtImg;
|
if (codeTypeList.has(ext)) return codeImg;
|
if (imgTypeList.has(ext)) return imageImg;
|
if (videoTypeList.has(ext)) return audioImg;
|
return blankImg;
|
}
|
// 下载
|
function handleDownload() {
|
download(state.selectedRowKeys).then((res) => {
|
downloadByUrl({ url: res.data.url, fileName: res.data.name });
|
});
|
}
|
// 删除
|
function handleDelete() {
|
createConfirm({
|
iconType: 'warning',
|
title: $t('common.tipTitle'),
|
content: '您确定要把所选文件放入回收站, 是否继续?',
|
onOk: () => {
|
batchDeleteDocument(state.selectedRowKeys).then((res) => {
|
createMessage.success(res.msg).then(() => {
|
initData();
|
});
|
});
|
},
|
});
|
}
|
// 取消共享
|
function handleUnshare() {
|
createConfirm({
|
iconType: 'warning',
|
title: $t('common.tipTitle'),
|
content: '您确定要取消共享, 是否继续?',
|
onOk: () => {
|
shareCancel(state.selectedRowKeys).then((res) => {
|
createMessage.success(res.msg).then(() => {
|
initData();
|
});
|
});
|
},
|
});
|
}
|
// 还原
|
function handleRecovery() {
|
createConfirm({
|
iconType: 'warning',
|
title: $t('common.tipTitle'),
|
content: '您确定要还原选中的文件?',
|
onOk: () => {
|
trashRecovery(state.selectedRowKeys).then((res) => {
|
createMessage.success(res.msg).then(() => {
|
initData();
|
});
|
});
|
},
|
});
|
}
|
// 彻底删除
|
function handleTrashDel() {
|
createConfirm({
|
iconType: 'warning',
|
title: '提示',
|
content: '文件删除后将无法恢复,您确定要彻底删除所选文件吗?',
|
onOk: () => {
|
trashDelete(state.selectedRowKeys).then((res) => {
|
createMessage.success(res.msg).then(() => {
|
initData();
|
});
|
});
|
},
|
});
|
}
|
// 切换展示模式
|
function toggleShowMode(type) {
|
state.showMode = type;
|
handleCheckedChange(state.selectedRowKeys);
|
}
|
// 缩略模式下全选
|
function handleCheckAllChange(e) {
|
const val = e.target.checked;
|
state.selectedRowKeys = val ? state.list.map((o) => o.id) : [];
|
state.isIndeterminate = false;
|
}
|
// 缩略模式下单选
|
function handleCheckedChange(val) {
|
const checkedCount = val.length;
|
state.checkAll = !!checkedCount && checkedCount === state.list.length;
|
state.isIndeterminate = !!checkedCount && checkedCount < state.list.length;
|
}
|
function onSelectionChange({ keys }) {
|
state.selectedRowKeys = keys;
|
}
|
function initData() {
|
state.loading = true;
|
state.list = [];
|
state.selectedRowKeys = [];
|
clearSelectedRowKeys();
|
handleCheckedChange(state.selectedRowKeys);
|
getApi()(state.searchInfo)
|
.then((res) => {
|
state.list = res.data.list || [];
|
state.loading = false;
|
})
|
.catch(() => {
|
state.list = [];
|
state.loading = false;
|
});
|
}
|
function resetBreadcrumb() {
|
const activeItem = leftList.find((o) => o.id === state.activeKey);
|
state.levelList = [{ id: '0', fullName: activeItem?.fullName }];
|
state.searchInfo.parentId = '0';
|
}
|
function init() {
|
resetBreadcrumb();
|
resetFields();
|
}
|
|
onMounted(() => {
|
init();
|
});
|
</script>
|
|
<template>
|
<div class="jnpf-content-wrapper document-wrapper bg-white">
|
<a-tabs v-model:active-key="activeKey" tab-position="left" class="common-left-tabs">
|
<a-tab-pane v-for="tab in leftList" :key="tab.id">
|
<template #tab><i :class="tab.icon"></i>{{ tab.fullName }}</template>
|
</a-tab-pane>
|
</a-tabs>
|
<div class="document-container">
|
<Breadcrumb class="!mb-[10px]">
|
<BreadcrumbItem v-if="levelList.length > 1" @click="handleJump(levelList[levelList.length - 2], levelList.length - 2)">
|
<a>返回上一级</a>
|
</BreadcrumbItem>
|
<BreadcrumbItem v-for="(item, i) in levelList" :key="i">
|
<span v-if="i + 1 >= levelList.length">{{ item.fullName }}</span>
|
<a v-else @click="handleJump(item, i)">{{ item.fullName }}</a>
|
</BreadcrumbItem>
|
</Breadcrumb>
|
<div class="jnpf-common-search-box">
|
<BasicForm class="search-form" @register="registerForm" @submit="handleSubmit" @reset="handleReset" />
|
<div class="jnpf-common-search-box-right">
|
<template v-if="!selectedRowKeys.length">
|
<template v-if="activeKey === 'all'">
|
<a-button pre-icon="icon-ym icon-ym-add-folder" @click="addFolder()">新建文件夹</a-button>
|
<a-button type="primary" pre-icon="icon-ym icon-ym-upload1" @click="uploadFile()" class="ml-[10px]">上传文件</a-button>
|
</template>
|
<a-tooltip>
|
<template #title>{{ showMode === 1 ? '缩略模式' : '列表模式' }}</template>
|
<i class="mode-icon icon-ym icon-ym-thumb-mode" @click="toggleShowMode(2)" v-show="showMode === 1"></i>
|
<i class="mode-icon icon-ym icon-ym-tile-mode" @click="toggleShowMode(1)" v-show="showMode === 2"></i>
|
</a-tooltip>
|
</template>
|
<template v-else>
|
<a-space-compact block>
|
<template v-if="activeKey === 'all'">
|
<a-button pre-icon="icon-ym icon-ym-app-share" @click="handleShare">共享</a-button>
|
<a-button pre-icon="icon-ym icon-ym-app-download" @click="handleDownload">下载</a-button>
|
<a-button pre-icon="icon-ym icon-ym-app-delete" @click="handleDelete">删除</a-button>
|
<a-button pre-icon="icon-ym icon-ym-app-rename" @click="handleRename" v-if="selectedRowKeys.length === 1">重命名</a-button>
|
<a-button pre-icon="icon-ym icon-ym-app-move" @click="handleMoveTo">移动</a-button>
|
</template>
|
<template v-if="activeKey === 'shareOut'">
|
<a-button pre-icon="icon-ym icon-ym-share-cancel" @click="handleUnshare" v-if="levelList.length <= 1">取消共享</a-button>
|
</template>
|
<template v-if="activeKey === 'shareTome'">
|
<a-button pre-icon="icon-ym icon-ym-app-download" @click="handleDownload">下载</a-button>
|
</template>
|
<template v-if="activeKey === 'trash'">
|
<a-button pre-icon="icon-ym icon-ym-recovery" @click="handleRecovery">还原</a-button>
|
<a-button pre-icon="icon-ym icon-ym-app-delete" @click="handleTrashDel">删除</a-button>
|
</template>
|
</a-space-compact>
|
</template>
|
</div>
|
</div>
|
<BasicVxeTable @register="registerTable" v-bind="getTableBindValue" v-show="showMode === 1">
|
<template #fullName="{ record }">
|
<span class="document-fileName" :class="{ 'link-fullName': record.type }" @click="onRecordClick(record)">
|
<img :src="getRecordImg(record.fileExtension)" class="file-img" />
|
{{ record.fullName }}
|
</span>
|
</template>
|
<template #isShare="{ record }">
|
<span v-if="record.isShare" title="共享文件"><i class="icon-ym icon-ym-share-filled i-default"></i></span>
|
<span v-else></span>
|
</template>
|
<template #fileSize="{ record }">
|
{{ toFileSize(record.fileSize) }}
|
</template>
|
<template #action="{ record }">
|
<a-button type="link" size="small" @click="handleSingleShare(record.id)" class="!px-0" v-if="levelList.length <= 1">共享</a-button>
|
</template>
|
</BasicVxeTable>
|
<div class="document-list-header" v-show="showMode === 2">
|
<a-checkbox :indeterminate="isIndeterminate" v-model:checked="checkAll" :disabled="!list.length" @change="handleCheckAllChange">全选</a-checkbox>
|
</div>
|
<a-checkbox-group v-model:value="selectedRowKeys" class="document-list" @change="handleCheckedChange" v-show="showMode === 2">
|
<ScrollContainer v-loading="loading">
|
<div class="document-list-main">
|
<div
|
class="document-item"
|
:class="{ active: selectedRowKeys.includes(record.id) }"
|
v-for="record in list"
|
:key="record.id"
|
@click="onRecordClick(record)">
|
<img :src="getRecordImg(record.fileExtension)" class="document-item-img" />
|
<p class="document-item-title" :title="record.fullName">{{ record.fullName }}</p>
|
<div class="check-icon" @click.stop>
|
<a-checkbox :value="record.id" />
|
</div>
|
</div>
|
</div>
|
<jnpf-empty v-if="!list.length" />
|
</ScrollContainer>
|
</a-checkbox-group>
|
</div>
|
<FileUploader ref="fileUploaderRef" :parent-id="searchInfo.parentId" @file-success="initData" />
|
<Form @register="registerFormModal" @reload="initData" />
|
<UserBox @register="registerUserBox" @reload="initData" />
|
<FolderTree @register="registerFolderTree" @reload="initData" />
|
<Preview ref="filePreviewRef" />
|
</div>
|
</template>
|
|
<style lang="scss">
|
.document-wrapper {
|
:deep(.ant-table-container),
|
.ant-table-container {
|
.ant-table-cell::before {
|
display: none !important;
|
}
|
}
|
|
.document-container {
|
display: flex;
|
flex: 1;
|
flex-direction: column;
|
height: 100%;
|
padding-top: 20px;
|
overflow: hidden;
|
|
.icon-ym {
|
font-size: 14px;
|
}
|
}
|
|
.jnpf-common-search-box {
|
margin-bottom: 10px;
|
|
.jnpf-common-search-box-right {
|
top: 10px;
|
display: flex;
|
align-items: center;
|
|
.mode-icon {
|
margin-left: 10px;
|
font-size: 18px;
|
line-height: 32px;
|
cursor: pointer;
|
|
&:hover {
|
color: var(--primary-color);
|
}
|
}
|
}
|
}
|
|
.document-list-header {
|
flex-shrink: 0;
|
margin-top: -10px;
|
margin-right: 10px;
|
line-height: 40px;
|
border-bottom: 1px solid var(--border-color-base1);
|
}
|
|
.document-list {
|
flex: 1;
|
width: 100%;
|
padding-bottom: 10px;
|
overflow: hidden;
|
|
.document-list-main {
|
display: flex;
|
flex-wrap: wrap;
|
place-content: flex-start flex-start;
|
height: 100%;
|
padding: 20px 10px 0 0;
|
}
|
|
.document-item {
|
position: relative;
|
width: 100px;
|
height: 100px;
|
padding: 5px;
|
margin: 0 20px 40px;
|
overflow: hidden;
|
cursor: pointer;
|
border-radius: var(--radius);
|
|
&:hover {
|
background-color: var(--app-content-background);
|
|
.check-icon {
|
display: block;
|
}
|
}
|
|
&.active {
|
.check-icon {
|
display: block;
|
}
|
}
|
|
.document-item-img {
|
width: 60px;
|
height: 60px;
|
margin: 0 auto 6px;
|
}
|
|
.document-item-title {
|
overflow: hidden;
|
text-overflow: ellipsis;
|
font-size: 14px;
|
color: var(--text-color-label);
|
text-align: center;
|
white-space: nowrap;
|
}
|
|
.check-icon {
|
position: absolute;
|
top: 2px;
|
right: 4px;
|
display: none;
|
}
|
}
|
}
|
|
.document-fileName {
|
cursor: pointer;
|
|
&.link-fullName {
|
&:hover {
|
color: var(--primary-color);
|
}
|
}
|
|
.file-img {
|
display: inline-block;
|
width: 16px;
|
height: 16px;
|
vertical-align: -3px;
|
}
|
}
|
}
|
</style>
|