Files
template-10561/src/views/oa/oaCompany/index.vue

841 lines
21 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>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<template #subTitle>
<div style="width: 300px; margin-left: 100px">
<a-steps :current="0" size="small">
<a-step title="单位信息" />
<a-step title="项目管理" />
</a-steps>
</div>
</template>
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="companyId"
: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 === 'companyLogo'">
<a-image
v-if="isImage(record.companyLogo)"
:src="record.companyLogo"
:width="80"
/>
</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)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<OaCompanyEdit v-model:visible="showEdit" :data="current" @done="reload" />
<!-- 文档管理弹窗 -->
<a-modal
v-model:visible="showDocManage"
:title="`文档管理 - ${currentKbName}`"
width="1200px"
: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-button type="link" size="small" @click="openAddDir" title="新增目录">
<template #icon><PlusOutlined /></template>
</a-button>
</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>
<span class="doc-tips">请选择分类上传资料</span>
</div>
</div>
<div class="doc-content">
<a-table
:dataSource="docList"
:columns="docColumns"
:loading="docLoading"
rowKey="id"
:scroll="{ y: 500 }"
:pagination="pagination"
@change="handleTableChange"
size="middle"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<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="showAddDir"
:title="`新增目录 - ${selectedDirName ? '在『' + selectedDirName + '』下' : '在根目录下'}`"
width="400px"
@ok="handleAddDir"
@cancel="showAddDir = false"
>
<a-form :model="dirForm" layout="vertical">
<a-form-item label="目录名称">
<a-input v-model:value="dirForm.name" placeholder="请输入目录名称" />
</a-form-item>
</a-form>
</a-modal>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref, computed } from 'vue';
import { message, Modal, Empty } from 'ant-design-vue';
import { ExclamationCircleOutlined, PlusOutlined, UploadOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import OaCompanyEdit from './components/oaCompanyEdit.vue';
// 导入Import2组件和API
import Import2 from './components/Import2.vue';
import {
pageOaCompany,
removeOaCompany,
removeBatchOaCompany
} from '@/api/oa/oaCompany';
import type { OaCompany, OaCompanyParam } from '@/api/oa/oaCompany/model';
import { getPageTitle, isImage } from '@/utils/common';
// 导入AiCloudDoc API
import { listAiCloudDoc, addAiCloudDoc } from '@/api/ai/aiCloudDoc';
import type { AiCloudDoc } from '@/api/ai/aiCloudDoc/model';
// 导入AiCloudFile API
import { listAiCloudFile, removeAiCloudFile } from '@/api/ai/aiCloudFile';
import type { AiCloudFile } from '@/api/ai/aiCloudFile/model';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<OaCompany[]>([]);
// 当前编辑数据
const current = ref<OaCompany | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 文档管理相关响应式变量
const showDocManage = ref(false); // 是否显示文档管理弹窗
const showImport = ref(false); // 是否显示导入弹窗
const showAddDir = ref(false); // 是否显示新增目录弹窗
// 加载状态
const loading = ref(true);
// 文档管理相关变量
const currentKbId = ref(''); // 当前知识库ID
const currentKbName = ref(''); // 当前知识库名称
const currentCompanyId = ref<number>(); // 当前单位ID
const docList = ref<AiCloudFile[]>([]); // 文档列表数据改为使用AiCloudFile类型
const docLoading = ref(false); // 文档加载状态
const allDirs = ref<AiCloudDoc[]>([]); // 所有目录列表
// 树形结构相关
const expandedKeys = ref<(string | number)[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
// 新增目录表单
const dirForm = ref({
name: ''
});
// 分页配置
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: number = 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 || '' // 假设 AiCloudDoc 有 categoryId 字段
} : null;
});
// 文档表格列配置 - 去掉固定宽度,使用自适应
const docColumns = ref([
{
title: '文件名',
dataIndex: 'fileName',
key: 'fileName',
ellipsis: true
},
{
title: '文件大小',
dataIndex: 'fileSize',
key: 'fileSize',
width: 120,
customRender: ({ text }: { text: string }) => {
if (!text) return '-';
const size = Number(text);
if (size < 1024) return size + ' B';
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB';
return (size / (1024 * 1024)).toFixed(1) + ' MB';
}
},
{
title: '文件类型',
dataIndex: 'fileType',
key: 'fileType',
width: 120,
ellipsis: true
},
{
title: '上传时间',
dataIndex: 'uploadTime',
key: 'uploadTime',
width: 180,
customRender: ({ text }: { text: string }) => toDateString(text)
},
{
title: '操作',
key: 'action',
width: 80
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pageOaCompany({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'companyId',
key: 'companyId',
align: 'center',
hideInTable: true,
width: 90
},
{
title: 'LOGO',
dataIndex: 'companyLogo',
key: 'companyLogo',
align: 'center'
},
{
title: '单位名称',
dataIndex: 'companyName',
key: 'companyName',
align: 'center'
},
{
title: '单位标识',
dataIndex: 'companyCode',
key: 'companyCode',
align: 'center'
},
{
title: '推荐',
dataIndex: 'recommend',
key: 'recommend',
align: 'center'
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
align: 'center'
},
{
title: '排序号',
dataIndex: 'sortNumber',
key: 'sortNumber',
align: 'center'
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
ellipsis: true
},
{
title: '操作',
key: 'action',
width: 220,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: OaCompanyParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: OaCompany) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 打开文档管理弹窗
const openDocManage = async (record: OaCompany) => {
currentKbId.value = record.kbId;
currentKbName.value = record.companyName;
currentCompanyId.value = record.companyId;
pagination.value.current = 1;
showDocManage.value = true;
// 重置选择状态
expandedKeys.value = [];
selectedKeys.value = [];
// 加载目录列表
await loadAllCloudDocs();
};
// 加载所有目录
const loadAllCloudDocs = async () => {
try {
const params = {
companyId: currentCompanyId.value
// 不传parentId获取所有目录
};
const result = await listAiCloudDoc(params);
allDirs.value = result || [];
// 默认展开根节点并选中第一个目录
if (allDirs.value.length > 0) {
const rootDirs = allDirs.value.filter(item => item.parentId === 0);
if (rootDirs.length > 0) {
expandedKeys.value = [0]; // 展开根节点
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;
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();
};
// 加载文档列表 - 改为从AiCloudFile表查询
const loadCloudFiles = async () => {
docLoading.value = true;
try {
if (!selectedCategoryId.value) {
docList.value = [];
pagination.value.total = 0;
return;
}
const params = {
docId: parseInt(selectedCategoryId.value),
page: pagination.value.current,
pageSize: pagination.value.pageSize
};
const result = await listAiCloudFile(params);
// 假设返回的数据结构为 { records: [], total: number }
if (result && result.records) {
docList.value = result.records;
pagination.value.total = result.total;
} else {
// 如果API没有分页结构使用全部数据
docList.value = result || [];
pagination.value.total = docList.value.length;
}
} catch (error) {
message.error('加载文件列表失败');
console.error('加载文件错误:', error);
} finally {
docLoading.value = false;
}
};
// 删除文档 - 改为调用AiCloudFile的删除接口
const deleteDoc = async (record: AiCloudFile) => {
try {
await removeAiCloudFile(record.id);
// 重新加载当前页数据
await loadCloudFiles();
message.success('删除成功');
} catch (error) {
message.error('删除失败');
console.error(error);
}
};
// 打开导入弹窗
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;
}
dirForm.value.name = '';
showAddDir.value = true;
};
// 处理新增目录
const handleAddDir = async () => {
if (!dirForm.value.name) {
message.error('请输入目录名称');
return;
}
try {
const newDir: AiCloudDoc = {
companyId: currentCompanyId.value,
parentId: selectedKeys.value[0] as number,
name: dirForm.value.name,
sortNumber: 0
};
await addAiCloudDoc(newDir);
message.success('新增目录成功');
showAddDir.value = false;
// 重新加载目录列表
await loadAllCloudDocs();
// 展开父节点
if (!expandedKeys.value.includes(newDir.parentId!)) {
expandedKeys.value = [...expandedKeys.value, newDir.parentId!];
}
} catch (error) {
message.error('新增目录失败');
console.error('新增目录错误:', error);
}
};
/* 删除单个 */
const remove = (row: OaCompany) => {
const hide = message.loading('请求中..', 0);
removeOaCompany(row.companyId)
.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);
removeBatchOaCompany(selection.value.map((d) => d.oaCompanyId))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: OaCompany) => {
return {
onClick: () => {
// console.log(record);
},
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'OaCompany'
};
</script>
<style scoped>
/* 修改已完成步骤的连接线颜色 */
.ant-steps-item-finish
> .ant-steps-item-container
> .ant-steps-item-tail::after {
background-color: red !important;
}
/* 文档管理布局 */
.doc-manage-container {
height: 650px; /* 增加高度 */
}
.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-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;
}
: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; /* 防止分页器被压缩 */
}
</style>