Compare commits

...

2 Commits

3 changed files with 634 additions and 61 deletions

View File

@@ -0,0 +1,182 @@
<template>
<a-modal
:visible="visible"
title="编辑行数据"
@ok="handleOk"
@cancel="handleCancel"
:confirm-loading="loading"
width="600px"
>
<a-form layout="vertical">
<template v-for="field in processedFields" :key="field.key">
<a-form-item :label="field.title" v-if="!field.children">
<template v-if="field.type === 'textarea'">
<a-textarea
v-model:value="formData[field.dataIndex]"
:rows="4"
:placeholder="`请输入${field.title}`"
/>
</template>
<template v-else-if="field.dataIndex === 'workPaperIndex'">
<a-textarea
v-model:value="formData[field.dataIndex]"
:rows="4"
:placeholder="'每行一个文件格式为file_id||文件名||url'"
/>
</template>
<template v-else>
<a-input
v-model:value="formData[field.dataIndex]"
:placeholder="`请输入${field.title}`"
/>
</template>
</a-form-item>
<!-- 处理嵌套字段如职务下的党内行政 -->
<template v-else-if="field.children">
<div class="nested-fields">
<div class="field-group-title">{{ field.title }}</div>
<div class="field-group-content">
<a-form-item
v-for="childField in field.children"
:key="childField.key"
:label="childField.title"
class="nested-field-item"
>
<a-input
v-model:value="formData[childField.dataIndex]"
:placeholder="`请输入${childField.title}`"
/>
</a-form-item>
</div>
</div>
</template>
</template>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, computed } from 'vue';
import { message } from 'ant-design-vue';
const props = defineProps<{
visible: boolean;
record: any;
fields: any[];
}>();
const emit = defineEmits(['update:visible', 'save']);
const loading = ref(false);
const formData = ref({});
const hasWorkPaperIndexField = computed(() => {
return (props.fields || []).some((field) => {
return field?.dataIndex === 'workPaperIndex' || field?.key === 'workPaperIndex';
});
});
// 处理字段,将嵌套结构展平
const processedFields = computed(() => {
const processed: any[] = [];
props.fields.forEach(field => {
if (field.children && Array.isArray(field.children)) {
// 处理有子字段的情况(如职务)
processed.push({
...field,
children: field.children.flatMap(child =>
child.children && Array.isArray(child.children)
? child.children // 如果是多层嵌套,直接取孙子字段
: child // 否则就是子字段
)
});
} else {
processed.push(field);
}
});
return processed;
});
watch(() => props.visible, (visible) => {
if (visible && props.record) {
// 深拷贝记录数据
const recordCopy = JSON.parse(JSON.stringify(props.record));
// 特殊处理workPaperIndex如果是对象数组转换为字符串数组
if (hasWorkPaperIndexField.value && recordCopy.workPaperIndex && Array.isArray(recordCopy.workPaperIndex)) {
recordCopy.workPaperIndex = recordCopy.workPaperIndex.map(item => {
if (typeof item === 'object') {
// 如果是对象转换为字符串格式file_id||文件名||url
return `${item.fileId || ''}||${item.fileName || ''}||${item.fileUrl || ''}`;
}
return item;
}).join('\n');
}
formData.value = recordCopy;
}
}, { immediate: true });
const handleOk = () => {
if (!formData.value) {
message.warning('没有数据可保存');
return;
}
// 处理workPaperIndex将字符串转换回对象数组
const dataToSave = JSON.parse(JSON.stringify(formData.value));
if (hasWorkPaperIndexField.value && dataToSave.workPaperIndex && typeof dataToSave.workPaperIndex === 'string') {
const lines = dataToSave.workPaperIndex.split('\n').filter(line => line.trim() !== '');
dataToSave.workPaperIndex = lines.map(line => {
const parts = line.split('||');
if (parts.length >= 3) {
return {
fileId: parts[0] || '',
fileName: parts[1] || '',
fileUrl: parts[2] || ''
};
}
return line;
});
}
loading.value = true;
emit('save', dataToSave);
loading.value = false;
emit('update:visible', false);
};
const handleCancel = () => {
emit('update:visible', false);
};
</script>
<style scoped>
.nested-fields {
margin-bottom: 16px;
border: 1px solid #f0f0f0;
border-radius: 4px;
padding: 12px;
background-color: #fafafa;
}
.field-group-title {
font-weight: 600;
margin-bottom: 8px;
color: #1890ff;
}
.field-group-content {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 12px;
}
.nested-field-item {
margin-bottom: 0;
}
</style>

