Files
template-10561/src/views/pwl/pwlProject/components/reportContent.vue

1016 lines
33 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-drawer
:width="`95%`"
:visible="visible"
:confirm-loading="loading"
:maxable="maxAble"
title="AI审计内容生成器"
:body-style="{ paddingBottom: '8px', background: '#f3f3f3' }"
@update:visible="updateVisible"
:maskClosable="false"
:footer="null"
>
<!-- 基本信息卡片 -->
<a-card title="基本信息" style="margin-bottom: 20px" :bordered="false">
<a-descriptions>
<a-descriptions-item label="公司名称" :labelStyle="{ width: '90px', color: '#808080' }">
<span @click="copyText(form.name)">{{ form.name }}</span>
</a-descriptions-item>
<a-descriptions-item label="被审计人" :labelStyle="{ width: '90px', color: '#808080' }">
<span>{{ form.nickname || '暂无' }}</span>
</a-descriptions-item>
<a-descriptions-item label="审计时间" :labelStyle="{ width: '90px', color: '#808080' }">
<span>{{ form.expirationTime }}</span>
</a-descriptions-item>
</a-descriptions>
</a-card>
<!-- 操作按钮区域 -->
<a-card
style="margin-bottom: 20px; text-align: center; background: transparent"
:bordered="false"
>
<a-space>
<a-button
size="large"
type="primary"
@click="handleGenerateAll"
:loading="generatingAll"
class="generate-all-button"
:disabled="!hasTripleOneData"
>
<template #icon>
<UngroupOutlined />
</template>
生成全部方案
<a-tooltip
v-if="!hasTripleOneData"
title="请先生成三重一大制度对比分析表"
>
<QuestionCircleOutlined style="margin-left: 5px" />
</a-tooltip>
</a-button>
<a-button size="large">
<template #icon>
<DownloadOutlined />
</template>
保存草稿
</a-button>
<a-button size="large">
<template #icon>
<RedoOutlined />
</template>
加载草稿
</a-button>
<a-button
size="large"
type="danger"
class="export-button"
@click="handleExport"
>
<template #icon>
<UploadOutlined />
</template>
下载文件
</a-button>
</a-space>
</a-card>
<!-- 快速导航 -->
<a-card style="margin-bottom: 20px" :bordered="false">
<template #title>
<div
style="
display: flex;
align-items: center;
justify-content: space-between;
"
>
<span>快速导航</span>
<a-tooltip title="快捷键Ctrl+1~9 快速跳转Ctrl+↑↓ 上下导航">
<QuestionCircleOutlined style="color: #999; cursor: help" />
</a-tooltip>
</div>
</template>
<div class="navigation-container">
<div class="nav-grid">
<a-button
v-for="(item, index) in navigationItems"
:key="index"
:type="currentSection === index ? 'primary' : 'default'"
size="small"
@click="scrollToSection(index)"
class="nav-button"
:class="{ active: currentSection === index }"
>
<span class="nav-number">{{ item.number }}</span>
<span class="nav-text">{{ item.name }}</span>
</a-button>
</div>
<!-- 进度指示器 -->
<div class="progress-container">
<div class="progress-bar">
<div
class="progress-fill"
:style="{
width: `${((currentSection + 1) / navigationItems.length) * 100}%`
}"
></div>
</div>
<div class="progress-text">
{{ currentSection + 1 }} / {{ navigationItems.length }}
</div>
</div>
</div>
</a-card>
<!-- 审计方案内容 -->
<div class="audit-content">
<div
v-for="(item, index) in navigationItems"
:key="index"
:id="`section-${index}`"
class="audit-section"
>
<a-card style="margin-bottom: 20px" :bordered="false">
<template #title>
<span class="font-bold">{{ `${item.number}${item.name}` }}</span>
<span class="ml-4 text-gray-400" v-if="item.title">
{{ item.title }}
</span>
<!-- 所有表格都显示选择文件按钮 -->
<span class="ml-4 text-gray-400" v-if="item.showFileSelect">
<a-button
type="link"
@click="openDocSelect(index)"
class="select-file-btn"
>
<FolderOpenOutlined />
选择文件
</a-button>
</span>
</template>
<template #extra>
<a-space>
<!-- 所有表格都显示导出按钮 -->
<template v-if="item.mode === 'table' && item.tableOptions.length > 0">
<a-button
type="primary"
@click="handleExportTable(index)"
:loading="exportStates[`${item.tableType}_${item.tableOptions[item.currentTableIndex].value}`]"
>
<DownloadOutlined />
导出表格
</a-button>
</template>
<!-- AI生成按钮 -->
<a-button
type="primary"
@click="generateContent(index)"
:loading="item.generating"
>
<template #icon>
<RobotOutlined />
</template>
AI生成
</a-button>
</a-space>
</template>
<!-- 通用表格切换器 - 所有表格都显示 -->
<div class="flex justify-between items-center mb-4" v-if="item.mode === 'table'">
<div class="flex justify-start items-center">
<template v-if="item.tableOptions.length > 0">
<div class="flex items-center">
<span class="mr-2">切换表格</span>
<a-radio-group
v-model:value="item.currentTableIndex"
@change="(e) => handleTableChange(index, e.target.value)"
>
<a-radio-button
v-for="(option, optIndex) in item.tableOptions"
:key="optIndex"
:value="optIndex"
>
{{ option.title }}
</a-radio-button>
</a-radio-group>
</div>
<span class="ml-4">
共生成
<span class="text-red-400 mx-1 font-bold">
{{ item.data ? item.data.length : 0 }}
</span>
条数据
</span>
</template>
</div>
<!-- 所有表格都显示历史记录 -->
<a @click="openHistory(index)" class="cursor-pointer">历史记录</a>
</div>
<!-- 表格内容 -->
<a-table
v-if="item.mode === 'table' && item.columns"
:columns="item.columns"
:scroll="{ y: 500, x: 1000 }"
:pagination="false"
bordered
:data-source="item.data"
>
<template #bodyCell="{ column, record }">
<!-- 操作列 -->
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" size="small" @click="editRow(record, index)">编辑</a-button>
<a-button type="link" size="small" danger @click="deleteRow(record, index)">删除</a-button>
</a-space>
</template>
<!-- 测试结果列 -->
<template v-if="column.key === 'testResult' || column.key === 'result'">
<span class="text-green-400" v-if="record[column.key] === '通过'">
通过
</span>
<span class="text-red-400" v-else-if="record[column.key] === '不通过'">
不通过
</span>
<span class="text-gray-400" v-else>
{{ record[column.key] || '待检查' }}
</span>
</template>
<!-- 工作底稿索引列 -->
<template v-if="column.key === 'workPaperIndex'">
<div v-if="record.workPaperIndex && record.workPaperIndex.length > 0">
<div
v-for="(fileItem, fileIndex) in record.workPaperIndex"
:key="fileIndex"
class="file-item"
>
<img
src="@/assets/word.png"
style="width: 20px; height: 20px; vertical-align: middle; margin-right: 4px;"
alt=""
/>
<!-- 新格式支持 -->
<a
v-if="fileItem.fileUrl"
:href="fileItem.fileUrl"
target="_blank"
@click.stop="handleFileDownload(fileItem, $event)"
class="file-link"
style="color: #1890ff; text-decoration: none; cursor: pointer;"
>
{{ fileItem.fileName || '未命名文件' }}
</a>
<!-- 旧格式兼容 -->
<span v-else class="file-name">
{{ fileItem.fileName || fileItem || '未命名文件' }}
</span>
</div>
</div>
</template>
<!-- 文件索引列 -->
<template v-if="column.key === 'fileIndex'">
<div v-if="record.fileIndex && record.fileIndex.length > 0">
<div
v-for="(fileItem, fileIndex) in record.fileIndex"
:key="fileIndex"
>
<img
src="@/assets/word.png"
style="width: 20px; height: 20px"
alt=""
/>
<span class="cursor-pointer text-wrap">{{ fileItem }}</span>
</div>
</div>
</template>
<!-- 执行效果相关列 -->
<template v-if="column.key === 'goods' || column.key === 'normal' || column.key === 'bad'">
<span v-if="record[column.key] === '是'"></span>
<span v-else></span>
</template>
<!-- 审计内容列长文本处理 -->
<template v-if="column.key === 'auditContent'">
<div class="text-left">
{{ record[column.key] }}
<!-- <a-tooltip v-if="record[column.key] && record[column.key].length > 50" :title="record[column.key]">
<MoreOutlined class="ml-1" />
</a-tooltip>-->
</div>
</template>
<!-- 检查的证据及测试内容列 -->
<template v-if="column.key === 'checkEvidence'">
<div class="text-left">
{{ record[column.key] || '待填写' }}
<!-- <a-tooltip v-if="record[column.key] && record[column.key].length > 30" :title="record[column.key]">
<MoreOutlined class="ml-1" />
</a-tooltip>-->
</div>
</template>
</template>
</a-table>
<!-- 单选模式 -->
<template v-else-if="item.mode === 'radio'">
<div
class="mb-1"
v-for="(radio, radioIndex) in item.radioList"
:key="radioIndex"
>
<p class="mb-1">{{ radio.label }}</p>
<a-radio-group v-model:value="radio.content" :name="radio.label">
<a-radio value="是"></a-radio>
<a-radio value="否"></a-radio>
</a-radio-group>
</div>
</template>
<!-- 文本区域模式 -->
<template v-else>
<!-- 多文本区域 -->
<template v-if="item.textareaList && item.textareaList.length">
<div
class="py-2 box-border"
:key="textareaIndex"
v-for="(textarea, textareaIndex) in item.textareaList"
>
<p class="mb-1">{{ textarea.label }}</p>
<a-textarea
v-model:value="textarea.content"
:rows="item.rows || 8"
placeholder="点击(AI生成)按钮让AI为您生成该部分内容或直接填入"
class="content-textarea"
style="margin-top: 16px; background-color: #f0fdf4"
/>
</div>
</template>
<!-- 单文本区域 -->
<template v-else>
<a-textarea
v-model:value="item.content"
:rows="item.rows || 8"
placeholder="点击(AI生成)按钮让AI为您生成该部分内容或直接填入"
class="content-textarea"
style="margin-top: 16px; background-color: #f0fdf4"
/>
</template>
</template>
<!-- AI助手 -->
<div style="margin-top: 12px">
<div class="question-prompt">AI小助手</div>
<div class="textarea-with-button" style="width: 600px">
<a-textarea
v-model:value="item.suggestion"
:rows="3"
placeholder="请输入您的要求并回车..."
class="suggestion-textarea-inner"
@pressEnter="generateContent(index)"
/>
<a-button
type="danger"
size="small"
@click="generateContent(index)"
:loading="item.generating"
class="send-button-inner"
>
<template #icon>
<RobotOutlined />
</template>
发送
</a-button>
</div>
</div>
</a-card>
</div>
</div>
<!-- 返回顶部按钮 -->
<a-back-top :target="getScrollContainer" />
<FileModal ref="fileModal" :current-company-id="currentCompanyId" />
<HistoryModal
v-model:visible="showHistory"
:interface-name="currentInterfaceName"
:project-id="props.data?.id"
@select="handleHistorySelect"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro';
import { copyText } from '@/utils/common';
import type { PwlProject } from '@/api/pwl/pwlProject/model';
import {
RedoOutlined,
RobotOutlined,
UngroupOutlined,
QuestionCircleOutlined,
DownloadOutlined,
UploadOutlined,
FolderOpenOutlined,
FileOutlined,
MoreOutlined
} from '@ant-design/icons-vue';
import { generateAuditReport, downloadAuditReport } from '@/api/ai/auditReport';
import { getPwlProjectLibraryByIds } from '@/api/pwl/pwlProjectLibrary';
import * as auditContentApi from '@/api/ai/auditContent';
import FileModal from '@/views/pwl/pwlProject/components/components/FileModal.vue';
import HistoryModal from '@/views/pwl/pwlProject/components/components/HistoryModal.vue';
import useNavigationItems from './data/navigationItems';
import { tableConfigs, getTableConfig, createDataMapper, apiMethodMap, findTableConfigByInterface } from './data/tableCommon';
// 工具函数
import {
scrollToSection,
getScrollContainer,
handleScroll,
buildExportData,
hasContent
} from './data/funcs';
const useForm = Form.useForm;
const props = defineProps<{
visible: boolean;
data?: PwlProject | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// ========== 基础状态 ==========
const maxAble = ref(true);
const loading = ref(true);
const generatingAll = ref(false);
const currentSection = ref(0);
const combinedKbIds = ref('');
const currentCompanyId = ref<number>();
// 使用响应式导航项
const navigationItems = useNavigationItems();
// ========== 导出状态管理 ==========
const exportStates = reactive({});
// ========== 文档选择相关 ==========
const fileModal = ref();
const showDocSelect = ref(false);
const currentSectionIndex = ref(0);
const selectedDocList = ref<string[]>([]);
const selectedFileList = ref<string[]>([]);
const selectedFileKeys = ref<(string | number)[]>([]);
const checkedDirKeys = ref<(string | number)[]>([]);
const lastSelectedDirKeys = ref<(string | number)[]>([]);
const lastSelectedFileKeys = ref<(string | number)[]>([]);
// ========== 历史记录相关 ==========
const showHistory = ref(false);
const currentInterfaceName = ref('');
// ========== 三重一大数据 ==========
const tripleOneData = ref(null);
// ========== 国资管理数据 ==========
const stateAssetsData = ref(null);
// ========== 表格数据存储 ==========
const tableData = reactive({});
// ========== 表单数据 ==========
const form = reactive<PwlProject>({
// ... 表单属性保持不变
});
const { resetFields } = useForm(form);
// ========== 计算属性 ==========
const hasTripleOneData = computed(() => {
const section = navigationItems.value[2];
return section.data && section.data.length > 0;
});
// ========== 通用方法 ==========
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 表格切换处理 */
const handleTableChange = async (sectionIndex: number, tableIndex: number) => {
const section = navigationItems.value[sectionIndex];
if (!section || !section.tableOptions[tableIndex]) return;
section.currentTableIndex = tableIndex;
const tableOption = section.tableOptions[tableIndex];
// 动态加载列配置
if (typeof tableOption.columns === 'function') {
const module = await tableOption.columns();
section.columns = module.default || module;
} else {
section.columns = tableOption.columns;
}
// 加载对应的数据
const dataKey = `${section.tableType}_${tableOption.value}`;
if (tableData[dataKey]) {
section.data = tableData[dataKey];
} else {
section.data = [];
}
message.success(`已切换到:${tableOption.title}`);
};
/* 打开文档选择弹窗 */
const openDocSelect = (sectionIndex: number) => {
currentSectionIndex.value = sectionIndex;
showDocSelect.value = true;
const section = navigationItems.value[sectionIndex];
const currentTable = section.tableOptions[section.currentTableIndex];
const dataKey = `${section.tableType}_${currentTable.value}`;
checkedDirKeys.value = [...lastSelectedDirKeys.value];
selectedFileKeys.value = [...lastSelectedFileKeys.value];
selectedDocList.value = lastSelectedDirKeys.value.map(key => key.toString());
selectedFileList.value = lastSelectedFileKeys.value.map(key => key.toString());
fileModal.value.open(dataKey);
};
/* 通用表格内容生成方法 */
const generateTableContent = async (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex];
if (!section) {
message.error('章节不存在');
return;
}
const currentTable = section.tableOptions[section.currentTableIndex];
if (!currentTable) {
message.error('表格配置不存在');
return;
}
section.generating = true;
try {
// 构建请求参数
const requestData = {
projectId: props.data?.id || '',
kbIds: props.data?.kbId || '',
libraryIds: props.data?.libraryIds || '',
projectLibrary: props.data?.projectLibrary || '',
history: section.data?.length > 0 ? JSON.stringify(section.data, null, 2) : '',
suggestion: section.suggestion || '',
docList: checkedDirKeys.value,
fileList: selectedFileKeys.value,
// 重大经济决策调查表需要三重一大数据
...(currentTable.value === 'decisionTable' ? { data: tripleOneData.value } : {}),
// 预算管理审计表需要国资管理数据
...(currentTable.value === 'budgetManage' ? { data: stateAssetsData.value } : {}),
// 预算执行情况审计表需要预算管理审计的数据
...(currentTable.value === 'budgetExecution' ? { data: tableData['auditContent5_budgetManage'] } : {}),
};
// 获取对应的生成方法
const apiMethod = apiMethodMap[currentTable.value] || apiMethodMap.default;
const generateMethod = auditContentApi[apiMethod.generate];
if (!generateMethod) {
throw new Error(`生成方法 ${apiMethod.generate} 不存在`);
}
const result = await generateMethod(requestData);
if (result.code === 0 && result.data?.success) {
// 使用对应的数据映射函数
const dataMapper = createDataMapper(currentTable.value);
const mappedData = dataMapper(result.data.data);
// 保存数据
const dataKey = `${section.tableType}_${currentTable.value}`;
tableData[dataKey] = mappedData;
// 特殊处理数据存储
if (currentTable.value === 'tripleOne') {
tripleOneData.value = result.data.data;
} else if (currentTable.value === 'assets') {
// 存储国资管理数据,供预算管理审计表使用
stateAssetsData.value = result.data.data;
}
section.data = mappedData;
section.suggestion = '';
message.success(`成功生成 ${mappedData.length}${currentTable.title}记录`);
} else {
throw new Error(result.data?.error || result.message || '生成失败');
}
} catch (error: any) {
console.error(`生成表格内容失败:`, error);
message.error(`生成失败: ${error.message || '未知错误'}`);
} finally {
section.generating = false;
}
};
/* 通用表格导出方法 */
const handleExportTable = async (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex];
const currentTable = section.tableOptions[section.currentTableIndex];
if (!section.data || section.data.length === 0) {
message.warning(`没有可导出的${currentTable.title}数据`);
return;
}
// 使用正确的公司名称和审计时间
const companyName = form.name || props.data?.name || '未知公司';
const auditTime = form.expirationTime || props.data?.expirationTime || '未知时间';
// 设置导出状态
const exportKey = `${section.tableType}_${currentTable.value}`;
exportStates[exportKey] = true;
try {
const exportData = {
data: section.data,
companyName: companyName,
auditTime: auditTime
};
// 获取对应的导出方法
const apiMethod = apiMethodMap[currentTable.value] || apiMethodMap.default;
const exportMethod = auditContentApi[apiMethod.export];
if (!exportMethod) {
throw new Error(`导出方法 ${apiMethod.export} 不存在`);
}
const blob = await exportMethod(exportData);
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', `${currentTable.title}_${companyName}.xlsx`);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
message.success(`${currentTable.title}导出成功`);
} catch (error: any) {
console.error(`导出表格失败:`, error);
message.error(`导出失败: ${error.message || '未知错误'}`);
} finally {
exportStates[exportKey] = false;
}
};
/* 批量生成全部内容 */
const handleGenerateAll = async () => {
generatingAll.value = true;
try {
for (let i = 0; i < navigationItems.value.length; i++) {
// 跳过未完成的依赖项
if (i === 4 && !hasContent(navigationItems.value[3])) {
message.warning('请先生成第四项「被审计单位基本情况」');
continue;
}
if (i === 5 && !hasContent(navigationItems.value[4])) {
message.warning('请先生成第五项「审计内容和重点及审计方法」');
continue;
}
await generateContent(i);
}
message.success('全部内容生成完成');
} catch (error) {
console.error('批量生成失败:', error);
message.error('部分内容生成失败,请检查');
} finally {
generatingAll.value = false;
}
};
/* AI生成内容 */
const generateContent = async (sectionIndex: number, childIndex?: number) => {
const section = navigationItems.value[sectionIndex];
if (section.mode === 'table') {
await generateTableContent(sectionIndex);
} else {
await generateDefaultContent(sectionIndex, childIndex);
}
};
/* 默认内容生成方法 */
const generateDefaultContent = async (sectionIndex: number, childIndex?: number) => {
const section = navigationItems.value[sectionIndex];
const contextData = await buildExportData();
section.generating = true;
try {
section.content = await generateAuditReport({
kbId: form.kbId || '',
kbIds: combinedKbIds.value,
formCommit: section.formCommit || 0,
history: section.content || '',
suggestion: section.suggestion || '',
...contextData
});
} finally {
section.generating = false;
}
};
/* 打开历史记录 */
const openHistory = (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex];
const currentTable = section.tableOptions[section.currentTableIndex];
if (currentTable) {
// 通过表格配置获取接口名称
const tableConfig = getTableConfig(sectionIndex);
if (tableConfig && tableConfig.interfaceMap) {
currentInterfaceName.value = tableConfig.interfaceMap[currentTable.value] || '';
}
}
showHistory.value = true;
};
/* 处理历史记录选择 */
const handleHistorySelect = (record: any) => {
showHistory.value = false;
try {
const responseData = JSON.parse(record.responseData);
if (!responseData.success || !responseData.data) {
throw new Error('历史记录数据格式错误');
}
// 通过接口名称查找对应的表格配置
const tableConfig = findTableConfigByInterface(record.interfaceName);
if (!tableConfig) {
message.warning('不支持的历史记录类型');
return;
}
// 找到对应的章节索引
const sectionIndex = Object.keys(tableConfigs).findIndex(key => key === tableConfig.sectionType);
if (sectionIndex === -1) {
message.warning('未找到对应的章节');
return;
}
// 应用历史数据
applyHistoryData(sectionIndex, responseData.data, tableConfig.tableValue);
message.success('已应用选择的历史记录');
} catch (error: any) {
console.error('解析历史记录数据失败:', error);
message.error('历史记录数据解析失败');
}
};
/* 应用历史数据 */
const applyHistoryData = async (sectionIndex: number, data: any[], tableValue: string) => {
const section = navigationItems.value[sectionIndex];
if (!section) return;
const tableConfig = getTableConfig(sectionIndex);
if (!tableConfig) return;
// 找到对应的表格选项索引
const tableOptionIndex = section.tableOptions.findIndex(option => option.value === tableValue);
if (tableOptionIndex === -1) return;
// 使用数据映射函数
const dataMapper = createDataMapper(tableValue);
const mappedData = dataMapper(data);
// 保存数据
const dataKey = `${section.tableType}_${tableValue}`;
tableData[dataKey] = mappedData;
// 切换表格(如果当前不是这个表格)
if (section.currentTableIndex !== tableOptionIndex) {
await handleTableChange(sectionIndex, tableOptionIndex);
} else {
// 如果已经是当前表格,直接更新数据
section.data = mappedData;
}
// 特殊处理数据存储
if (tableValue === 'tripleOne') {
tripleOneData.value = data;
} else if (tableValue === 'assets') {
// 存储国资管理数据,供预算管理审计表使用
stateAssetsData.value = data;
}
};
/* 编辑行 */
const editRow = (record: any, sectionIndex: number) => {
const section = navigationItems.value[sectionIndex];
message.info(`编辑第${record.index}行数据`);
// 这里可以打开一个编辑弹窗
};
/* 删除行 */
const deleteRow = (record: any, sectionIndex: number) => {
const section = navigationItems.value[sectionIndex];
if (section.data) {
const index = section.data.findIndex((item: any) => item.key === record.key);
if (index !== -1) {
section.data.splice(index, 1);
message.success('删除成功');
}
}
};
/* 键盘快捷键处理 */
const handleKeydown = (event: KeyboardEvent) => {
if (!props.visible) return;
// Ctrl/Cmd + 数字键 1-9 快速跳转
if ((event.ctrlKey || event.metaKey) && event.key >= '1' && event.key <= '9') {
event.preventDefault();
const index = parseInt(event.key) - 1;
if (index < navigationItems.value.length) {
scrollToSection(index);
currentSection.value = index;
}
}
// 上下箭头键导航
if (event.key === 'ArrowUp' && event.ctrlKey) {
event.preventDefault();
const prevIndex = Math.max(0, currentSection.value - 1);
scrollToSection(prevIndex);
currentSection.value = prevIndex;
} else if (event.key === 'ArrowDown' && event.ctrlKey) {
event.preventDefault();
const nextIndex = Math.min(
navigationItems.value.length - 1,
currentSection.value + 1
);
scrollToSection(nextIndex);
currentSection.value = nextIndex;
}
};
// ========== 生命周期钩子 ==========
onMounted(() => {
const scrollContainer = getScrollContainer();
if (scrollContainer) {
scrollContainer.addEventListener('scroll', handleScroll);
}
document.addEventListener('keydown', handleKeydown);
});
onUnmounted(() => {
const scrollContainer = getScrollContainer();
if (scrollContainer) {
scrollContainer.removeEventListener('scroll', handleScroll);
}
document.removeEventListener('keydown', handleKeydown);
});
// ========== 观察者 ==========
watch(
() => props.visible,
async (visible) => {
if (visible) {
if (props.data) {
loading.value = true;
assignObject(form, props.data);
// 获取知识库并拼接 kbIds
if (props.data.libraryIds?.length > 0) {
try {
const result = await getPwlProjectLibraryByIds(props.data.libraryIds);
const kbIds = result.map((lib) => lib.kbId).filter((kbId) => kbId);
if (form.kbId) {
kbIds.unshift(form.kbId);
}
combinedKbIds.value = kbIds.join(',');
} catch (error) {
console.error('获取知识库失败:', error);
combinedKbIds.value = form.kbId || '';
}
} else {
combinedKbIds.value = form.kbId || '';
}
// 设置当前公司ID用于文档管理
currentCompanyId.value = props.data.companyId;
// 初始化所有表格的列配置
for (let i = 0; i < navigationItems.value.length; i++) {
const section = navigationItems.value[i];
if (section.tableOptions.length > 0) {
const tableOption = section.tableOptions[section.currentTableIndex];
if (typeof tableOption.columns === 'function') {
try {
const module = await tableOption.columns();
section.columns = module.default || module;
} catch (error) {
console.error(`加载表格${i}列配置失败:`, error);
section.columns = [];
}
} else {
section.columns = tableOption.columns;
}
}
}
// 重置到第一个章节
currentSection.value = 0;
loading.value = false;
}
} else {
resetFields();
combinedKbIds.value = '';
// 清空表格数据
Object.keys(tableData).forEach(key => {
delete tableData[key];
});
// 清空特殊数据
tripleOneData.value = null;
stateAssetsData.value = null;
}
}
);
</script>
<script lang="ts">
import * as MenuIcons from '@/layout/menu-icons';
export default {
name: 'PwlProjectInfo',
components: MenuIcons
};
</script>
<style lang="less" scoped>
@import './style/style.scss';
/* 表格样式优化 */
:deep(.ant-table) {
.ant-table-thead > tr > th {
background-color: #fafafa;
font-weight: 600;
}
.ant-table-tbody > tr > td {
padding: 8px 12px;
}
}
/* 长文本显示优化 */
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 表格操作按钮 */
.action-buttons {
display: flex;
gap: 8px;
}
/* 文件图标样式 */
.file-icon {
color: #1890ff;
margin-right: 4px;
}
/* 表格提示图标 */
.tooltip-icon {
color: #8c8c8c;
cursor: help;
}
</style>