Files
jzsj-vue/src/views/pwl/pwlProjectLibrary/index.vue
b2894lxlx f1a9f53099 1、文档新增批量删除
2、文档新增点击文件名预览
2026-05-27 11:10:29 +08:00

1394 lines
38 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="pwlProjectLibraryId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<a-tag v-if="record.type === 'biz'" color="blue">行业库</a-tag>
<a-tag v-if="record.type === 'pub'" color="green">公共库</a-tag>
<span
v-if="
!record.type ||
(record.type !== 'biz' && record.type !== 'pub')
"
>未设置</span
>
</template>
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<a
:class="{ 'disabled-text': !record.kbId }"
:style="
!record.kbId ? 'cursor: not-allowed; color: #999' : ''
"
@click="record.kbId && openDocManage(record)"
>文档管理</a
>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
v-if="false"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<PwlProjectLibraryEdit
v-model:visible="showEdit"
:data="current"
@done="reload"
/>
<!-- 文档管理弹窗 -->
<a-modal
v-model:visible="showDocManage"
:title="`文档管理 - ${currentKbName}`"
width="80%"
:footer="null"
wrap-class-name="doc-manage-modal"
>
<div class="doc-manage-container">
<div class="doc-layout">
<!-- 左侧目录树 -->
<div class="dir-tree-panel">
<div class="dir-header">
<span>文档目录</span>
<a-space>
<a-button
type="link"
size="small"
@click="openEditDir"
title="编辑目录"
:disabled="!selectedKeys.length"
>
<template #icon><EditOutlined /></template>
</a-button>
<a-button
type="link"
size="small"
@click="openAddDir"
title="新增目录"
>
<template #icon><PlusOutlined /></template>
</a-button>
<a-button
type="link"
size="small"
@click="deleteSelectedDir"
title="删除目录"
:disabled="!selectedKeys.length"
class="ele-text-danger"
>
<template #icon><DeleteOutlined /></template>
</a-button>
</a-space>
</div>
<div class="tree-container">
<a-tree
v-if="treeData.length > 0"
:tree-data="treeData"
:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"
:load-data="onLoadData"
@expand="onExpand"
@select="onSelect"
:field-names="{
title: 'name',
key: 'id',
children: 'children'
}"
>
<template #title="{ name, id }">
<span :class="{ 'active-dir': selectedKeys[0] === id }">{{
name
}}</span>
</template>
</a-tree>
<a-empty v-else :image="simpleImage" description="暂无目录" />
</div>
</div>
<!-- 右侧文档列表 -->
<div class="doc-list-panel">
<div class="doc-header">
<div class="doc-actions">
<a-button type="primary" @click="openImport">
<template #icon><UploadOutlined /></template>
上传文档
</a-button>
<a-button
danger
:disabled="!selectedDocRows.length"
@click="deleteSelectedDocs"
>
<template #icon><DeleteOutlined /></template>
批量删除
</a-button>
<span
v-if="selectedDocRows.length"
class="doc-selected-count"
>
已选 {{ selectedDocRows.length }} 个文件
</span>
<span class="doc-tips">请选择分类上传资料</span>
</div>
</div>
<div class="doc-content">
<a-table
:dataSource="docList"
:columns="docColumns"
:loading="docLoading"
:row-selection="docRowSelection"
rowKey="id"
:scroll="{ x: 2600, y: 500 }"
:pagination="pagination"
@change="handleTableChange"
size="middle"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'">
<a @click="openDocPreview(record)">{{
renderDocText(record, ['fileName', 'name'])
}}</a>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openDocPreview(record)">预览</a>
<a-divider type="vertical" />
<a @click="openDocEdit(record)">编辑</a>
<a-divider type="vertical" />
<a-popconfirm
title="确定要删除此文档吗?"
@confirm="deleteDoc(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</div>
</div>
</div>
</div>
</a-modal>
<!-- 导入弹窗 -->
<Import2
v-model:visible="showImport"
@done="loadCloudFiles"
:doc="selectedDoc"
/>
<a-modal
v-model:visible="showDocEdit"
title="编辑文档"
width="700px"
@ok="handleSaveDoc"
@cancel="closeDocEdit"
>
<a-form :model="docForm" layout="vertical">
<a-row :gutter="16">
<a-col :span="12"
><a-form-item label="文件名称"
><a-input v-model:value="docForm.fileName" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="制度标题"
><a-input v-model:value="docForm.title" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="发文字号"
><a-input v-model:value="docForm.issueNumber" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="版本号"
><a-input v-model:value="docForm.version" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="成文日期"
><a-date-picker
v-model:value="docForm.documentDate"
value-format="YYYY-MM-DD"
style="width: 100%" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="生效日期"
><a-date-picker
v-model:value="docForm.effectiveDate"
value-format="YYYY-MM-DD"
style="width: 100%" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="废止日期"
><a-date-picker
v-model:value="docForm.abolishDate"
value-format="YYYY-MM-DD"
style="width: 100%" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="适用业务范围"
><a-input v-model:value="docForm.businessScope" /></a-form-item
></a-col>
<a-col :span="12"
><a-form-item label="适用区域"
><a-input v-model:value="docForm.region" /></a-form-item
></a-col>
<a-col :span="24"
><a-form-item label="关联制度"
><a-input
v-model:value="docForm.relatedDocuments" /></a-form-item
></a-col>
<a-col :span="24"
><a-form-item label="备注"
><a-textarea
v-model:value="docForm.comments"
:rows="3" /></a-form-item
></a-col>
</a-row>
</a-form>
</a-modal>
<!-- 新增/编辑目录弹窗 -->
<a-modal
v-model:visible="showDirModal"
:title="dirModalTitle"
width="400px"
@ok="handleSaveDir"
@cancel="closeDirModal"
>
<a-form :model="dirForm" layout="vertical">
<a-form-item label="目录名称">
<a-input
v-model:value="dirForm.name"
placeholder="请输入目录名称"
/>
</a-form-item>
<a-form-item label="排序号">
<a-input-number
v-model:value="dirForm.sortNumber"
placeholder="请输入排序号"
:min="0"
style="width: 100%"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref, computed, h } from 'vue';
import { message, Modal, Empty } from 'ant-design-vue';
import {
ExclamationCircleOutlined,
PlusOutlined,
UploadOutlined,
EditOutlined,
DeleteOutlined
} from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import PwlProjectLibraryEdit from './components/pwlProjectLibraryEdit.vue';
import {
pagePwlProjectLibrary,
removePwlProjectLibrary,
removeBatchPwlProjectLibrary
} from '@/api/pwl/pwlProjectLibrary';
import type {
PwlProjectLibrary,
PwlProjectLibraryParam
} from '@/api/pwl/pwlProjectLibrary/model';
import Import2 from '@/views/oa/oaCompany/components/Import2.vue';
import {
listAiCloudDoc,
addAiCloudDoc,
updateAiCloudDoc,
removeAiCloudDoc
} from '@/api/ai/aiCloudDoc';
import type { AiCloudDoc } from '@/api/ai/aiCloudDoc/model';
import {
listAiCloudFile,
removeAiCloudFile,
updateAiCloudFile
} from '@/api/ai/aiCloudFile';
import type { AiCloudFile } from '@/api/ai/aiCloudFile/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<PwlProjectLibrary[]>([]);
// 当前编辑数据
const current = ref<PwlProjectLibrary | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 文档管理相关响应式变量
const showDocManage = ref(false); // 是否显示文档管理弹窗
const showImport = ref(false); // 是否显示导入弹窗
const showDirModal = ref(false); // 是否显示目录弹窗(新增/编辑)
const showDocEdit = ref(false);
// 文档管理相关变量
const currentKbId = ref(''); // 当前知识库ID
const currentKbName = ref(''); // 当前知识库名称
const currentCompanyId = ref<number>(0); // 当前单位ID - 固定为0
const docList = ref<AiCloudFile[]>([]); // 文档列表数据
const docLoading = ref(false); // 文档加载状态
const allDirs = ref<AiCloudDoc[]>([]); // 所有目录列表
const selectedDocRowKeys = ref<(string | number)[]>([]);
const selectedDocRows = ref<AiCloudFile[]>([]);
// 树形结构相关
const expandedKeys = ref<(string | number)[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
// 目录弹窗相关
const isEditingDir = ref(false); // 是否为编辑模式
const currentDirId = ref<number>(); // 当前操作的目录ID
// 目录表单
const dirForm = ref({
name: '',
sortNumber: 0
});
const docForm = ref<AiCloudFile>({
id: undefined,
docId: undefined,
fileName: undefined,
fileSize: undefined,
fileType: undefined,
workspaceId: undefined,
fileId: undefined,
fileUrl: undefined,
fileExt: undefined,
title: undefined,
issueNumber: undefined,
version: undefined,
documentDate: undefined,
effectiveDate: undefined,
abolishDate: undefined,
businessScope: undefined,
relatedDocuments: undefined,
region: undefined,
comments: undefined,
uploadTime: undefined,
sortNumber: 0,
status: 0
});
// 计算目录弹窗标题
const dirModalTitle = computed(() => {
const mode = isEditingDir.value ? '编辑' : '新增';
const location = selectedDirName.value
? `在『${selectedDirName.value}』下`
: '在根目录下';
return `${mode}目录 - ${location}`;
});
// 分页配置
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total: number) => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100']
});
// 计算树形数据
const treeData = computed(() => {
const buildTree = (parentId = 0): any[] => {
return allDirs.value
.filter((item) => item.parentId === parentId)
.map((item) => ({
...item,
key: item.id,
title: item.name,
children: buildTree(item.id),
isLeaf:
allDirs.value.filter((child) => child.parentId === item.id)
.length === 0
}));
};
return buildTree(0);
});
// 选中的目录名称
const selectedDirName = computed(() => {
if (selectedKeys.value.length === 0) return '';
const selectedId = selectedKeys.value[0];
const dir = allDirs.value.find((item) => item.id === selectedId);
return dir?.name || '';
});
// 计算选中的目录ID用于文件上传
const selectedCategoryId = computed(() => {
if (selectedKeys.value.length === 0) return '';
return selectedKeys.value[0].toString();
});
// 计算选中的文档对象
const selectedDoc = computed(() => {
if (selectedKeys.value.length === 0) return null;
const selectedId = selectedKeys.value[0];
const doc = allDirs.value.find((item) => item.id === selectedId);
return doc
? {
id: doc.id!,
categoryId: doc.categoryId || ''
}
: null;
});
const getDocFieldValue = (
record: AiCloudFile,
keys: string[],
fallback = '-'
) => {
const row = record as Record<string, any>;
for (const key of keys) {
const value = row[key];
if (value !== undefined && value !== null && value !== '') {
return value;
}
}
return fallback;
};
const renderDocText = (record: AiCloudFile, keys: string[]) => {
return getDocFieldValue(record, keys);
};
const renderDocLink = (record: AiCloudFile, keys: string[]) => {
const value = getDocFieldValue(record, keys);
if (value === '-') {
return '-';
}
return h(
'a',
{
href: String(value),
target: '_blank',
rel: 'noreferrer'
},
String(value)
);
};
const resetDocSelection = () => {
selectedDocRowKeys.value = [];
selectedDocRows.value = [];
};
const getDocFileUrl = (record: AiCloudFile) => {
const value = getDocFieldValue(
record,
['fileUrl', 'downloadUrl', 'url'],
''
);
return value === '-' ? '' : String(value).trim();
};
const getDocFileExt = (record: AiCloudFile) => {
const candidates = [
record.fileExt,
record.fileType,
record.fileName,
getDocFileUrl(record)
].filter(Boolean) as string[];
for (const item of candidates) {
const cleanValue = item.split('?')[0];
const ext = cleanValue.includes('.')
? cleanValue.split('.').pop()
: cleanValue;
if (ext) {
return ext.toLowerCase().replace(/^\./, '');
}
}
return '';
};
const openDocPreview = (record: AiCloudFile) => {
const fileUrl = getDocFileUrl(record);
if (!fileUrl) {
message.warning('该文件暂无可预览地址');
return;
}
const ext = getDocFileExt(record);
const officePreviewExts = ['doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'];
const previewUrl = officePreviewExts.includes(ext)
? `https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
fileUrl
)}`
: fileUrl;
window.open(previewUrl, '_blank', 'noopener,noreferrer');
};
const docRowSelection = computed(() => ({
selectedRowKeys: selectedDocRowKeys.value,
onChange: (keys: (string | number)[], rows: AiCloudFile[]) => {
selectedDocRowKeys.value = keys;
selectedDocRows.value = rows;
}
}));
// 文档表格列配置
const docColumns = ref([
{
title: '文件名称',
dataIndex: 'fileName',
key: 'fileName',
width: 180,
ellipsis: true,
customRender: ({ record }: { record: AiCloudFile }) =>
renderDocText(record, ['fileName', 'name'])
},
{
title: '制度标题',
dataIndex: 'title',
key: 'title',
width: 220,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['title', 'documentTitle'])
},
{
title: '发文字号',
dataIndex: 'issueNumber',
key: 'issueNumber',
width: 160,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['issueNumber', 'documentNo', 'fileNo'])
},
{
title: '版本号(反映当前制度更新状态,如有)',
dataIndex: 'version',
key: 'version',
width: 220,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text ||
renderDocText(record, ['version', 'versionNumber', 'versionName'])
},
{
title: '成文日期(落款日期)',
dataIndex: 'documentDate',
key: 'documentDate',
width: 180,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['documentDate', 'signDate', 'writeDate'])
},
{
title: '生效日期',
dataIndex: 'effectiveDate',
key: 'effectiveDate',
width: 160,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['effectiveDate'])
},
{
title: '废止日期',
dataIndex: 'abolishDate',
key: 'abolishDate',
width: 160,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['abolishDate', 'invalidDate'])
},
{
title: '适用业务范围',
dataIndex: 'businessScope',
key: 'businessScope',
width: 220,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['businessScope'])
},
{
title: '关联制度(与本制度相关的其他文件,如新旧制度间的关联文件)',
dataIndex: 'relatedDocuments',
key: 'relatedDocuments',
width: 260,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text ||
renderDocText(record, [
'relatedDocuments',
'relatedDoc',
'relatedFiles'
])
},
{
title:
'适用区域(全国/广西/南宁市等)如果是公司文档管理的模板,不需要这个字段内容',
dataIndex: 'region',
key: 'region',
width: 280,
ellipsis: true,
customRender: ({ record, text }: { record: AiCloudFile; text: any }) =>
text || renderDocText(record, ['region', 'applyRegion'])
},
{
title: '下载链接',
dataIndex: 'fileUrl',
key: 'fileUrl',
width: 220,
ellipsis: true,
customRender: ({ record }: { record: AiCloudFile }) =>
renderDocLink(record, ['fileUrl', 'downloadUrl', 'url'])
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
width: 80
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pagePwlProjectLibrary({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90
},
{
title: '资料库',
dataIndex: 'name',
key: 'name',
align: 'center'
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
align: 'center'
},
// {
// title: '关联知识库ID',
// dataIndex: 'kbId',
// key: 'kbId',
// align: 'center',
// },
// {
// title: '资料库图标',
// dataIndex: 'image',
// key: 'image',
// align: 'center',
// },
// {
// title: '是否推荐',
// dataIndex: 'recommend',
// key: 'recommend',
// align: 'center',
// },
// {
// title: '状态',
// dataIndex: 'status',
// key: 'status',
// align: 'center',
// },
// {
// title: '备注',
// dataIndex: 'comments',
// key: 'comments',
// align: 'center',
// },
{
title: '排序',
dataIndex: 'sort',
key: 'sort',
align: 'center'
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: PwlProjectLibraryParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: PwlProjectLibrary) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 打开文档管理弹窗
const openDocManage = async (record: PwlProjectLibrary) => {
currentKbId.value = record.kbId;
currentKbName.value = record.name;
currentCompanyId.value = 0; // 固定为0
pagination.value.current = 1;
showDocManage.value = true;
// 重置选择状态
expandedKeys.value = [];
selectedKeys.value = [];
resetDocSelection();
// 加载目录列表
await loadAllCloudDocs(true);
};
// 加载所有目录
const loadAllCloudDocs = async (resetState = false) => {
try {
const params = {
companyId: 0 // 固定为0
};
const result = await listAiCloudDoc(params);
allDirs.value = result || [];
if (resetState) {
// 默认展开根节点并选中第一个目录
if (allDirs.value.length > 0) {
const rootDirs = allDirs.value.filter((item) => item.parentId === 0);
if (rootDirs.length > 0) {
expandedKeys.value = [rootDirs[0].id!];
selectedKeys.value = [rootDirs[0].id!];
loadCloudFiles();
}
}
}
} catch (error) {
message.error('加载目录列表失败');
console.error('加载目录错误:', error);
}
};
// 树节点展开
const onExpand = (keys: (string | number)[]) => {
expandedKeys.value = keys;
};
// 树节点选择
const onSelect = (keys: (string | number)[], { node }: any) => {
selectedKeys.value = keys;
pagination.value.current = 1;
resetDocSelection();
loadCloudFiles();
};
// 异步加载子节点
const onLoadData = (node: any) => {
return new Promise<void>((resolve) => {
resolve();
});
};
// 表格分页变化处理
const handleTableChange = (pag: any) => {
pagination.value.current = pag.current;
pagination.value.pageSize = pag.pageSize;
loadCloudFiles();
};
// 加载文档列表
const loadCloudFiles = async () => {
docLoading.value = true;
try {
if (!selectedCategoryId.value) {
docList.value = [];
pagination.value.total = 0;
resetDocSelection();
return;
}
const params = {
docId: parseInt(selectedCategoryId.value),
page: pagination.value.current,
pageSize: pagination.value.pageSize
};
const result = await listAiCloudFile(params);
if (result && result.records) {
docList.value = result.records;
pagination.value.total = result.total;
} else {
docList.value = result || [];
pagination.value.total = docList.value.length;
}
resetDocSelection();
} catch (error) {
message.error('加载文件列表失败');
console.error('加载文件错误:', error);
} finally {
docLoading.value = false;
}
};
// 删除文档
const deleteDoc = async (record: AiCloudFile) => {
try {
await removeAiCloudFile(record.id);
await loadCloudFiles();
message.success('删除成功');
} catch (error) {
message.error('删除失败');
console.error(error);
}
};
const deleteSelectedDocs = () => {
if (!selectedDocRows.value.length) {
message.info('请先选择要删除的文件');
return;
}
Modal.confirm({
title: '提示',
content: `确定要删除选中的 ${selectedDocRows.value.length} 个文件吗?`,
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: async () => {
const currentPageSize = docList.value.length;
const ids = selectedDocRows.value
.map((item) => item.id)
.filter((id): id is number => typeof id === 'number');
const results = await Promise.allSettled(
ids.map((id) => removeAiCloudFile(id))
);
const successCount = results.filter(
(item) => item.status === 'fulfilled'
).length;
const failedCount = results.length - successCount;
if (
successCount > 0 &&
successCount === currentPageSize &&
pagination.value.current > 1
) {
pagination.value.current -= 1;
}
if (successCount > 0) {
await loadCloudFiles();
}
if (failedCount === 0) {
message.success(`成功删除 ${successCount} 个文件`);
} else if (successCount > 0) {
message.warning(
`已删除 ${successCount} 个文件,${failedCount} 个删除失败`
);
} else {
message.error('批量删除失败');
}
}
});
};
const openDocEdit = (record: AiCloudFile) => {
docForm.value = { ...record };
showDocEdit.value = true;
};
const closeDocEdit = () => {
showDocEdit.value = false;
};
const handleSaveDoc = async () => {
try {
await updateAiCloudFile(docForm.value);
message.success('修改成功');
closeDocEdit();
await loadCloudFiles();
} catch (error: any) {
message.error(error.message || '修改失败');
}
};
// 打开导入弹窗
const openImport = () => {
if (selectedKeys.value.length === 0) {
message.info('请先选择一个目录');
return;
}
if (!selectedDoc.value) {
message.info('选中的目录信息不完整');
return;
}
showImport.value = true;
};
// 打开新增目录弹窗
const openAddDir = () => {
if (selectedKeys.value.length === 0) {
message.info('请先选择一个目录');
return;
}
isEditingDir.value = false;
currentDirId.value = undefined;
dirForm.value.name = '';
dirForm.value.sortNumber = 0;
showDirModal.value = true;
};
// 打开编辑目录弹窗
const openEditDir = () => {
if (selectedKeys.value.length === 0) {
message.info('请先选择一个目录');
return;
}
const selectedDir = allDirs.value.find(
(item) => item.id === selectedKeys.value[0]
);
if (!selectedDir) {
message.error('未找到选中的目录');
return;
}
isEditingDir.value = true;
currentDirId.value = selectedDir.id;
dirForm.value.name = selectedDir.name || '';
dirForm.value.sortNumber = selectedDir.sortNumber || 0;
showDirModal.value = true;
};
// 删除选中目录
const deleteSelectedDir = async () => {
if (selectedKeys.value.length === 0) {
message.info('请先选择一个目录');
return;
}
const selectedDir = allDirs.value.find(
(item) => item.id === selectedKeys.value[0]
);
if (!selectedDir) {
message.error('未找到选中的目录');
return;
}
await deleteDir(selectedDir.id!, selectedDir.name || '');
};
// 关闭目录弹窗
const closeDirModal = () => {
showDirModal.value = false;
dirForm.value.name = '';
dirForm.value.sortNumber = 0;
currentDirId.value = undefined;
};
// 处理保存目录(新增/编辑)
const handleSaveDir = async () => {
if (!dirForm.value.name) {
message.error('请输入目录名称');
return;
}
try {
if (isEditingDir.value) {
const updateData: AiCloudDoc = {
id: currentDirId.value,
name: dirForm.value.name,
sortNumber: dirForm.value.sortNumber || 0
};
await updateAiCloudDoc(updateData);
message.success('修改目录成功');
} else {
const newDir: AiCloudDoc = {
companyId: 0, // 固定为0
parentId: selectedKeys.value[0] as number,
name: dirForm.value.name,
sortNumber: dirForm.value.sortNumber || 0
};
await addAiCloudDoc(newDir);
message.success('新增目录成功');
}
showDirModal.value = false;
await loadAllCloudDocs(false);
if (!isEditingDir.value) {
const parentId = selectedKeys.value[0] as number;
if (!expandedKeys.value.includes(parentId)) {
expandedKeys.value = [...expandedKeys.value, parentId];
}
}
} catch (error) {
message.error(isEditingDir.value ? '修改目录失败' : '新增目录失败');
console.error('目录操作错误:', error);
}
};
// 删除目录
const deleteDir = async (dirId: number, dirName: string) => {
const hasChildren = allDirs.value.some((item) => item.parentId === dirId);
if (hasChildren) {
message.error(
`目录「${dirName}」包含子目录,无法删除。请先删除该目录下的所有子目录。`
);
return;
}
Modal.confirm({
title: '提示',
content: `确定要删除目录「${dirName}」吗?`,
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: async () => {
try {
await removeAiCloudDoc(dirId);
message.success('删除目录成功');
await loadAllCloudDocs(false);
if (selectedKeys.value.includes(dirId)) {
selectedKeys.value = [];
docList.value = [];
pagination.value.total = 0;
}
} catch (error: any) {
message.error(error.message || '删除目录失败');
console.error('删除目录错误:', error);
}
}
});
};
/* 删除单个 */
const remove = (row: PwlProjectLibrary) => {
const hide = message.loading('请求中..', 0);
removePwlProjectLibrary(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchPwlProjectLibrary(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: PwlProjectLibrary) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'PwlProjectLibrary'
};
</script>
<style lang="less" scoped>
/* 文档管理布局 */
.doc-manage-container {
height: 700px;
}
.doc-layout {
display: flex;
height: 100%;
gap: 16px;
}
.dir-tree-panel {
width: 280px;
border: 1px solid #e8e8e8;
border-radius: 6px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.dir-header {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
}
.tree-container {
flex: 1;
padding: 8px;
overflow: auto;
}
.doc-list-panel {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid #e8e8e8;
border-radius: 6px;
min-width: 0;
overflow: hidden;
}
.doc-header {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
}
.doc-actions {
display: flex;
align-items: center;
gap: 12px;
}
.doc-tips {
color: #ff4d4f;
font-size: 12px;
}
.doc-selected-count {
color: #1890ff;
font-size: 12px;
}
.doc-content {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
/* 树节点激活样式 */
:deep(.active-dir) {
color: #1890ff;
font-weight: 500;
}
:deep(.ant-tree-node-content-wrapper) {
border-radius: 4px;
transition: all 0.3s;
width: 100%;
}
:deep(.ant-tree-node-content-wrapper:hover) {
background-color: #f5f5f5;
}
:deep(.ant-tree .ant-tree-treenode-selected .ant-tree-node-content-wrapper) {
background-color: #e6f7ff;
}
/* 优化表格样式 */
:deep(.doc-manage-modal .ant-modal-body) {
padding: 16px;
}
:deep(.doc-content .ant-table-wrapper) {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
:deep(.doc-content .ant-spin-nested-loading) {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
:deep(.doc-content .ant-spin-container) {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
min-width: 0;
}
:deep(.doc-content .ant-table) {
width: 100%;
flex: 1;
}
:deep(.doc-content .ant-table-container) {
flex: 1;
display: flex;
flex-direction: column;
}
:deep(.doc-content .ant-table-body) {
flex: 1;
}
:deep(.doc-content .ant-table-thead > tr > th) {
background: #fafafa;
font-weight: 600;
white-space: nowrap;
}
:deep(.doc-content .ant-table-tbody > tr > td) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 文件名列特殊处理,允许换行 */
:deep(.doc-content .ant-table-tbody > tr > td:first-child) {
white-space: normal;
word-break: break-word;
line-height: 1.4;
max-width: 400px;
}
/* 分页样式调整 */
:deep(.doc-content .ant-pagination) {
margin-top: 16px;
margin-bottom: 0;
}
:deep(.doc-content .ant-table-pagination) {
margin-top: 16px;
margin-bottom: 0;
flex-shrink: 0;
}
/* 删除按钮样式 */
:deep(.ele-text-danger) {
color: #ff4d4f;
}
</style>