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

2033 lines
64 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 danger @click="clearCurrentTable(index)">
清空表格
</a-button>
<a-button
type="primary"
@click="handleExportTable(index)"
:loading="exportStates[getTableKey(index)]"
>
<DownloadOutlined />
导出表格
</a-button>
<!-- 添加保存按钮 -->
<a-button
type="primary"
@click="saveCurrentTable(index)"
:loading="savingStates[getTableKey(index)]"
>
<SaveOutlined />
保存表格
</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">
<a-space>
<a-button type="primary" @click.native="editRow(index)"
>编辑</a-button
>
<a-button type="primary" danger @click.native="deleteRow(index)"
>删除</a-button
>
<a-button>导出</a-button>
<a-button @click="openEvidenceModal(index)"
>导出取证单</a-button
>
<a-button
type="primary"
@click="generateEvidence(index)"
:loading="generatingEvidenceStates[getTableKey(index)]"
>
生成取证单
</a-button>
<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>
</a-space>
</div>
<!-- 所有表格都显示历史记录 -->
<a @click="openHistory(index)" class="cursor-pointer">历史记录</a>
</div>
<!-- 表格内容 -->
<template v-if="item.mode === 'table' && item.columns">
<a-table
:row-selection="getRowSelection(index)"
:columns="item.columns"
:scroll="{ y: 500, x: 1000 }"
:pagination="false"
bordered
:row-key="(row) => row?.key ?? row?.id ?? row?.index"
: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
@click="openDoc(fileItem)"
v-for="(fileItem, fileIndex) in record.workPaperIndex"
:key="fileIndex"
>
<img
src="@/assets/word.png"
style="
width: 20px;
height: 20px;
vertical-align: middle;
margin-right: 4px;
"
alt=""
/>
<!-- 新格式支持 -->
<span
v-if="fileItem.fileUrl"
@click.stop="handleFilePreview(fileItem)"
class="file-link cursor-pointer text-wrap"
style="color: #1890ff; text-decoration: none"
>
{{ fileItem.fileName || '未命名文件' }}
</span>
<!-- 旧格式兼容 -->
<span v-else class="cursor-pointer text-wrap">
{{ 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] }}
</div>
</template>
<!-- 检查的证据及测试内容列 -->
<template v-if="column.key === 'checkEvidence'">
<div class="text-left">
{{ record[column.key] || '待填写' }}
</div>
</template>
</template>
</a-table>
<p v-if="item.extraTableTitle" class="font-bold my-2">{{
item.extraTableTitle
}}</p>
<a-table
v-if="item.extraColumns && item.extraColumns.length"
:columns="item.extraColumns"
:scroll="{ y: 500, x: 1000 }"
:pagination="false"
bordered
:data-source="item.extraData"
></a-table>
</template>
<!-- 单选模式 -->
<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"
/>
<!-- 编辑弹窗 -->
<EditModal
v-model:visible="editModalVisible"
:record="editingRecord"
:records="editingTargetRows"
:fields="editingFields"
@save="saveEdit"
/>
<EvidenceModal
v-model:visible="evidenceModalVisible"
:base-info="evidenceBaseInfo"
:project="data"
:selected-rows="evidenceSelectedRows"
/>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue';
import { Form, message, Modal, TableProps } 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,
SaveOutlined
} from '@ant-design/icons-vue';
import { generateAuditReport } from '@/api/ai/auditReport';
import { getPwlProjectLibraryByIds } from '@/api/pwl/pwlProjectLibrary';
import { addAiHistory } from '@/api/ai/aiHistory';
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 EvidenceModal from '@/views/pwl/pwlProject/components/components/EvidenceModal.vue';
import EditModal from './EditModal.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 savingStates = reactive({});
// ========== 生成取证单状态管理 ==========
const generatingEvidenceStates = reactive({});
// ========== 编辑相关状态 ==========
const editModalVisible = ref(false);
const editingRecord = ref<any>(null);
const editingSectionIndex = ref(0);
const editingTableKey = ref('');
const editingFields = ref<any[]>([]);
const editingTargetRows = ref<any[]>([]);
// ========== 文档选择相关 ==========
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 evidenceModalVisible = ref(false);
const evidenceBaseInfo = reactive({
caseIndex: '',
projectName: '',
auditedTarget: '',
auditMatter: ''
});
const evidenceSelectedRows = ref<any[]>([]);
// ========== 表格选择 ==========
const selectedRowsMap = reactive<Record<number, any[]>>({});
const selectedRowKeysMap = reactive<Record<number, (string | number)[]>>({});
const getRowSelection = (
sectionIndex: number
): TableProps['rowSelection'] => {
return {
selectedRowKeys: selectedRowKeysMap[sectionIndex] || [],
onChange: (selectedRowKeys: (string | number)[], selectedRows: any[]) => {
selectedRowsMap[sectionIndex] = selectedRows;
selectedRowKeysMap[sectionIndex] = selectedRowKeys;
}
};
};
// ========== 历史记录相关 ==========
const showHistory = ref(false);
const currentInterfaceName = ref('');
// ========== 三重一大数据 ==========
const tripleOneData = ref<any>(null);
// ========== 国资管理数据 ==========
const stateAssetsData = ref<any>(null);
// ========== 表格数据存储 ==========
const tableData = reactive({});
// ========== 统一的表格生成数据存储 ==========
const tableGenerationData = reactive<
Record<
string,
{
requestData?: any;
responseData?: any;
interfaceName?: string;
timestamp?: number;
}
>
>({});
// ========== 表单数据 ==========
const form = reactive<PwlProject>({
// ... 表单属性保持不变
});
const { resetFields } = useForm(form);
// ========== 计算属性 ==========
const hasTripleOneData = computed(() => {
const section: any = navigationItems.value[2];
return section.data && section.data.length > 0;
});
// ========== 工具函数 ==========
const getTableKey = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
if (!section || !section.tableOptions || section.tableOptions.length === 0)
return '';
const currentTable = section.tableOptions[section.currentTableIndex];
if (!currentTable) return '';
return `${section.tableType}_${currentTable.value}`;
};
const getTableInfo = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
if (!section || !section.tableOptions || section.tableOptions.length === 0)
return null;
const currentTable = section.tableOptions[section.currentTableIndex];
if (!currentTable) return null;
return {
section,
currentTable,
tableKey: `${section.tableType}_${currentTable.value}`,
tableValue: currentTable.value
};
};
// ========== 通用方法 ==========
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
/* 表格切换处理 */
const handleTableChange = async (
sectionIndex: number,
tableIndex: number
) => {
const section: any = 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 tableKey = `${section.tableType}_${tableOption.value}`;
if (tableData[tableKey]) {
section.data = tableData[tableKey];
} else {
section.data = [];
}
selectedRowsMap[sectionIndex] = [];
selectedRowKeysMap[sectionIndex] = [];
message.success(`已切换到:${tableOption.title}`);
};
/* 打开文档选择弹窗 */
const openDocSelect = (sectionIndex: number) => {
currentSectionIndex.value = sectionIndex;
showDocSelect.value = true;
const tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) return;
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(tableInfo.tableKey);
};
/* 打开取证单预览弹窗 */
const openEvidenceModal = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
const tableInfo = getTableInfo(sectionIndex);
evidenceBaseInfo.caseIndex =
(form as any).caseIndex || props.data?.caseIndex || '';
evidenceBaseInfo.projectName = form.name || props.data?.name || '';
evidenceBaseInfo.auditedTarget =
(form as any).nickname || (props.data as any)?.nickname || '';
const baseMatter = section?.title || section?.name || '';
const tableTitle = tableInfo?.currentTable?.title || '';
evidenceBaseInfo.auditMatter = tableTitle
? `${baseMatter ? `${baseMatter} - ` : ''}${tableTitle}`
: baseMatter;
evidenceSelectedRows.value = selectedRowsMap[sectionIndex] || [];
evidenceModalVisible.value = true;
};
/* 保存表格生成数据 */
const saveTableGenerationData = (
tableKey: string,
requestData: any,
responseData: any,
tableValue?: string
) => {
// 根据tableKey找到对应的表格配置获取正确的接口名称
let correctInterfaceName = tableKey;
// 从tableKey中提取sectionType和tableValue
const [sectionType, value] = tableKey.split('_');
// 查找对应的表格配置
const sectionKeys = Object.keys(tableConfigs);
const sectionIndex = sectionKeys.findIndex((key) => key === sectionType);
if (sectionIndex !== -1) {
const tableConfig = getTableConfig(sectionIndex);
if (
tableConfig &&
tableConfig.interfaceMap &&
tableConfig.interfaceMap[value || tableValue]
) {
correctInterfaceName = tableConfig.interfaceMap[value || tableValue];
}
}
tableGenerationData[tableKey] = {
requestData,
responseData,
interfaceName: correctInterfaceName, // 保存正确的接口名称
timestamp: Date.now()
};
};
/* 获取表格生成数据 */
const getTableGenerationData = (tableKey: string) => {
return tableGenerationData[tableKey] || null;
};
/* 通用表格内容生成方法 */
const generateTableContent = async (sectionIndex: number) => {
const tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) {
message.error('表格配置不存在');
return;
}
const { section, currentTable, tableKey } = tableInfo;
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) {
// 保存表格生成数据
saveTableGenerationData(
tableKey,
requestData,
result.data,
currentTable.value
);
// 使用对应的数据映射函数
const dataMapper = createDataMapper(currentTable.value);
const mappedData = dataMapper(result.data.data);
// 处理不同类型的表格数据
if (currentTable.value === 'personnel') {
// 对于人员编制管理审计,需要分别处理主表格和额外表格数据
tableData[tableKey] = mappedData.mainData;
section.data = mappedData.mainData;
section.extraData = mappedData.extraData || []; // 设置额外表格数据
// 如果有额外数据确保extraColumns存在
if (mappedData.extraData && mappedData.extraData.length > 0) {
if (!section.extraColumns) {
// 从配置中获取extraColumns
const tableConfig = getTableConfig(sectionIndex);
if (tableConfig && tableConfig.extraColumns) {
section.extraColumns = tableConfig.extraColumns;
}
}
}
} else {
// 其他表格的正常处理
tableData[tableKey] = mappedData;
section.data = mappedData;
}
// 特殊处理数据存储
if (currentTable.value === 'tripleOne') {
tripleOneData.value = result.data.data;
} else if (currentTable.value === 'assets') {
// 存储国资管理数据,供预算管理审计表使用
stateAssetsData.value = result.data.data;
}
section.suggestion = '';
const recordCount =
currentTable.value === 'personnel'
? mappedData.mainData.length
: mappedData.length;
message.success(
`成功生成 ${recordCount} 条${currentTable.title}记录` +
(currentTable.value === 'personnel' &&
mappedData.extraData?.length > 0
? `,以及 ${mappedData.extraData.length} 条福利费明细记录`
: '')
);
} else {
throw new Error(result.data?.error || result.message || '生成失败');
}
} catch (error: any) {
console.error(`生成表格内容失败:`, error);
message.error(`生成失败: ${error.message || '未知错误'}`);
} finally {
section.generating = false;
}
};
/* 生成取证单 */
const generateEvidence = async (sectionIndex: number) => {
const tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) {
message.error('表格配置不存在');
return;
}
const { section, currentTable, tableKey } = tableInfo;
// 设置生成状态
generatingEvidenceStates[tableKey] = true;
try {
// 获取当前表格的生成数据
const generationData = getTableGenerationData(tableKey);
// 构建history参数使用当前表格的responseData内容
let historyData = '';
if (generationData && generationData.responseData) {
historyData = JSON.stringify(generationData.responseData, null, 2);
} else if (section.data && section.data.length > 0) {
// 如果没有生成数据,使用当前表格数据
historyData = JSON.stringify(
{
success: true,
data: section.data,
data_source: currentTable.value
},
null,
2
);
}
// 检查history是否为空 - 新增的验证逻辑
if (!historyData || historyData.trim() === '') {
message.error(
'无法生成取证单:没有找到相关的审计数据,请先生成表格内容'
);
return;
}
// 调用generateAuditEvidence API
const projectCaseIndex =
(form as any).caseIndex || props.data?.caseIndex || '';
const requestData = {
// 基础信息
caseIndex: projectCaseIndex,
projectName: form.name || props.data?.name || '',
auditedTarget:
(form as any).nickname || (props.data as any)?.nickname || '',
auditMatter:
currentTable.title || section?.title || section?.name || '',
summaryTitle: section?.title || section?.name || '',
auditRecord: '',
auditFinding: '',
evidenceBasis: '',
handling: '',
suggestion: section.suggestion || '',
attachment: '',
auditors: '',
compileDate: new Date().toISOString().split('T')[0], // 当前日期
providerOpinion: '',
providerDate: '',
attachmentPages: '',
feedbackDeadline: '',
// 历史内容(用于工作流生成)
history: historyData,
// 项目相关参数
projectId: props.data?.id || '',
kbIds: props.data?.kbId || '',
libraryIds: props.data?.libraryIds || '',
projectLibrary: props.data?.projectLibrary || '',
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'] }
: {}),
// 用户信息(后端会自动设置,这里可以留空或传空字符串)
userName: ''
};
const apiResult = await auditContentApi.generateAuditEvidence(
requestData
);
if (apiResult.code === 0 && apiResult.data?.success) {
message.success('取证单生成成功');
console.log('生成的取证单数据:', apiResult.data);
// 自动打开取证单预览弹窗并填充数据
evidenceBaseInfo.caseIndex = projectCaseIndex;
evidenceBaseInfo.projectName =
apiResult.data.projectName || form.name || props.data?.name || '';
evidenceBaseInfo.auditedTarget =
apiResult.data.auditedTarget ||
(form as any).nickname ||
(props.data as any)?.nickname ||
'';
evidenceBaseInfo.auditMatter =
apiResult.data.auditMatter ||
currentTable.title ||
section?.title ||
section?.name ||
'';
// 将生成的取证单数据作为选中的行数据传入,包含所有字段
const evidenceData = {
// 基础信息字段
caseIndex: projectCaseIndex,
projectName: apiResult.data.projectName || '',
auditedTarget: apiResult.data.auditedTarget || '',
auditMatter: apiResult.data.auditMatter || '',
summaryTitle: apiResult.data.summaryTitle || '',
auditRecord: apiResult.data.auditRecord || '',
auditFinding: apiResult.data.auditFinding || '',
evidenceBasis: apiResult.data.evidenceBasis || '',
handling: apiResult.data.handling || '',
suggestion: apiResult.data.suggestion || '',
auditors: apiResult.data.auditors || '',
compileDate: apiResult.data.compileDate || '',
providerOpinion: apiResult.data.providerOpinion || '',
providerDate: apiResult.data.providerDate || '',
attachmentPages: apiResult.data.attachmentPages || '',
feedbackDeadline: apiResult.data.feedbackDeadline || '',
// 处理附件字段,如果是数组则转换为字符串
attachment: Array.isArray(apiResult.data.attachment)
? apiResult.data.attachment.join('\n')
: apiResult.data.attachment || '',
// 确保页码字段有默认值
pageIndex: '1',
pageTotal: '1',
// 保留其他可能的字段如success、processing_time、generated_time等
success: apiResult.data.success,
processing_time: apiResult.data.processing_time,
generated_time: apiResult.data.generated_time
};
evidenceSelectedRows.value = [evidenceData];
evidenceModalVisible.value = true;
} else {
throw new Error(
apiResult.data?.error || apiResult.message || '生成取证单失败'
);
}
} catch (error: any) {
console.error('生成取证单失败:', error);
message.error(`生成取证单失败: ${error.message || '未知错误'}`);
} finally {
generatingEvidenceStates[tableKey] = false;
}
};
/* 通用表格导出方法 */
const handleExportTable = async (sectionIndex: number) => {
const tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) return;
const { section, currentTable, tableKey } = tableInfo;
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 || '未知时间';
// 设置导出状态
exportStates[tableKey] = 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[tableKey] = 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: any = navigationItems.value[sectionIndex];
if (section.mode === 'table') {
await generateTableContent(sectionIndex);
} else {
await generateDefaultContent(sectionIndex, childIndex);
}
};
/* 默认内容生成方法 */
const generateDefaultContent = async (
sectionIndex: number,
childIndex?: number
) => {
const section: any = 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 tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) return;
const { section, currentTable } = tableInfo;
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,
record.requestData,
record.interfaceName
);
message.success('已应用选择的历史记录');
} catch (error: any) {
console.error('解析历史记录数据失败:', error);
message.error('历史记录数据解析失败');
}
};
/* 应用历史数据 */
const applyHistoryData = async (
sectionIndex: number,
data: any[],
tableValue: string,
requestDataStr?: string,
interfaceName?: string
) => {
const section: any = 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;
// 解析请求数据
let requestData = null;
try {
requestData = requestDataStr ? JSON.parse(requestDataStr) : null;
} catch (e) {
console.warn('解析历史请求数据失败:', e);
}
// 构建响应数据对象
const responseData = {
success: true,
data: data,
data_source: interfaceName || tableValue
};
// 保存表格生成数据
const tableKey = `${section.tableType}_${tableValue}`;
saveTableGenerationData(tableKey, requestData, responseData, interfaceName);
// 使用数据映射函数
const dataMapper = createDataMapper(tableValue);
const mappedData = dataMapper(data);
// 保存数据
if (tableValue === 'personnel') {
tableData[tableKey] = mappedData.mainData;
section.extraData = mappedData.extraData || []; // 设置额外表格数据
// 如果有额外数据确保extraColumns存在
if (mappedData.extraData && mappedData.extraData.length > 0) {
if (!section.extraColumns) {
section.extraColumns = tableConfig.extraColumns || [];
}
}
} else {
tableData[tableKey] = mappedData;
}
// 切换表格(如果当前不是这个表格)
if (section.currentTableIndex !== tableOptionIndex) {
await handleTableChange(sectionIndex, tableOptionIndex);
} else {
// 如果已经是当前表格,直接更新数据
if (tableValue === 'personnel') {
section.data = mappedData.mainData;
} else {
section.data = mappedData;
}
}
// 特殊处理数据存储
if (tableValue === 'tripleOne') {
tripleOneData.value = data;
} else if (tableValue === 'assets') {
// 存储国资管理数据,供预算管理审计表使用
stateAssetsData.value = data;
}
// 清空选中行
selectedRowsMap[sectionIndex] = [];
selectedRowKeysMap[sectionIndex] = [];
};
/* 编辑行 */
const editRow = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
if (!section || !section.columns) return;
const selectedRows = selectedRowsMap[sectionIndex] || [];
if (!selectedRows.length) {
message.warning('请先选择要编辑的行');
return;
}
// 获取可编辑的字段(排除操作列)
editingFields.value = section.columns.filter(
(col) =>
col.key !== 'action' &&
col.dataIndex !== 'key' &&
col.dataIndex !== 'index' &&
col.dataIndex !== 'workPaperIndex' &&
col.key !== 'workPaperIndex'
);
// 将所有选中的行传递给编辑弹窗
editingRecord.value = { ...selectedRows[0] };
editingTargetRows.value = selectedRows.map((row: any) => ({ ...row }));
editingSectionIndex.value = sectionIndex;
// 记录当前编辑的表格key
const tableInfo = getTableInfo(sectionIndex);
if (tableInfo) {
editingTableKey.value = tableInfo.tableKey;
} else {
editingTableKey.value = '';
}
editModalVisible.value = true;
};
/* 保存编辑 */
const saveEdit = (updatedRecords: any | any[]) => {
const section: any = navigationItems.value[editingSectionIndex.value];
if (!section || !section.data) return;
const targets =
editingTargetRows.value && editingTargetRows.value.length
? editingTargetRows.value
: editingRecord.value
? [editingRecord.value]
: [];
if (!targets.length) {
message.warning('未找到需要编辑的行');
return;
}
const updatesArray = Array.isArray(updatedRecords)
? updatedRecords
: [updatedRecords];
// 只提取允许编辑的字段,避免把首行的其他值覆盖到所有行
const allowedKeys = new Set<string>();
const collectKeys = (field: any) => {
if (field?.dataIndex) allowedKeys.add(field.dataIndex);
if (field?.children && Array.isArray(field.children)) {
field.children.forEach((child: any) => collectKeys(child));
}
};
editingFields.value.forEach((field: any) => collectKeys(field));
const buildApplyFields = (source: any) => {
const applied: Record<string, any> = {};
allowedKeys.forEach((key) => {
if (key === 'key' || key === 'index') return;
if (Object.prototype.hasOwnProperty.call(source, key)) {
applied[key] = source[key];
}
});
return applied;
};
const stripResponseFields = (data: any) => {
const { workPaperIndex, key, index, ...rest } = data;
return rest;
};
const findRawIndex = (
responseList: any[],
targetRow: any,
fallbackUiIndex: number
) => {
const hasValidId =
targetRow?.id !== undefined &&
targetRow?.id !== null &&
targetRow?.id !== '';
if (hasValidId) {
const byId = responseList.findIndex(
(item: any) => item.id === targetRow.id
);
if (byId !== -1) return byId;
}
if (
typeof targetRow?.key === 'number' &&
targetRow.key >= 0 &&
targetRow.key < responseList.length
) {
return targetRow.key;
}
if (targetRow?.key !== undefined && targetRow?.key !== null) {
const byKey = responseList.findIndex(
(item: any) => item.key !== undefined && item.key === targetRow.key
);
if (byKey !== -1) return byKey;
}
if (
fallbackUiIndex !== -1 &&
fallbackUiIndex >= 0 &&
fallbackUiIndex < responseList.length
) {
return fallbackUiIndex;
}
return -1;
};
let hasApplied = false;
targets.forEach((targetRow: any, idx: number) => {
const updateSource = updatesArray[idx] || updatesArray[0] || {};
const appliedFields = buildApplyFields(updateSource);
if (Object.keys(appliedFields).length === 0) {
return;
}
hasApplied = true;
const uiIndex = section.data.findIndex(
(item: any) => item.key === targetRow.key
);
if (uiIndex !== -1) {
const originalRecord = section.data[uiIndex];
const merged = { ...originalRecord, ...appliedFields };
merged.key = originalRecord.key;
if (Object.prototype.hasOwnProperty.call(originalRecord, 'index')) {
merged.index = originalRecord.index;
}
section.data.splice(uiIndex, 1, merged);
}
if (editingTableKey.value && tableGenerationData[editingTableKey.value]) {
const responseData =
tableGenerationData[editingTableKey.value].responseData;
if (
responseData &&
responseData.data &&
Array.isArray(responseData.data)
) {
const rawIndex = findRawIndex(responseData.data, targetRow, uiIndex);
if (rawIndex !== -1) {
responseData.data[rawIndex] = {
...responseData.data[rawIndex],
...stripResponseFields(appliedFields)
};
} else {
responseData.data.push(stripResponseFields(appliedFields));
}
tableGenerationData[editingTableKey.value].responseData =
responseData;
}
}
});
if (editingTableKey.value) {
tableData[editingTableKey.value] = section.data;
}
const currentSelections = selectedRowsMap[editingSectionIndex.value] || [];
selectedRowsMap[editingSectionIndex.value] = currentSelections.map(
(row: any) =>
section.data.find((item: any) => item.key === row.key) || row
);
if (!hasApplied) {
message.warning('没有可应用的字段');
return;
}
message.success('编辑成功');
editModalVisible.value = false;
editingTableKey.value = '';
editingTargetRows.value = [];
};
/* 删除行(多选) */
const deleteRow = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
if (!section || !section.data) return;
const selectedRows = selectedRowsMap[sectionIndex] || [];
if (!selectedRows.length) {
message.warning('请先选择要删除的行');
return;
}
const tableInfo = getTableInfo(sectionIndex);
const responseData =
tableInfo &&
tableInfo.tableKey &&
tableGenerationData[tableInfo.tableKey] &&
tableGenerationData[tableInfo.tableKey].responseData &&
tableGenerationData[tableInfo.tableKey].responseData.data &&
Array.isArray(tableGenerationData[tableInfo.tableKey].responseData.data)
? tableGenerationData[tableInfo.tableKey].responseData
: null;
const findRawIndex = (
responseList: any[],
targetRow: any,
fallbackUiIndex: number
) => {
const hasValidId =
targetRow?.id !== undefined &&
targetRow?.id !== null &&
targetRow?.id !== '';
if (hasValidId) {
const byId = responseList.findIndex(
(item: any) => item.id === targetRow.id
);
if (byId !== -1) return byId;
}
if (
typeof targetRow?.key === 'number' &&
targetRow.key >= 0 &&
targetRow.key < responseList.length
) {
return targetRow.key;
}
if (targetRow?.key !== undefined && targetRow?.key !== null) {
const byKey = responseList.findIndex(
(item: any) => item.key !== undefined && item.key === targetRow.key
);
if (byKey !== -1) return byKey;
}
if (
fallbackUiIndex !== -1 &&
fallbackUiIndex >= 0 &&
fallbackUiIndex < responseList.length
) {
return fallbackUiIndex;
}
return -1;
};
const uiIndexesToDelete: number[] = [];
const rawIndexesToDelete: number[] = [];
selectedRows.forEach((row: any) => {
const uiIndex = section.data.findIndex(
(item: any) => item.key === row.key
);
if (uiIndex !== -1) uiIndexesToDelete.push(uiIndex);
if (responseData) {
const rawIndex = findRawIndex(responseData.data, row, uiIndex);
if (rawIndex !== -1) rawIndexesToDelete.push(rawIndex);
}
});
const uniqueUiIndexes = Array.from(new Set(uiIndexesToDelete)).sort(
(a, b) => b - a
);
uniqueUiIndexes.forEach((idx) => {
section.data.splice(idx, 1);
});
// 删除后重排 key/index保证后续操作按 key(索引) 能正确匹配
section.data = section.data.map((item: any, index: number) => {
const next: any = { ...item, key: index };
if (item && Object.prototype.hasOwnProperty.call(item, 'index')) {
next.index = index + 1;
}
return next;
});
if (tableInfo && tableInfo.tableKey) {
tableData[tableInfo.tableKey] = section.data;
}
if (responseData && tableInfo && tableInfo.tableKey) {
const uniqueRawIndexes = Array.from(new Set(rawIndexesToDelete)).sort(
(a, b) => b - a
);
uniqueRawIndexes.forEach((idx) => {
responseData.data.splice(idx, 1);
});
tableGenerationData[tableInfo.tableKey].responseData = responseData;
}
selectedRowsMap[sectionIndex] = [];
selectedRowKeysMap[sectionIndex] = [];
message.success(
`已删除${uniqueUiIndexes.length || selectedRows.length}条记录`
);
};
/* 保存当前表格到历史记录 */
const saveCurrentTable = async (sectionIndex: number) => {
const tableInfo = getTableInfo(sectionIndex);
if (!tableInfo) return;
const { section, currentTable, tableKey } = tableInfo;
if (!section || !section.data || section.data.length === 0) {
message.warning('没有数据可保存');
return;
}
// 设置保存状态
savingStates[tableKey] = true;
try {
// 获取表格生成数据
const generationData = getTableGenerationData(tableKey);
if (!generationData || !generationData.responseData) {
message.warning('没有找到该表格的生成记录,请先生成数据');
return;
}
// 关键修复:从表格配置中获取正确的接口名称
const tableConfig = getTableConfig(sectionIndex);
let correctInterfaceName = generationData.interfaceName; // 默认为当前值
if (
tableConfig &&
tableConfig.interfaceMap &&
tableConfig.interfaceMap[currentTable.value]
) {
correctInterfaceName = tableConfig.interfaceMap[currentTable.value];
}
const normalizedResponseData = (() => {
const src = generationData.responseData;
if (!src || !src.data || !Array.isArray(src.data)) return src;
const cloned = JSON.parse(JSON.stringify(src));
cloned.data = cloned.data.map((row: any) => {
const workPaperIndex = row?.workPaperIndex;
if (!Array.isArray(workPaperIndex)) return row;
const normalized = workPaperIndex
.map((item: any) => {
if (typeof item === 'string') return item;
if (item && typeof item === 'object') {
const fileId = item.fileId || item.file_id || '';
const fileName = item.fileName || item.file_name || '';
const fileUrl = item.fileUrl || item.url || '';
return `${fileId}||${fileName}||${fileUrl}`;
}
return '';
})
.filter((s: string) => s && s.trim() !== '');
return {
...row,
workPaperIndex: normalized
};
});
return cloned;
})();
const aiHistory = {
projectId: props.data?.id,
interfaceName: correctInterfaceName, // 使用正确的接口名称
requestData: generationData.requestData
? JSON.stringify(generationData.requestData)
: '',
responseData: JSON.stringify(normalizedResponseData)
// 其他字段后端会自动设置
};
// 调用后端保存接口
await addAiHistory(aiHistory);
message.success('保存成功');
} catch (error: any) {
console.error('保存表格失败:', error);
message.error('保存失败: ' + (error.message || '未知错误'));
} finally {
savingStates[tableKey] = false;
}
};
const clearCurrentTable = (sectionIndex: number) => {
const section: any = navigationItems.value[sectionIndex];
if (
!section ||
section.mode !== 'table' ||
!section.tableOptions ||
section.tableOptions.length === 0
)
return;
Modal.confirm({
title: '确认清空表格?',
content: '将清空当前章节下所有表格选项卡的数据,且无法恢复。',
okText: '清空',
okType: 'danger',
cancelText: '取消',
onOk: () => {
section.tableOptions.forEach((option: any) => {
const tableKey = `${section.tableType}_${option.value}`;
tableData[tableKey] = [];
const gen = tableGenerationData[tableKey];
if (gen && gen.responseData) {
const next = { ...gen.responseData, data: [] };
tableGenerationData[tableKey].responseData = next;
}
if (exportStates[tableKey] !== undefined)
delete exportStates[tableKey];
if (savingStates[tableKey] !== undefined)
delete savingStates[tableKey];
if (generatingEvidenceStates[tableKey] !== undefined)
delete generatingEvidenceStates[tableKey];
if (option.value === 'tripleOne') {
tripleOneData.value = null;
}
if (option.value === 'assets') {
stateAssetsData.value = null;
}
});
section.data = [];
message.success('已清空表格');
selectedRowsMap[sectionIndex] = [];
selectedRowKeysMap[sectionIndex] = [];
}
});
};
const openDoc = (fileItem) => {
console.log(fileItem);
// window.open(
// `http://view.officeapps.live.com/op/view.aspx?src=文件地址`,
// '_blank'
// );
};
/* 键盘快捷键处理 */
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 && 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: any = 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 = '';
evidenceModalVisible.value = false;
// 清空表格数据
Object.keys(tableData).forEach((key) => {
delete tableData[key];
});
// 清空表格生成数据
Object.keys(tableGenerationData).forEach((key) => {
delete tableGenerationData[key];
});
// 清空特殊数据
tripleOneData.value = null;
stateAssetsData.value = null;
// 清空选中行
Object.keys(selectedRowsMap).forEach((key) => {
delete selectedRowsMap[key as any];
});
Object.keys(selectedRowKeysMap).forEach((key) => {
delete selectedRowKeysMap[key as any];
});
// 清空生成取证单状态
Object.keys(generatingEvidenceStates).forEach((key) => {
delete generatingEvidenceStates[key as any];
});
}
}
);
const handleFilePreview = (file) => {
console.log(
file.fileUrl,
`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
file.fileUrl
)}`
);
window.open(
`https://view.officeapps.live.com/op/view.aspx?src=${encodeURIComponent(
file.fileUrl
)}`
);
};
</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>