View File

@@ -163,6 +163,13 @@ function parseWorkPaperIndex(workPaperIndex) {
if (!workPaperIndex || !Array.isArray(workPaperIndex)) return []; if (!workPaperIndex || !Array.isArray(workPaperIndex)) return [];
return workPaperIndex.map(item => { return workPaperIndex.map(item => {
if (item && typeof item === 'object') {
return {
fileId: item.fileId || item.file_id || '',
fileName: item.fileName || item.file_name || '',
fileUrl: item.fileUrl || item.url || ''
};
}
if (typeof item === 'string') { if (typeof item === 'string') {
const parts = item.split('||'); const parts = item.split('||');
if (parts.length >= 3) { if (parts.length >= 3) {
@@ -231,22 +238,20 @@ export function createDataMapper(type: string) {
budgetManage: (data) => data.map((item, index) => ({ budgetManage: (data) => data.map((item, index) => ({
key: index, key: index,
index: index + 1, index: index + 1,
...item,
budgetSubject: item.budgetSubject || '-', budgetSubject: item.budgetSubject || '-',
// 指标来源部分字段映射 indicatorSource: item.indicatorSource,
indicatorSource: item.indicatorSource, // 指标来源 total: formatAmount(item.indicatorSourceTotal),
total: formatAmount(item.indicatorSourceTotal), // 指标来源-合计 lastYearBalance: formatAmount(item.indicatorSourceLastYearBalance),
lastYearBalance: formatAmount(item.indicatorSourceLastYearBalance), // 指标来源-上年结余 initialBudget: formatAmount(item.indicatorSourceInitialBudget),
initialBudget: formatAmount(item.indicatorSourceInitialBudget), // 指标来源-年初部门预算 additionalBudget: formatAmount(item.indicatorSourceAdditionalBudget),
additionalBudget: formatAmount(item.indicatorSourceAdditionalBudget), // 指标来源-追加(减)预算 indicatorUseTotal: formatAmount(item.indicatorUseTotal),
// 指标运用部分字段映射 appropriationSubtotal: formatAmount(item.indicatorUseAppropriationSubtotal),
indicatorUseTotal: formatAmount(item.indicatorUseTotal), // 指标运用-合计 appropriation: formatAmount(item.indicatorUseAppropriation),
appropriationSubtotal: formatAmount(item.indicatorUseAppropriationSubtotal), // 指标运用-拨款小计 salaryPayment: formatAmount(item.indicatorUseSalaryPayment),
appropriation: formatAmount(item.indicatorUseAppropriation), // 指标运用-拨款 govProcurement: formatAmount(item.indicatorUseGovProcurement),
salaryPayment: formatAmount(item.indicatorUseSalaryPayment), // 指标运用-工资统发 indicatorBalance: formatAmount(item.indicatorBalance),
govProcurement: formatAmount(item.indicatorUseGovProcurement), // 指标运用-政府采购 workPaperIndex: parseWorkPaperIndex(item.workPaperIndex),
// 其他字段
indicatorBalance: formatAmount(item.indicatorBalance), // 指标结余
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex), // 工作底稿索引
})), })),
investmentSituation: (data) => data.map((item, index) => ({ investmentSituation: (data) => data.map((item, index) => ({
key: index, key: index,

View File

@@ -156,14 +156,29 @@
<a-space> <a-space>
<!-- 所有表格都显示导出按钮 --> <!-- 所有表格都显示导出按钮 -->
<template v-if="item.mode === 'table' && item.tableOptions.length > 0"> <template v-if="item.mode === 'table' && item.tableOptions.length > 0">
<a-button
danger
@click="clearCurrentTable(index)"
>
清空表格
</a-button>
<a-button <a-button
type="primary" type="primary"
@click="handleExportTable(index)" @click="handleExportTable(index)"
:loading="exportStates[`${item.tableType}_${item.tableOptions[item.currentTableIndex].value}`]" :loading="exportStates[getTableKey(index)]"
> >
<DownloadOutlined /> <DownloadOutlined />
导出表格 导出表格
</a-button> </a-button>
<!-- 添加保存按钮 -->
<a-button
type="primary"
@click="saveCurrentTable(index)"
:loading="savingStates[getTableKey(index)]"
>
<SaveOutlined />
保存表格
</a-button>
</template> </template>
<!-- AI生成按钮 --> <!-- AI生成按钮 -->
@@ -303,9 +318,6 @@
<template v-if="column.key === 'auditContent'"> <template v-if="column.key === 'auditContent'">
<div class="text-left"> <div class="text-left">
{{ record[column.key] }} {{ 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> </div>
</template> </template>
@@ -313,9 +325,6 @@
<template v-if="column.key === 'checkEvidence'"> <template v-if="column.key === 'checkEvidence'">
<div class="text-left"> <div class="text-left">
{{ record[column.key] || '待填写' }} {{ 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> </div>
</template> </template>
</template> </template>
@@ -405,11 +414,19 @@
:project-id="props.data?.id" :project-id="props.data?.id"
@select="handleHistorySelect" @select="handleHistorySelect"
/> />
<!-- 编辑弹窗 -->
<EditModal
v-model:visible="editModalVisible"
:record="editingRecord"
:fields="editingFields"
@save="saveEdit"
/>
</a-drawer> </a-drawer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue'; import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue';
import { Form, message } from 'ant-design-vue'; import { Form, message, Modal } from 'ant-design-vue';
import { assignObject } from 'ele-admin-pro'; import { assignObject } from 'ele-admin-pro';
import { copyText } from '@/utils/common'; import { copyText } from '@/utils/common';
import type { PwlProject } from '@/api/pwl/pwlProject/model'; import type { PwlProject } from '@/api/pwl/pwlProject/model';
@@ -421,15 +438,16 @@ import {
DownloadOutlined, DownloadOutlined,
UploadOutlined, UploadOutlined,
FolderOpenOutlined, FolderOpenOutlined,
FileOutlined, SaveOutlined
MoreOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { generateAuditReport, downloadAuditReport } from '@/api/ai/auditReport'; import { generateAuditReport, downloadAuditReport } from '@/api/ai/auditReport';
import { getPwlProjectLibraryByIds } from '@/api/pwl/pwlProjectLibrary'; import { getPwlProjectLibraryByIds } from '@/api/pwl/pwlProjectLibrary';
import { addAiHistory } from '@/api/ai/aiHistory';
import * as auditContentApi from '@/api/ai/auditContent'; import * as auditContentApi from '@/api/ai/auditContent';
import FileModal from '@/views/pwl/pwlProject/components/components/FileModal.vue'; import FileModal from '@/views/pwl/pwlProject/components/components/FileModal.vue';
import HistoryModal from '@/views/pwl/pwlProject/components/components/HistoryModal.vue'; import HistoryModal from '@/views/pwl/pwlProject/components/components/HistoryModal.vue';
import EditModal from './EditModal.vue';
import useNavigationItems from './data/navigationItems'; import useNavigationItems from './data/navigationItems';
import { tableConfigs, getTableConfig, createDataMapper, apiMethodMap, findTableConfigByInterface } from './data/tableCommon'; import { tableConfigs, getTableConfig, createDataMapper, apiMethodMap, findTableConfigByInterface } from './data/tableCommon';
@@ -469,6 +487,16 @@ const navigationItems = useNavigationItems();
// ========== 导出状态管理 ========== // ========== 导出状态管理 ==========
const exportStates = reactive({}); const exportStates = reactive({});
// ========== 保存状态管理 ==========
const savingStates = reactive({});
// ========== 编辑相关状态 ==========
const editModalVisible = ref(false);
const editingRecord = ref<any>(null);
const editingSectionIndex = ref(0);
const editingTableKey = ref('');
const editingFields = ref<any[]>([]);
// ========== 文档选择相关 ========== // ========== 文档选择相关 ==========
const fileModal = ref(); const fileModal = ref();
const showDocSelect = ref(false); const showDocSelect = ref(false);
@@ -492,6 +520,14 @@ const stateAssetsData = ref(null);
// ========== 表格数据存储 ========== // ========== 表格数据存储 ==========
const tableData = reactive({}); const tableData = reactive({});
// ========== 统一的表格生成数据存储 ==========
const tableGenerationData = reactive<Record<string, {
requestData?: any;
responseData?: any;
interfaceName?: string;
timestamp?: number;
}>>({});
// ========== 表单数据 ========== // ========== 表单数据 ==========
const form = reactive<PwlProject>({ const form = reactive<PwlProject>({
// ... 表单属性保持不变 // ... 表单属性保持不变
@@ -505,6 +541,29 @@ const hasTripleOneData = computed(() => {
return section.data && section.data.length > 0; return section.data && section.data.length > 0;
}); });
// ========== 工具函数 ==========
const getTableKey = (sectionIndex: number) => {
const section = 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 = 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 */ /* 更新visible */
@@ -529,9 +588,9 @@ const handleTableChange = async (sectionIndex: number, tableIndex: number) => {
} }
// 加载对应的数据 // 加载对应的数据
const dataKey = `${section.tableType}_${tableOption.value}`; const tableKey = `${section.tableType}_${tableOption.value}`;
if (tableData[dataKey]) { if (tableData[tableKey]) {
section.data = tableData[dataKey]; section.data = tableData[tableKey];
} else { } else {
section.data = []; section.data = [];
} }
@@ -544,32 +603,59 @@ const openDocSelect = (sectionIndex: number) => {
currentSectionIndex.value = sectionIndex; currentSectionIndex.value = sectionIndex;
showDocSelect.value = true; showDocSelect.value = true;
const section = navigationItems.value[sectionIndex]; const tableInfo = getTableInfo(sectionIndex);
const currentTable = section.tableOptions[section.currentTableIndex]; if (!tableInfo) return;
const dataKey = `${section.tableType}_${currentTable.value}`;
checkedDirKeys.value = [...lastSelectedDirKeys.value]; checkedDirKeys.value = [...lastSelectedDirKeys.value];
selectedFileKeys.value = [...lastSelectedFileKeys.value]; selectedFileKeys.value = [...lastSelectedFileKeys.value];
selectedDocList.value = lastSelectedDirKeys.value.map(key => key.toString()); selectedDocList.value = lastSelectedDirKeys.value.map(key => key.toString());
selectedFileList.value = lastSelectedFileKeys.value.map(key => key.toString()); selectedFileList.value = lastSelectedFileKeys.value.map(key => key.toString());
fileModal.value.open(dataKey); fileModal.value.open(tableInfo.tableKey);
};
/* 保存表格生成数据 */
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 generateTableContent = async (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex]; const tableInfo = getTableInfo(sectionIndex);
if (!section) { if (!tableInfo) {
message.error('章节不存在');
return;
}
const currentTable = section.tableOptions[section.currentTableIndex];
if (!currentTable) {
message.error('表格配置不存在'); message.error('表格配置不存在');
return; return;
} }
const { section, currentTable, tableKey } = tableInfo;
section.generating = true; section.generating = true;
try { try {
@@ -602,13 +688,15 @@ const generateTableContent = async (sectionIndex: number) => {
const result = await generateMethod(requestData); const result = await generateMethod(requestData);
if (result.code === 0 && result.data?.success) { if (result.code === 0 && result.data?.success) {
// 保存表格生成数据
saveTableGenerationData(tableKey, requestData, result.data, currentTable.value);
// 使用对应的数据映射函数 // 使用对应的数据映射函数
const dataMapper = createDataMapper(currentTable.value); const dataMapper = createDataMapper(currentTable.value);
const mappedData = dataMapper(result.data.data); const mappedData = dataMapper(result.data.data);
// 保存数据 // 保存数据
const dataKey = `${section.tableType}_${currentTable.value}`; tableData[tableKey] = mappedData;
tableData[dataKey] = mappedData;
// 特殊处理数据存储 // 特殊处理数据存储
if (currentTable.value === 'tripleOne') { if (currentTable.value === 'tripleOne') {
@@ -634,8 +722,10 @@ const generateTableContent = async (sectionIndex: number) => {
/* 通用表格导出方法 */ /* 通用表格导出方法 */
const handleExportTable = async (sectionIndex: number) => { const handleExportTable = async (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex]; const tableInfo = getTableInfo(sectionIndex);
const currentTable = section.tableOptions[section.currentTableIndex]; if (!tableInfo) return;
const { section, currentTable, tableKey } = tableInfo;
if (!section.data || section.data.length === 0) { if (!section.data || section.data.length === 0) {
message.warning(`没有可导出的${currentTable.title}数据`); message.warning(`没有可导出的${currentTable.title}数据`);
@@ -647,8 +737,7 @@ const handleExportTable = async (sectionIndex: number) => {
const auditTime = form.expirationTime || props.data?.expirationTime || '未知时间'; const auditTime = form.expirationTime || props.data?.expirationTime || '未知时间';
// 设置导出状态 // 设置导出状态
const exportKey = `${section.tableType}_${currentTable.value}`; exportStates[tableKey] = true;
exportStates[exportKey] = true;
try { try {
const exportData = { const exportData = {
@@ -681,7 +770,7 @@ const handleExportTable = async (sectionIndex: number) => {
console.error(`导出表格失败:`, error); console.error(`导出表格失败:`, error);
message.error(`导出失败: ${error.message || '未知错误'}`); message.error(`导出失败: ${error.message || '未知错误'}`);
} finally { } finally {
exportStates[exportKey] = false; exportStates[tableKey] = false;
} }
}; };
@@ -744,8 +833,10 @@ const generateDefaultContent = async (sectionIndex: number, childIndex?: number)
/* 打开历史记录 */ /* 打开历史记录 */
const openHistory = (sectionIndex: number) => { const openHistory = (sectionIndex: number) => {
const section = navigationItems.value[sectionIndex]; const tableInfo = getTableInfo(sectionIndex);
const currentTable = section.tableOptions[section.currentTableIndex]; if (!tableInfo) return;
const { section, currentTable } = tableInfo;
if (currentTable) { if (currentTable) {
// 通过表格配置获取接口名称 // 通过表格配置获取接口名称
@@ -783,7 +874,7 @@ const handleHistorySelect = (record: any) => {
} }
// 应用历史数据 // 应用历史数据
applyHistoryData(sectionIndex, responseData.data, tableConfig.tableValue); applyHistoryData(sectionIndex, responseData.data, tableConfig.tableValue, record.requestData, record.interfaceName);
message.success('已应用选择的历史记录'); message.success('已应用选择的历史记录');
} catch (error: any) { } catch (error: any) {
console.error('解析历史记录数据失败:', error); console.error('解析历史记录数据失败:', error);
@@ -792,7 +883,7 @@ const handleHistorySelect = (record: any) => {
}; };
/* 应用历史数据 */ /* 应用历史数据 */
const applyHistoryData = async (sectionIndex: number, data: any[], tableValue: string) => { const applyHistoryData = async (sectionIndex: number, data: any[], tableValue: string, requestDataStr?: string, interfaceName?: string) => {
const section = navigationItems.value[sectionIndex]; const section = navigationItems.value[sectionIndex];
if (!section) return; if (!section) return;
@@ -803,13 +894,31 @@ const applyHistoryData = async (sectionIndex: number, data: any[], tableValue: s
const tableOptionIndex = section.tableOptions.findIndex(option => option.value === tableValue); const tableOptionIndex = section.tableOptions.findIndex(option => option.value === tableValue);
if (tableOptionIndex === -1) return; 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 dataMapper = createDataMapper(tableValue);
const mappedData = dataMapper(data); const mappedData = dataMapper(data);
// 保存数据 // 保存数据
const dataKey = `${section.tableType}_${tableValue}`; tableData[tableKey] = mappedData;
tableData[dataKey] = mappedData;
// 切换表格(如果当前不是这个表格) // 切换表格(如果当前不是这个表格)
if (section.currentTableIndex !== tableOptionIndex) { if (section.currentTableIndex !== tableOptionIndex) {
@@ -831,20 +940,293 @@ const applyHistoryData = async (sectionIndex: number, data: any[], tableValue: s
/* 编辑行 */ /* 编辑行 */
const editRow = (record: any, sectionIndex: number) => { const editRow = (record: any, sectionIndex: number) => {
const section = navigationItems.value[sectionIndex]; const section = navigationItems.value[sectionIndex];
message.info(`编辑第${record.index}行数据`); if (!section || !section.columns) return;
// 这里可以打开一个编辑弹窗
// 获取可编辑的字段(排除操作列)
editingFields.value = section.columns.filter(col =>
col.key !== 'action' &&
col.dataIndex !== 'key' &&
col.dataIndex !== 'index' &&
col.dataIndex !== 'workPaperIndex' &&
col.key !== 'workPaperIndex'
);
editingRecord.value = { ...record };
editingSectionIndex.value = sectionIndex;
// 记录当前编辑的表格key
const tableInfo = getTableInfo(sectionIndex);
if (tableInfo) {
editingTableKey.value = tableInfo.tableKey;
}
editModalVisible.value = true;
};
/* 保存编辑 */
const saveEdit = (updatedRecord: any) => {
const section = navigationItems.value[editingSectionIndex.value];
if (!section || !section.data) return;
const index = section.data.findIndex((item: any) => item.key === updatedRecord.key);
if (index !== -1) {
// 保留原有数据的其他字段
const originalRecord = section.data[index];
section.data[index] = { ...originalRecord, ...updatedRecord };
// 更新对应的响应数据
if (editingTableKey.value && tableGenerationData[editingTableKey.value]) {
// 获取当前的响应数据
const responseData = tableGenerationData[editingTableKey.value].responseData;
// 如果响应数据存在且包含data数组
if (responseData && responseData.data && Array.isArray(responseData.data)) {
// 找到对应的原始数据索引
const dataIndex = (() => {
const hasValidId =
updatedRecord.id !== undefined &&
updatedRecord.id !== null &&
updatedRecord.id !== '';
if (hasValidId) {
const byId = responseData.data.findIndex((item: any) => item.id === updatedRecord.id);
if (byId !== -1) return byId;
}
// 默认 key 由 mapper 使用 index 生成,后端原始数据通常没有 key
if (
typeof updatedRecord.key === 'number' &&
updatedRecord.key >= 0 &&
updatedRecord.key < responseData.data.length
) {
return updatedRecord.key;
}
const hasKey = updatedRecord.key !== undefined && updatedRecord.key !== null;
if (hasKey) {
const byKey = responseData.data.findIndex(
(item: any) => item.key !== undefined && item.key === updatedRecord.key
);
if (byKey !== -1) return byKey;
}
return -1;
})();
if (dataIndex !== -1) {
// 更新原始响应数据中的对应记录
responseData.data[dataIndex] = {
...responseData.data[dataIndex],
...(() => {
const { workPaperIndex, key, index, ...rest } = updatedRecord;
return rest;
})()
};
// 更新tableGenerationData中的响应数据
tableGenerationData[editingTableKey.value].responseData = responseData;
} else {
// 如果没找到对应的原始数据,直接将编辑后的数据添加到响应数据中
// 这可能发生在手动添加行的情况下
responseData.data.push((() => {
const { workPaperIndex, key, index, ...rest } = updatedRecord;
return rest;
})());
tableGenerationData[editingTableKey.value].responseData = responseData;
}
}
}
message.success('编辑成功');
}
editModalVisible.value = false;
editingTableKey.value = ''; // 清空编辑的表格key
}; };
/* 删除行 */ /* 删除行 */
const deleteRow = (record: any, sectionIndex: number) => { const deleteRow = (record: any, sectionIndex: number) => {
const section = navigationItems.value[sectionIndex]; const section = navigationItems.value[sectionIndex];
if (section.data) { if (!section || !section.data) return;
const index = section.data.findIndex((item: any) => item.key === record.key);
if (index !== -1) { const uiIndex = section.data.findIndex((item: any) => item.key === record.key);
section.data.splice(index, 1); if (uiIndex === -1) return;
// 同步删除原始 responseData删除前就计算 rawIndex避免 key 重排导致错位)
const tableInfo = getTableInfo(sectionIndex);
if (tableInfo && tableInfo.tableKey && tableGenerationData[tableInfo.tableKey]) {
const responseData = tableGenerationData[tableInfo.tableKey].responseData;
if (responseData && responseData.data && Array.isArray(responseData.data)) {
const rawIndex = (() => {
const hasValidId = record?.id !== undefined && record?.id !== null && record?.id !== '';
if (hasValidId) {
const byId = responseData.data.findIndex((item: any) => item.id === record.id);
if (byId !== -1) return byId;
}
// 默认 key 由 mapper 使用 index 生成
if (
typeof record.key === 'number' &&
record.key >= 0 &&
record.key < responseData.data.length
) {
return record.key;
}
const hasKey = record?.key !== undefined && record?.key !== null;
if (hasKey) {
const byKey = responseData.data.findIndex((item: any) => item.key !== undefined && item.key === record.key);
if (byKey !== -1) return byKey;
}
// 最后兜底:使用 UI 索引
if (uiIndex >= 0 && uiIndex < responseData.data.length) return uiIndex;
return -1;
})();
if (rawIndex !== -1) {
responseData.data.splice(rawIndex, 1);
tableGenerationData[tableInfo.tableKey].responseData = responseData;
}
}
}
// 删除 UI 行
section.data.splice(uiIndex, 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;
}
message.success('删除成功'); message.success('删除成功');
};
/* 保存当前表格到历史记录 */
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 = 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 (option.value === 'tripleOne') {
tripleOneData.value = null;
}
if (option.value === 'assets') {
stateAssetsData.value = null;
}
});
section.data = [];
message.success('已清空表格');
}
});
}; };
/* 键盘快捷键处理 */ /* 键盘快捷键处理 */
@@ -956,6 +1338,10 @@ watch(
Object.keys(tableData).forEach(key => { Object.keys(tableData).forEach(key => {
delete tableData[key]; delete tableData[key];
}); });
// 清空表格生成数据
Object.keys(tableGenerationData).forEach(key => {
delete tableGenerationData[key];
});
// 清空特殊数据 // 清空特殊数据
tripleOneData.value = null; tripleOneData.value = null;
stateAssetsData.value = null; stateAssetsData.value = null;