chore(config): 添加项目配置文件和隐私协议

- 新增 .editorconfig 文件统一代码风格配置
- 新增 .env 环境变量配置文件
- 添加开发和生产环境的环境变量配置
- 配置 ESLint 忽略规则文件
- 设置代码检查配置文件 .eslintrc.js
- 添加 Git 忽略文件规则
- 创建 Prettier 格式化忽略规则
- 添加隐私政策和服务协议HTML文件
- 实现访问密钥编辑组件基础结构
This commit is contained in:
2026-02-07 16:33:13 +08:00
commit 92a6a32868
1384 changed files with 224513 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<template #extra>
<Extra/>
</template>
<a-card :bordered="false">
<!-- 表格 -->
<ele-pro-table
ref="tableRef"
row-key="userId"
:columns="columns"
:datasource="list"
cache-key="proSystemUserTable"
>
<template #toolbar>
<a-date-picker v-model:value="where.itemYear" value-format="YYYY" picker="year" @change="onYear"/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'completed'">
<!-- <a-badge :count="record.balance" :number-style="{ backgroundColor: '#52c41a' }"/>-->
{{ record.balance || 0 }}
</template>
<template v-if="column.key === 'incomplete'">
{{ record.points || 0 }}
<!-- <a-badge :count="record.points" :number-style="{ backgroundColor: '#ff0000' }"/>-->
</template>
<template v-if="column.key === 'fans'">
{{ record.fans || 0 }}
</template>
</template>
</ele-pro-table>
</a-card>
</a-page-header>
</template>
<script lang="ts" setup>
import {ref, onMounted} from 'vue';
import type {EleProTable} from 'ele-admin-pro/es';
import type {
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import {getPageTitle} from "@/utils/common";
import Extra from "@/views/pwl/pwlProject/components/extra.vue";
import {pwlProjectCount} from "@/api/pwl/pwlProject";
import {User} from "@/api/system/user/model";
import useSearch from "@/utils/use-search";
import {PwlProjectParam} from "@/api/pwl/pwlProject/model";
const list = ref<User[]>([]);
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格列配置
const columns = ref<ColumnItem[]>([
// {
// title: 'ID',
// dataIndex: 'userId',
// width: 90,
// showSorterTooltip: false
// },
{
title: '姓名',
dataIndex: 'realName',
align: 'center'
},
{
title: '未完成项目',
dataIndex: 'incomplete',
key: 'incomplete',
align: 'center'
},
{
title: '已完成项目',
dataIndex: 'completed',
key: 'completed',
align: 'center'
},
{
title: '签字报告数量',
dataIndex: 'fans',
key: 'fans',
align: 'center'
}
]);
// 表单数据
const {where} = useSearch<PwlProjectParam>({
id: undefined,
type: undefined,
itemYear: undefined,
keywords: undefined
});
onMounted(() => {
pwlProjectCount().then(data => {
list.value = data || [];
})
});
</script>
<script lang="ts">
export default {
name: 'PwlCount'
};
</script>

View File

@@ -0,0 +1,296 @@
<template>
<a-modal
:visible="visible"
title="编辑行数据"
@ok="handleOk"
@cancel="handleCancel"
:confirm-loading="loading"
width="600px"
>
<template #title>
<div class="modal-title">
<span>编辑行数据</span>
<a-tag v-if="displayRecords.length > 1" color="blue" class="ml-2">
同步编辑 {{ displayRecords.length }}
</a-tag>
</div>
</template>
<a-alert
v-if="displayRecords.length > 1"
type="info"
show-icon
style="margin-bottom: 12px"
:message="`已选择 ${displayRecords.length} 条记录,将把当前修改同步到这些记录`"
/>
<a-list
v-if="displayRecords.length > 1"
size="small"
bordered
:data-source="displayRecords"
style="margin-bottom: 12px; max-height: 160px; overflow-y: auto"
>
<template #renderItem="{ item, index }">
<a-list-item
:class="['record-item', { active: index === selectedRecordIndex }]"
@click="selectRecord(index)"
>
<span class="record-label">#{{ index + 1 }}</span>
</a-list-item>
</template>
</a-list>
<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="activeFormData[field.dataIndex]"
:rows="4"
:placeholder="`请输入${field.title}`"
/>
</template>
<template v-else-if="field.dataIndex === 'workPaperIndex'">
<a-textarea
v-model:value="activeFormData[field.dataIndex]"
:rows="4"
:placeholder="'每行一个文件格式为file_id||文件名||url'"
/>
</template>
<template v-else>
<a-input
v-model:value="activeFormData[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="activeFormData[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[];
records?: any[];
}>();
const emit = defineEmits(['update:visible', 'save']);
const loading = ref(false);
const formDataList = ref<any[]>([]);
const selectedRecordIndex = ref(0);
const displayRecords = computed(() => {
if (props.records && Array.isArray(props.records) && props.records.length) {
return props.records;
}
if (props.record) return [props.record];
return [];
});
const transformRecordToFormData = (record: any) => {
if (!record) return {};
const recordCopy = JSON.parse(JSON.stringify(record));
if (
hasWorkPaperIndexField.value &&
recordCopy.workPaperIndex &&
Array.isArray(recordCopy.workPaperIndex)
) {
recordCopy.workPaperIndex = recordCopy.workPaperIndex
.map((item: any) => {
if (typeof item === 'object') {
return `${item.fileId || ''}||${item.fileName || ''}||${item.fileUrl || ''}`;
}
return item;
})
.join('\n');
}
return recordCopy;
};
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) {
selectedRecordIndex.value = 0;
formDataList.value = displayRecords.value.map((rec) =>
transformRecordToFormData(rec)
);
}
},
{ immediate: true }
);
const handleOk = () => {
if (!formDataList.value || formDataList.value.length === 0) {
message.warning('没有数据可保存');
return;
}
// 处理workPaperIndex将字符串转换回对象数组
const dataToSave = formDataList.value.map((item) => {
const cloned = JSON.parse(JSON.stringify(item || {}));
if (hasWorkPaperIndexField.value && cloned.workPaperIndex && typeof cloned.workPaperIndex === 'string') {
const lines = cloned.workPaperIndex.split('\n').filter((line: string) => line.trim() !== '');
cloned.workPaperIndex = lines.map((line: string) => {
const parts = line.split('||');
if (parts.length >= 3) {
return {
fileId: parts[0] || '',
fileName: parts[1] || '',
fileUrl: parts[2] || ''
};
}
return line;
});
}
return cloned;
});
loading.value = true;
emit('save', dataToSave);
loading.value = false;
emit('update:visible', false);
};
const handleCancel = () => {
emit('update:visible', false);
};
const selectRecord = (index: number) => {
if (index < 0 || index >= displayRecords.value.length) return;
selectedRecordIndex.value = index;
if (!formDataList.value[index]) {
formDataList.value[index] = transformRecordToFormData(
displayRecords.value[index]
);
}
};
const activeFormData = computed({
get() {
if (!displayRecords.value.length) return {};
if (!formDataList.value[selectedRecordIndex.value]) {
formDataList.value[selectedRecordIndex.value] = transformRecordToFormData(
displayRecords.value[selectedRecordIndex.value]
);
}
return formDataList.value[selectedRecordIndex.value] || {};
},
set(val) {
if (!displayRecords.value.length) return;
formDataList.value[selectedRecordIndex.value] = val || {};
}
});
</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;
}
.modal-title {
display: flex;
align-items: center;
}
.record-label {
display: inline-block;
width: 36px;
color: #888;
}
.record-text {
color: #333;
}
.record-item {
cursor: pointer;
transition: background-color 0.2s;
}
.record-item:hover {
background-color: #f5f5f5;
}
.record-item.active {
background-color: #e6f7ff;
}
</style>

View File

@@ -0,0 +1,82 @@
<!-- 用户导入弹窗 -->
<template>
<ele-modal
:width="520"
:footer="null"
title="批量导入"
:visible="visible"
@update:visible="updateVisible"
>
<a-spin :spinning="loading">
<a-upload-dragger
accept=".xls,.xlsx"
:show-upload-list="false"
:customRequest="doUpload"
style="padding: 24px 0; margin-bottom: 16px"
>
<p class="ant-upload-drag-icon">
<cloud-upload-outlined />
</p>
<p class="ant-upload-hint">将文件拖到此处或点击上传</p>
</a-upload-dragger>
</a-spin>
<div class="ele-text-center">
<span>只能上传xlsxlsx文件导入模板和导出模板格式一致</span>
</div>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { message } from 'ant-design-vue/es';
import { CloudUploadOutlined } from '@ant-design/icons-vue';
import {importPwlProject} from "@/api/pwl/pwlProject";
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
defineProps<{
// 是否打开弹窗
visible: boolean;
}>();
// 导入请求状态
const loading = ref(false);
/* 上传 */
const doUpload = ({ file }) => {
if (
![
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
].includes(file.type)
) {
message.error('只能选择 excel 文件');
return false;
}
if (file.size / 1024 / 1024 > 10) {
message.error('大小不能超过 10MB');
return false;
}
loading.value = true;
importPwlProject(file)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
return false;
};
/* 更新 visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
</script>

View File

@@ -0,0 +1,636 @@
<template>
<!-- 审计核查弹窗 -->
<a-drawer
:width="`90%`"
:visible="visible"
:confirm-loading="loading"
:maxable="true"
:title="`审计核查 - ${form.name || ''}`"
:body-style="{ paddingBottom: '8px', background: '#f5f5f5' }"
@update:visible="updateVisible"
:maskClosable="false"
:footer="null"
>
<div class="audit-container">
<!-- 顶部操作区 -->
<div class="action-bar">
<div class="title">审计发现问题情况表</div>
<div class="buttons">
<a-space>
<!-- 附件上传 -->
<a-upload
v-model:file-list="fileList"
:before-upload="beforeUpload"
:max-count="1"
:show-upload-list="true"
accept=".pdf,.doc,.docx,.xls,.xlsx,.txt"
>
<a-button class="action-button">
<template #icon><UploadOutlined /></template>
上传附件
</a-button>
</a-upload>
<a-button type="primary" @click="addProblem" class="action-button">
<template #icon><PlusOutlined /></template>
新增问题
</a-button>
<a-button @click="generateProblems" :loading="generating" class="action-button">
<template #icon><RobotOutlined /></template>
AI生成问题
</a-button>
<a-button @click="exportExcel" class="action-button">
<template #icon><ExportOutlined /></template>
导出Excel
</a-button>
<a-button type="danger" @click="saveData" class="action-button">
<template #icon><SaveOutlined /></template>
保存数据
</a-button>
</a-space>
</div>
</div>
<!-- 附件信息显示 -->
<a-card v-if="fileList.length > 0" :bordered="false" class="file-info-card">
<template #title>
<PaperClipOutlined /> 已上传附件
</template>
<div class="file-info">
<div class="file-item" v-for="file in fileList" :key="file.uid">
<div class="file-name">{{ file.name }}</div>
<a-button type="link" size="small" @click="removeFile(file.uid)" danger>
删除
</a-button>
</div>
</div>
</a-card>
<!-- 问题列表表格 -->
<a-card :bordered="false" class="table-card">
<a-table
:dataSource="problemList"
:columns="columns"
:scroll="{ x: 1800 }"
:pagination="false"
rowKey="id"
size="middle"
bordered
>
<template #bodyCell="{ column, record, index }">
<!-- 序号列 -->
<template v-if="column.dataIndex === 'index'">
{{ index + 1 }}
</template>
<!-- 是否存在问题列 -->
<template v-if="column.dataIndex === 'hasProblem'">
<a-tag :color="record.hasProblem === 'true' ? 'red' : 'green'">
{{ record.hasProblem === 'true' ? '是' : '否' }}
</a-tag>
</template>
<!-- 金额列 -->
<template v-if="column.dataIndex === 'amount'">
<a-input-number
v-model:value="record.amount"
:min="0"
:precision="2"
style="width: 100%"
:bordered="false"
/>
</template>
<!-- 整改类型列 -->
<template v-if="column.dataIndex === 'rectificationType'">
<a-select
v-model:value="record.rectificationType"
style="width: 100%"
:bordered="false"
>
<a-select-option value="立行立改">立行立改</a-select-option>
<a-select-option value="限期整改">限期整改</a-select-option>
<a-select-option value="长期整改">长期整改</a-select-option>
</a-select>
</template>
<!-- 整改时限列 -->
<template v-if="column.dataIndex === 'deadline'">
<a-input
v-model:value="record.deadline"
style="width: 100%"
:bordered="false"
placeholder="如1个月"
/>
</template>
<!-- 操作列 -->
<template v-if="column.dataIndex === 'action'">
<a-space>
<a-button type="link" @click="editProblem(record)">
编辑
</a-button>
<a-button type="link" danger @click="deleteProblem(index)">
删除
</a-button>
</a-space>
</template>
</template>
</a-table>
</a-card>
<!-- 其他问题区域 -->
<a-card :bordered="false" class="other-problems-card">
<template #title>审计发现的其他问题未在报告正文反映</template>
<a-textarea
v-model:value="otherProblems"
:rows="4"
placeholder="请输入其他审计发现问题..."
/>
</a-card>
<!-- 问题编辑弹窗 -->
<a-modal
v-model:visible="showEditModal"
:title="editingProblem.id ? '编辑问题' : '新增问题'"
width="800px"
@ok="saveProblem"
@cancel="cancelEdit"
>
<a-form layout="vertical">
<a-form-item label="是否存在相关问题" required>
<a-radio-group v-model:value="editingProblem.hasProblem">
<a-radio value="true"></a-radio>
<a-radio value="false"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="问题表述" required>
<a-textarea
v-model:value="editingProblem.problemDescription"
:rows="3"
placeholder="请输入问题表述"
/>
</a-form-item>
<a-form-item label="问题明细">
<a-textarea
v-model:value="editingProblem.problemDetails"
:rows="3"
placeholder="请输入问题明细"
/>
</a-form-item>
<a-form-item label="法规依据">
<a-textarea
v-model:value="editingProblem.legalBasis"
:rows="3"
placeholder="请输入法规依据"
/>
</a-form-item>
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="涉及金额(万元)">
<a-input-number
v-model:value="editingProblem.amount"
:min="0"
:precision="2"
style="width: 100%"
/>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="整改类型">
<a-select v-model:value="editingProblem.rectificationType">
<a-select-option value="立行立改">立行立改</a-select-option>
<a-select-option value="限期整改">限期整改</a-select-option>
<a-select-option value="长期整改">长期整改</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="整改时限">
<a-input
v-model:value="editingProblem.deadline"
placeholder="如1个月"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="具体责任界定">
<a-textarea
v-model:value="editingProblem.responsibility"
:rows="2"
placeholder="请输入具体责任界定"
/>
</a-form-item>
<a-form-item label="整改要求">
<a-textarea
v-model:value="editingProblem.rectificationRequirement"
:rows="2"
placeholder="请输入整改要求"
/>
</a-form-item>
<a-form-item label="审计建议">
<a-textarea
v-model:value="editingProblem.auditSuggestion"
:rows="3"
placeholder="请输入审计建议"
/>
</a-form-item>
</a-form>
</a-modal>
</div>
</a-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { message } from 'ant-design-vue';
import type { UploadProps } from 'ant-design-vue';
import {
PlusOutlined,
RobotOutlined,
ExportOutlined,
SaveOutlined,
UploadOutlined,
PaperClipOutlined
} from '@ant-design/icons-vue';
import type { PwlProject } from '@/api/pwl/pwlProject/model';
import { generateAuditReport2 } from "@/api/ai/auditReport";
// Props 定义
const props = defineProps<{
visible: boolean;
data?: PwlProject | null;
}>();
// Emits 定义
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 表单数据
const form = reactive<PwlProject>({
id: undefined,
name: undefined,
code: undefined,
});
// 状态变量
const problemList = ref<any[]>([]);
const otherProblems = ref('');
const loading = ref(false);
const generating = ref(false);
const showEditModal = ref(false);
const fileList = ref<any[]>([]);
const editingProblem = ref<any>({
hasProblem: 'true',
problemDescription: '',
problemDetails: '',
legalBasis: '',
amount: 0,
responsibility: '',
rectificationType: '',
rectificationRequirement: '',
deadline: '',
auditSuggestion: ''
});
// 表格列定义 - 优化后
const columns = [
{
title: '序号',
dataIndex: 'index',
width: 60,
fixed: 'left'
},
{
title: '是否存在问题',
dataIndex: 'hasProblem',
width: 100,
fixed: 'left'
},
{
title: '问题表述',
dataIndex: 'problemDescription',
width: 200,
ellipsis: true
},
{
title: '问题明细',
dataIndex: 'problemDetails',
width: 200,
ellipsis: true
},
{
title: '法规依据',
dataIndex: 'legalBasis',
width: 200,
ellipsis: true
},
{
title: '涉及金额(万元)',
dataIndex: 'amount',
width: 120
},
{
title: '具体责任界定',
dataIndex: 'responsibility',
width: 150,
ellipsis: true
},
{
title: '整改类型',
dataIndex: 'rectificationType',
width: 120
},
{
title: '整改要求',
dataIndex: 'rectificationRequirement',
width: 150,
ellipsis: true
},
{
title: '整改时限',
dataIndex: 'deadline',
width: 120
},
{
title: '审计建议',
dataIndex: 'auditSuggestion',
width: 150,
ellipsis: true
},
{
title: '操作',
dataIndex: 'action',
width: 120,
fixed: 'right'
}
];
// 上传前处理
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
// 添加到文件列表
fileList.value = [file];
return false; // 阻止自动上传
};
// 删除文件
const removeFile = (uid: string) => {
fileList.value = fileList.value.filter(file => file.uid !== uid);
};
// 更新visible
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 新增问题
const addProblem = () => {
editingProblem.value = {
hasProblem: 'true',
problemDescription: '',
problemDetails: '',
legalBasis: '',
amount: 0,
responsibility: '',
rectificationType: '',
rectificationRequirement: '',
deadline: '',
auditSuggestion: ''
};
showEditModal.value = true;
};
// 编辑问题
const editProblem = (problem: any) => {
editingProblem.value = { ...problem };
showEditModal.value = true;
};
// 保存问题
const saveProblem = () => {
if (!editingProblem.value.problemDescription) {
message.warning('请输入问题表述');
return;
}
if (!editingProblem.value.id) {
editingProblem.value.id = Date.now();
problemList.value.push({ ...editingProblem.value });
} else {
const index = problemList.value.findIndex(p => p.id === editingProblem.value.id);
if (index !== -1) {
problemList.value[index] = { ...editingProblem.value };
}
}
message.success('保存成功');
showEditModal.value = false;
};
// 取消编辑
const cancelEdit = () => {
showEditModal.value = false;
};
// 删除问题
const deleteProblem = (index: number) => {
problemList.value.splice(index, 1);
message.success('删除成功');
};
// AI生成问题 - 优化版
const generateProblems = async () => {
// 检查是否有上传文件
if (fileList.value.length === 0) {
message.warning('请先上传附件文件');
return;
}
generating.value = true;
try {
console.log('当前行数据:', props.data);
console.log('项目名称:', props.data?.name);
console.log('项目编号:', props.data?.code);
console.log('分析文库:', props.data?.analysisLibrary);
console.log('项目文库1:', props.data?.projectLibrary);
console.log('项目文库2:', props.data?.kbId);
console.log('项目文库3:', props.data?.libraryIds);
// 获取上传的文件
const fileItem = fileList.value[0];
const file = fileItem.originFileObj || fileItem;
// 调用后端接口
const result = await generateAuditReport2(
file,
props.data?.kbId,
props.data?.libraryIds,
props.data?.analysisLibrary,
props.data?.projectLibrary
);
// 处理返回结果 - 优化后的数据映射
if (result && result.findings) {
const generatedProblems = result.findings.map((finding: any, index: number) => {
// 统一处理字段映射
const problem = {
id: Date.now() + index,
hasProblem: finding['是否存在相关问题'] || 'true',
problemDescription: finding['问题表述'] || finding.problemDescription || '',
problemDetails: finding['问题明细'] || finding.problemDetails || '',
legalBasis: finding['法规依据'] || finding.legalBasis || '',
amount: parseFloat(finding['涉及金额(万元)']) || finding.amount || 0,
responsibility: finding['具体责任界定'] || finding.responsibility || '',
rectificationType: finding['整改类型'] || finding.rectificationType || '',
rectificationRequirement: finding['整改要求'] || finding.rectificationRequirement || '',
deadline: finding['整改时限'] || finding.deadline || '',
auditSuggestion: finding['审计建议'] || finding.auditSuggestion || ''
};
// 处理金额字段的特殊情况
if (finding['涉及金额(万元)'] === '/') {
problem.amount = 0;
}
return problem;
});
problemList.value = generatedProblems;
message.success(`成功生成 ${generatedProblems.length} 个审计问题`);
} else {
throw new Error('生成结果格式错误');
}
} catch (error: any) {
console.error('AI生成问题失败:', error);
message.error('生成失败: ' + (error.message || '未知错误'));
} finally {
generating.value = false;
}
};
// 导出Excel
const exportExcel = () => {
message.info('导出功能开发中...');
};
// 保存数据
const saveData = () => {
const dataToSave = {
problems: problemList.value,
otherProblems: otherProblems.value
};
console.log('保存的数据:', dataToSave);
message.success('数据保存成功');
};
// 加载审计核查数据
const loadAuditCheckData = () => {
problemList.value = [];
otherProblems.value = '';
fileList.value = [];
};
// 监听数据变化
watch(
() => props.data,
(data) => {
if (data) {
Object.assign(form, data);
loadAuditCheckData();
}
}
);
</script>
<style lang="less" scoped>
.audit-container {
.action-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
background: white;
padding: 16px;
border-radius: 4px;
.title {
font-size: 16px;
font-weight: bold;
color: #1890ff;
}
.action-button {
border-radius: 20px;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
}
}
}
.file-info-card {
margin-bottom: 16px;
.file-info {
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
.file-name {
font-weight: 500;
}
}
}
}
.table-card {
margin-bottom: 16px;
:deep(.ant-table) {
.ant-table-thead > tr > th {
background: #1890ff;
color: white;
text-align: center;
}
.ant-table-tbody > tr > td {
.ant-input,
.ant-input-number-input,
.ant-select-selector,
.ant-picker {
&:hover {
background: #f5f5f5;
}
}
}
.ant-table-row:hover {
.ant-input,
.ant-input-number-input,
.ant-select-selector,
.ant-picker {
background: #f5f5f5;
}
}
}
}
.other-problems-card {
:deep(.ant-card-head) {
background: #fff2e8;
border-bottom: 1px solid #ffd8bf;
}
:deep(.ant-card-head-title) {
color: #873800;
font-weight: bold;
}
}
}
</style>

View File

@@ -0,0 +1,847 @@
<template>
<a-modal
:visible="visible"
title="审计取证单预览"
:width="1100"
:footer="null"
@cancel="handleCancel"
destroy-on-close
>
<div class="evidence-actions">
<a-space>
<a-button @click="resetFields">重置内容</a-button>
<a-button type="primary" @click="handleExport" :loading="exporting">
导出Word文档
</a-button>
<a-button v-if="false" @click="printEvidence">打印预览</a-button>
</a-space>
<div class="action-tip"
>可直接在表格中编辑导出即可生成与效果图一致的取证单</div
>
</div>
<div class="evidence-container">
<div class="evidence-sheet" ref="printArea">
<div class="sheet-title">审计取证单</div>
<div class="sheet-meta">
<div class="meta-left">
<span>索引号</span>
<input
v-model="form.caseIndex"
class="inline-input long"
placeholder="填写索引号"
/>
</div>
<div class="meta-right">
<span></span>
<input v-model="form.pageIndex" class="inline-input small" />
<span></span>
<input v-model="form.pageTotal" class="inline-input small" />
<span></span>
</div>
</div>
<table class="sheet-table">
<colgroup>
<col style="width: 16%" />
<col style="width: 84%" />
</colgroup>
<tbody>
<tr>
<th>项目名称</th>
<td>
<textarea
v-model="form.projectName"
class="cell-input single"
placeholder="填写项目名称"
></textarea>
</td>
</tr>
<tr>
<th>被审计调查单位或个人</th>
<td>
<textarea
v-model="form.auditedTarget"
class="cell-input single"
placeholder="填写被审计(调查)单位或个人"
></textarea>
</td>
</tr>
<tr>
<th>审计调查事项</th>
<td>
<textarea
v-model="form.auditMatter"
class="cell-input single"
placeholder="填写审计(调查)事项"
></textarea>
</td>
</tr>
<tr>
<th class="vertical-header">
<div class="vertical-text">
<span>审计</span>
<span>调查</span>
<span>事项</span>
<span>摘要</span>
</div>
</th>
<td class="summary-cell">
<div class="summary-title">
审计调查事项摘要包括标题审计记录审计发现定性依据
</div>
<!-- <div class="summary-content">-->
<!-- <div class="summary-item">-->
<!-- <strong>1.标题</strong-->
<!-- >突出核心问题采用观点性语句一般为审计内容审计目标的结论性描述例如-->
<!-- </div>-->
<!-- <div class="summary-example">-->
<!-- 在审计期间XX单位存在"服务费,其流通"行为-->
<!-- </div>-->
<!-- <div class="summary-item">-->
<!-- <strong>2.审计记录</strong-->
<!-- >仅客观记录审计核查的具体事实时间地点主体行为数据等不使用主观评价性语言"违规""不合理"或问题定性引证合同条款凭证等等原始凭证形式"经核查[凭证描述]……"例如-->
<!-- </div>-->
<!-- <div class="summary-example">-->
<!-- -->
<!-- 经查2019年1月1日签订的XX服务合同编号XYZ-2019-001第3条约定"乙方(服务商)员工薪酬中甲方考勤费,甲方有权对乙方员工薪酬进行审核并支付"-->
<!-- </div>-->
<!-- <div class="summary-example">-->
<!-- -->
<!-- 调取2019年6月外包人员考勤表显示实际出勤人数为8人缺勤2人由甲方部门主管确认缺勤2人由甲方资源部核实无-->
<!-- </div>-->
<!-- <div class="summary-example">-->
<!-- -->
<!-- 查证2020年3月服务费结算凭证凭证号FV20200315所附明细清单显示人员名单为8人且月计薪资×工资标准结算费用-->
<!-- </div>-->
<!-- </div>-->
<div class="summary-editor">
<div class="summary-field">
<div class="summary-field-label">标题</div>
<textarea
v-model="form.summaryTitle"
class="cell-input summary-textarea medium"
placeholder="填写核心标题或要点"
></textarea>
</div>
<div class="summary-field">
<div class="summary-field-label">审计记录</div>
<textarea
v-model="form.auditRecord"
class="cell-input summary-textarea tall"
placeholder="客观记录审计核查的过程与事实"
></textarea>
</div>
<div class="summary-field">
<div class="summary-field-label">审计发现</div>
<textarea
v-model="form.auditFinding"
class="cell-input summary-textarea tall"
placeholder="写明审计发现的事实、性质及影响"
></textarea>
</div>
<div class="summary-field">
<div class="summary-field-label">定性依据</div>
<textarea
v-model="form.evidenceBasis"
class="cell-input summary-textarea medium"
placeholder="引用法规、制度或合同条款作为依据"
></textarea>
</div>
</div>
</td>
</tr>
<tr>
<th>处理建议</th>
<td>
<textarea
v-model="form.handling"
class="cell-input medium"
placeholder="拟采取的处理措施"
></textarea>
</td>
</tr>
<tr>
<th>附件</th>
<td>
<textarea
v-model="form.attachment"
class="cell-input single"
placeholder="列示随附的证明材料"
></textarea>
</td>
</tr>
<tr>
<th>审计人员</th>
<td>
<div class="split-row">
<div class="split-left">
<input
v-model="form.auditors"
class="cell-input input-line"
placeholder="填写审计人员"
/>
</div>
<div class="split-right">
<span class="label">编制日期</span>
<input
v-model="form.compileDate"
class="cell-input input-line"
placeholder="YYYY-MM-DD"
/>
</div>
</div>
</td>
</tr>
<tr>
<th>证据提供单位或个人意见</th>
<td>
<div class="provider-row">
<div class="provider-opinion">
<textarea
v-model="form.providerOpinion"
class="cell-input tall"
placeholder="证据提供单位盖章,负责人或其他定的人员签名"
></textarea>
</div>
<div class="provider-date">
<div class="date-label">日期</div>
<input
v-model="form.providerDate"
class="cell-input input-line"
placeholder="YYYY-MM-DD"
/>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="footer-note">
<div class="note-row">
附件
<input
v-model="form.attachmentPages"
class="inline-input small"
placeholder="页数"
/>
</div>
<div class="note">
说明1. 请你单位在
<input
v-model="form.feedbackDeadline"
class="inline-input long"
placeholder="填写反馈期限"
/>
前反馈意见以生成的编制日期为基础往后推10天填充日期
</div>
<!-- <div class="note">-->
<!-- 2. 证据提供单位意见栏如填写不下可另附说明-->
<!-- </div>-->
</div>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { PropType, reactive, ref, watch } from 'vue';
import { message } from 'ant-design-vue';
import { PwlProject } from '@/api/pwl/pwlProject/model';
import { downloadAuditEvidence } from '@/api/ai/auditContent';
type BaseInfo = {
caseIndex?: string;
projectName?: string;
auditedTarget?: string;
auditMatter?: string;
};
const props = defineProps({
visible: {
type: Boolean,
default: false
},
baseInfo: {
type: Object as PropType<BaseInfo>,
default: () => ({})
},
project: {
type: Object as PropType<PwlProject>,
default: () => ({})
},
selectedRows: {
type: Array as PropType<any[]>,
default: () => []
}
});
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void;
}>();
const printArea = ref<HTMLElement | null>(null);
const exporting = ref(false);
const defaultForm = () => ({
caseIndex: '',
pageIndex: '1',
pageTotal: '1',
projectName: '',
auditedTarget: '',
auditMatter: '',
summaryTitle: '',
auditRecord: '',
auditFinding: '',
evidenceBasis: '',
handling: '',
suggestion: '',
attachment: '',
auditors: '',
compileDate: '',
providerOpinion: '',
providerDate: '',
attachmentPages: '',
feedbackDeadline: ''
});
const form = reactive(defaultForm());
const formatAttachmentText = (caseIndex: string, raw: string) => {
const safeCaseIndex = (caseIndex || '').trim();
const items = (raw || '')
.split(/[\n\r,,、;\s]+/)
.map((s) => s.trim())
.filter(Boolean);
if (!safeCaseIndex || items.length === 0) return raw || '';
const escaped = safeCaseIndex.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const prefixRe = new RegExp(`^${escaped}-\\d{3}-`);
return items
.map((name, idx) => {
const no = String(idx + 1).padStart(3, '0');
const baseName = name.replace(prefixRe, '');
return `${safeCaseIndex}-${no}-${baseName}`;
})
.join('\n');
};
const applyBaseInfo = () => {
console.log('applyBaseInfo called, selectedRows:', props.selectedRows);
console.log('baseInfo:', props.baseInfo);
// 重置表单为默认值
Object.assign(form, defaultForm(), props.baseInfo || {});
// 如果有传入的selectedRows直接使用第一个对象的数据生成取证单时通常只有一个
if (props.selectedRows && props.selectedRows.length > 0) {
const evidenceData = props.selectedRows[0] || {};
console.log('Evidence data from selectedRows:', evidenceData);
// 直接将后端返回的数据映射到表单字段
form.caseIndex =
props.baseInfo?.caseIndex || props.project?.caseIndex || form.caseIndex || '';
form.projectName = props.project?.code || evidenceData.projectName || form.projectName || '';
form.auditedTarget =
evidenceData.auditedTarget || form.auditedTarget || '';
form.auditMatter = evidenceData.auditMatter || form.auditMatter || '';
form.summaryTitle = evidenceData.summaryTitle || form.summaryTitle || '';
form.auditRecord = evidenceData.auditRecord || form.auditRecord || '';
form.auditFinding = evidenceData.auditFinding || form.auditFinding || '';
form.evidenceBasis =
evidenceData.evidenceBasis || form.evidenceBasis || '';
form.handling = evidenceData.handling || form.handling || '';
form.suggestion = evidenceData.suggestion || form.suggestion || '';
// form.auditors = evidenceData.auditors || form.auditors || '';
form.auditors = '';
if (props.project && props.project.saleUser) {
const saleUser = JSON.parse(props.project.saleUser);
form.auditors = saleUser.join();
}
form.compileDate = evidenceData.compileDate || form.compileDate || '';
// form.providerOpinion =
// evidenceData.providerOpinion || form.providerOpinion || '';
// form.providerDate = evidenceData.providerDate || form.providerDate || '';
form.providerOpinion = '';
form.providerDate = '';
form.attachmentPages =
evidenceData.attachmentPages || form.attachmentPages || '';
// form.feedbackDeadline =
// evidenceData.feedbackDeadline || form.feedbackDeadline || '';
form.feedbackDeadline = 'XX年XX月XX日';
// 处理attachment字段数组转字符串
if (evidenceData.attachment) {
const rawAttachment = Array.isArray(evidenceData.attachment)
? evidenceData.attachment.join('\n')
: evidenceData.attachment;
form.attachment = formatAttachmentText(form.caseIndex, rawAttachment);
}
// 特殊处理如果evidenceData中有title字段也填充到summaryTitle
if (evidenceData.title && !form.summaryTitle) {
form.summaryTitle = evidenceData.title;
}
}
// 设置默认编制日期(如果没有的话)
if (!form.compileDate) {
const now = new Date();
form.compileDate = `${now.getFullYear()}-${String(
now.getMonth() + 1
).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
}
// 确保页码字段有值
if (!form.pageIndex) form.pageIndex = '1';
if (!form.pageTotal) form.pageTotal = '1';
console.log(
'Form data after applyBaseInfo:',
JSON.stringify(form, null, 2)
);
};
watch(
() => props.visible,
(visible) => {
if (visible) {
applyBaseInfo();
}
}
);
watch(
() => props.baseInfo,
() => {
if (props.visible) {
applyBaseInfo();
}
},
{ deep: true }
);
watch(
() => props.project,
() => {
if (props.visible) {
applyBaseInfo();
}
},
{ deep: true }
);
watch(
() => props.selectedRows,
() => {
if (props.visible) {
applyBaseInfo();
}
},
{ deep: true }
);
const handleCancel = () => {
emit('update:visible', false);
};
const resetFields = () => {
applyBaseInfo();
message.success('内容已重置');
};
/**
* 导出Word文档
*/
const handleExport = async () => {
try {
exporting.value = true;
// 准备导出数据
const exportData = {
caseIndex: form.caseIndex,
pageIndex: form.pageIndex,
pageTotal: form.pageTotal,
projectName: form.projectName,
auditedTarget: form.auditedTarget,
auditMatter: form.auditMatter,
summaryTitle: form.summaryTitle,
auditRecord: form.auditRecord,
auditFinding: form.auditFinding,
evidenceBasis: form.evidenceBasis,
handling: form.handling,
attachment: form.attachment,
auditors: form.auditors,
compileDate: form.compileDate,
providerOpinion: form.providerOpinion,
providerDate: form.providerDate,
attachmentPages: form.attachmentPages,
feedbackDeadline: form.feedbackDeadline
};
// 调用后端下载接口
const blob = await downloadAuditEvidence(exportData);
// 创建下载链接
const url = window.URL.createObjectURL(new Blob([blob]));
const link = document.createElement('a');
link.href = url;
// 设置文件名
const fileName = `审计取证单_${form.projectName || '取证单'}_${form.caseIndex || ''}.docx`;
link.setAttribute('download', fileName);
// 触发下载
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 释放URL对象
window.URL.revokeObjectURL(url);
message.success('取证单导出成功');
} catch (error) {
console.error('导出失败:', error);
message.error('取证单导出失败');
} finally {
exporting.value = false;
}
};
/**
* 打印预览
*/
const printEvidence = () => {
const area = printArea.value;
if (!area) {
message.warning('暂无可打印的内容');
return;
}
const newWindow = window.open('', '_blank');
if (!newWindow) {
message.error('浏览器阻止了弹窗,请允许弹窗后重试');
return;
}
const style = `
* { box-sizing: border-box; }
body { margin: 0; padding: 12px; font-family: 'SimSun', 'Songti SC', serif; color: #000; }
.evidence-sheet { padding: 14px 16px; border: 1px solid #000; }
.sheet-title { text-align: center; font-size: 24px; letter-spacing: 8px; margin-bottom: 6px; font-weight: 700; }
.sheet-meta { display: flex; justify-content: space-between; align-items: flex-end; font-size: 14px; margin-bottom: 8px; }
.meta-left, .meta-right { display: flex; align-items: flex-end; gap: 4px; }
.inline-input { border: none; border-bottom: 1px solid #000; min-width: 70px; padding: 2px 4px; font-size: 14px; background: transparent; outline: none; height: 20px; line-height: 20px; }
.inline-input.small { width: 44px; text-align: center; }
.inline-input.long { min-width: 180px; }
.sheet-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
.sheet-table th, .sheet-table td { border: 1px solid #000; padding: 6px 8px; vertical-align: top; font-size: 14px; line-height: 1.6; }
.sheet-table th { background: #fff; font-weight: 600; text-align: center; vertical-align: middle; }
.vertical-header { padding: 4px 2px; font-weight: 600; }
.vertical-text { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.vertical-text span { display: block; line-height: 1.1; }
.cell-input { width: 100%; border: none; resize: none; min-height: 32px; font-size: 14px; line-height: 1.6; font-family: inherit; background: transparent; overflow: hidden; field-sizing: content; }
.cell-input:focus { outline: none; }
.cell-input.single { min-height: 30px; }
.cell-input.medium { min-height: 90px; }
.cell-input.tall { min-height: 170px; }
.input-line { height: 30px; }
.summary-cell { padding: 8px 12px; }
.summary-title { font-weight: 600; margin-bottom: 6px; text-align: left; }
.summary-content { text-align: left; line-height: 1.7; font-size: 12px; }
.summary-item { margin-bottom: 6px; }
.summary-example { margin-left: 16px; margin-bottom: 6px; color: #000; }
.summary-editor { margin-top: 8px; }
.summary-field { margin-bottom: 6px; }
.summary-field-label { font-weight: 600; margin-bottom: 4px; }
.summary-textarea { margin: 0; padding: 0; }
.split-row { display: flex; align-items: stretch; gap: 0; min-height: 36px; }
.split-left { flex: 1; padding-right: 8px; }
.split-right { display: flex; align-items: center; gap: 4px; white-space: nowrap; border-left: 1px solid #000; padding-left: 8px; }
.provider-row { display: flex; gap: 0; min-height: 140px; align-items: stretch; }
.provider-opinion { flex: 1; padding-right: 8px; }
.provider-date { width: 110px; display: flex; flex-direction: column; gap: 8px; justify-content: center; align-items: center; border-left: 1px solid #000; padding-left: 6px; }
.date-label { font-weight: 600; }
.footer-note { margin-top: 10px; font-size: 12px; line-height: 1.6; }
.note-row { margin-bottom: 6px; text-align: right; }
.note { margin-bottom: 4px; }
.note input { vertical-align: middle; }
`;
newWindow.document.write(`
<html>
<head>
<title>审计取证单</title>
<style>${style}</style>
</head>
<body>${area.innerHTML}</body>
</html>
`);
newWindow.document.close();
newWindow.focus();
newWindow.print();
};
</script>
<style scoped>
.evidence-actions {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.action-tip {
color: #666;
font-size: 13px;
}
.evidence-container {
max-height: 70vh;
overflow: auto;
background: #f2f2f2;
padding: 16px;
}
.evidence-sheet {
background: #fff;
padding: 14px 16px;
border: 1px solid #000;
box-shadow: none;
width: 794px;
max-width: 100%;
min-height: 1123px;
margin: 0 auto;
font-family: 'SimSun', 'Songti SC', serif;
color: #000;
}
.sheet-title {
text-align: center;
font-size: 24px;
letter-spacing: 8px;
margin-bottom: 6px;
font-weight: 700;
}
.sheet-meta {
display: flex;
justify-content: space-between;
align-items: flex-end;
font-size: 14px;
margin-bottom: 8px;
}
.meta-left,
.meta-right {
display: flex;
align-items: flex-end;
gap: 4px;
}
.inline-input {
border: none;
border-bottom: 1px solid #000;
min-width: 70px;
padding: 2px 4px;
font-size: 14px;
background: transparent;
outline: none;
height: 20px;
line-height: 20px;
}
.inline-input.small {
width: 44px;
text-align: center;
}
.inline-input.long {
min-width: 180px;
}
.sheet-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
.sheet-table th,
.sheet-table td {
border: 1px solid #000;
padding: 6px 8px;
vertical-align: top;
font-size: 14px;
line-height: 1.6;
}
.sheet-table th {
background: #fff;
font-weight: 600;
text-align: center;
vertical-align: middle;
}
.vertical-header {
padding: 4px 2px;
font-weight: 600;
}
.vertical-text {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.vertical-text span {
display: block;
line-height: 1.1;
}
.cell-input {
width: 100%;
border: none;
resize: none;
min-height: 32px;
font-size: 14px;
line-height: 1.6;
font-family: inherit;
background: transparent;
overflow: hidden;
field-sizing: content;
}
.cell-input:focus {
outline: none;
}
.cell-input.single {
min-height: 30px;
}
.cell-input.medium {
min-height: 90px;
}
.cell-input.tall {
min-height: 170px;
}
.input-line {
height: 30px;
}
.summary-cell {
padding: 8px 12px;
}
.summary-title {
font-weight: 600;
margin-bottom: 6px;
text-align: left;
}
.summary-content {
text-align: left;
line-height: 1.7;
font-size: 12px;
}
.summary-item {
margin-bottom: 6px;
}
.summary-example {
margin-left: 16px;
margin-bottom: 6px;
color: #000;
}
.summary-editor {
margin-top: 8px;
}
.summary-field {
margin-bottom: 6px;
}
.summary-field-label {
font-weight: 600;
margin-bottom: 4px;
}
.summary-textarea {
margin: 0;
padding: 0;
}
.split-row {
display: flex;
align-items: stretch;
gap: 0;
min-height: 36px;
}
.split-left {
flex: 1;
padding-right: 8px;
}
.split-right {
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
border-left: 1px solid #000;
padding-left: 8px;
}
.provider-row {
display: flex;
gap: 0;
min-height: 140px;
align-items: stretch;
}
.provider-opinion {
flex: 1;
padding-right: 8px;
}
.provider-date {
width: 110px;
display: flex;
flex-direction: column;
gap: 8px;
justify-content: center;
align-items: center;
border-left: 1px solid #000;
padding-left: 6px;
}
.date-label {
font-weight: 600;
}
.footer-note {
margin-top: 10px;
font-size: 12px;
line-height: 1.6;
}
.note-row {
margin-bottom: 6px;
text-align: right;
}
.note {
margin-bottom: 4px;
}
</style>

View File

@@ -0,0 +1,332 @@
<template>
<a-modal
v-model:visible="showDocSelect"
title="选择文档"
width="1200px"
:footer="null"
wrap-class-name="doc-select-modal"
@cancel="handleDocSelectCancel"
>
<div class="doc-select-container">
<div class="doc-layout">
<!-- 左侧目录树 -->
<div class="dir-tree-panel">
<div class="dir-header">
<span>文档目录</span>
</div>
<div class="tree-container">
<a-tree
v-if="treeData.length > 0"
:tree-data="treeData"
:expanded-keys="expandedKeys"
:selected-keys="selectedKeys"
:load-data="onLoadData"
@expand="onExpand"
@select="onSelect"
:field-names="{ title: 'name', key: 'id', children: 'children' }"
checkable
:checked-keys="checkedDirKeys"
@check="onDirCheck"
>
<template #title="{ name, id }">
<span :class="{ 'active-dir': selectedKeys[0] === id }">{{
name
}}</span>
</template>
</a-tree>
<a-empty v-else :image="simpleImage" description="暂无目录" />
</div>
</div>
<!-- 右侧文档列表 -->
<div class="doc-list-panel">
<div class="doc-header">
<div class="doc-actions">
<span class="doc-tips"
>已选择 {{ checkedDirKeys.length }} 个目录,
{{ selectedFileKeys.length }} 个文件</span
>
<a-space>
<a-button @click="clearSelection">清空选择</a-button>
<a-button type="primary" @click="confirmSelection"
>确认选择</a-button
>
</a-space>
</div>
</div>
<div class="doc-content">
<a-table
:dataSource="docList"
:columns="docColumns"
:loading="docLoading"
rowKey="id"
:scroll="{ y: 400 }"
:pagination="pagination"
@change="handleTableChange"
size="middle"
:row-selection="{
selectedRowKeys: selectedFileKeys,
onChange: onFileSelectionChange,
type: 'checkbox'
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'fileName'">
<div style="display: flex; align-items: center; gap: 8px">
<FileOutlined />
<span>{{ record.fileName }}</span>
</div>
</template>
<template v-if="column.key === 'fileSize'">
{{ formatFileSize(record.fileSize) }}
</template>
<template v-if="column.key === 'fileType'">
<a-tag color="blue">{{ record.fileType }}</a-tag>
</template>
</template>
</a-table>
</div>
</div>
</div>
</div>
</a-modal>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
import { FileOutlined } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue';
import { Empty } from 'ant-design-vue';
import { listAiCloudDoc } from '@/api/ai/aiCloudDoc';
import { listAiCloudFile } from '@/api/ai/aiCloudFile';
import type { AiCloudDoc } from '@/api/ai/aiCloudDoc/model';
import type { AiCloudFile } from '@/api/ai/aiCloudFile/model';
const showDocSelect = defineModel();
const props = defineProps<{
currentCompanyId: number;
}>();
// 树形结构相关
const expandedKeys = ref<(string | number)[]>([]);
const selectedKeys = ref<(string | number)[]>([]);
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
// const currentKbId = ref('');
// const currentKbName = ref('');
const docColumns = ref([
{
title: '文件名',
dataIndex: 'fileName',
key: 'fileName',
ellipsis: true
},
{
title: '文件大小',
dataIndex: 'fileSize',
key: 'fileSize',
width: 120
},
{
title: '文件类型',
dataIndex: 'fileType',
key: 'fileType',
width: 120,
ellipsis: true
},
{
title: '上传时间',
dataIndex: 'uploadTime',
key: 'uploadTime',
width: 180
}
]);
// 计算树形数据
const docList = ref<AiCloudFile[]>([]);
const docLoading = ref(false);
const allDirs = ref<AiCloudDoc[]>([]);
const treeData = computed(() => {
const buildTree = (parentId: number = 0): any[] => {
return allDirs.value
.filter((item) => item.parentId === parentId)
.map((item) => ({
...item,
key: item.id,
title: item.name,
children: buildTree(item.id),
isLeaf:
allDirs.value.filter((child) => child.parentId === item.id)
.length === 0
}));
};
return buildTree(0);
});
const lastSelectedDirKeys = ref<(string | number)[]>([]);
const lastSelectedFileKeys = ref<(string | number)[]>([]);
// const currentSectionIndex = ref(2); // 默认是审计内容3
const selectedDocList = ref<string[]>([]);
const selectedFileList = ref<string[]>([]);
const selectedFileKeys = ref<(string | number)[]>([]);
const checkedDirKeys = ref<(string | number)[]>([]); // 新增勾选的目录keys
const handleDocSelectCancel = () => {
// 保存当前选择状态
lastSelectedDirKeys.value = [...checkedDirKeys.value];
lastSelectedFileKeys.value = [...selectedFileKeys.value];
showDocSelect.value = false;
};
const confirmSelection = () => {
// 保存当前选择状态
lastSelectedDirKeys.value = [...checkedDirKeys.value];
lastSelectedFileKeys.value = [...selectedFileKeys.value];
message.success(
`已选择 ${checkedDirKeys.value.length} 个目录和 ${selectedFileKeys.value.length} 个文件`
);
showDocSelect.value = false;
};
const loadAllCloudDocs = async () => {
try {
const params = {
companyId: props.currentCompanyId
};
const result = await listAiCloudDoc(params);
allDirs.value = result || [];
// 默认展开根节点并选中第一个目录
if (allDirs.value.length > 0) {
const rootDirs = allDirs.value.filter((item) => item.parentId === 0);
if (rootDirs.length > 0) {
expandedKeys.value = [0];
// 如果已经有选中的目录,保持选中状态,否则选中第一个目录
if (selectedKeys.value.length === 0) {
selectedKeys.value = [rootDirs[0].id!];
}
loadCloudFiles();
}
}
} catch (error) {
message.error('加载目录列表失败');
console.error('加载目录错误:', error);
}
};
// 树节点展开
const onExpand = (keys: (string | number)[]) => {
expandedKeys.value = keys;
};
// 树节点选择
const onSelect = (keys: (string | number)[]) => {
selectedKeys.value = keys;
pagination.value.current = 1;
loadCloudFiles();
};
// 新增:目录勾选处理
const onDirCheck = (checkedKeys: (string | number)[]) => {
checkedDirKeys.value = checkedKeys;
selectedDocList.value = checkedKeys.map((key) => key.toString());
};
// 异步加载子节点
const onLoadData = () => {
return new Promise<void>((resolve) => {
resolve();
});
};
// 表格分页变化处理
const pagination = ref({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: false,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ['10', '20', '50', '100']
});
const handleTableChange = (pag: any) => {
pagination.value.current = pag.current;
pagination.value.pageSize = pag.pageSize;
loadCloudFiles();
};
// 文件选择变化
const onFileSelectionChange = (
selectedRowKeys: (string | number)[],
selectedRows: AiCloudFile[]
) => {
selectedFileKeys.value = selectedRowKeys;
selectedFileList.value = selectedRows.map((row) => row.id!.toString());
};
// 清空选择
const clearSelection = () => {
selectedDocList.value = [];
selectedFileList.value = [];
selectedFileKeys.value = [];
checkedDirKeys.value = []; // 新增:清空勾选的目录
// 重新选择当前目录
if (selectedKeys.value.length > 0) {
selectedDocList.value = [selectedKeys.value[0].toString()];
checkedDirKeys.value = [selectedKeys.value[0]]; // 新增:重新勾选当前目录
}
};
const loadCloudFiles = async () => {
docLoading.value = true;
try {
if (!selectedKeys.value.length) {
docList.value = [];
pagination.value.total = 0;
return;
}
const params = {
docId: parseInt(selectedKeys.value[0].toString()),
page: pagination.value.current,
pageSize: pagination.value.pageSize
};
const result: any = await listAiCloudFile(params);
if (result && result.records) {
docList.value = result.records;
pagination.value.total = result.total;
} else {
docList.value = result || [];
pagination.value.total = docList.value.length;
}
} catch (error) {
message.error('加载文件列表失败');
console.error('加载文件错误:', error);
} finally {
docLoading.value = false;
}
};
// 格式化文件大小
const formatFileSize = (size: number) => {
if (!size) return '-';
if (size < 1024) return size + ' B';
if (size < 1024 * 1024) return (size / 1024).toFixed(1) + ' KB';
return (size / (1024 * 1024)).toFixed(1) + ' MB';
};
const open = () => {
showDocSelect.value = true;
loadAllCloudDocs();
};
defineExpose({ open });
</script>

View File

@@ -0,0 +1,290 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
width="85%"
:visible="visible"
:maskClosable="false"
title="历史记录"
:body-style="{ paddingBottom: '20px' }"
@update:visible="updateVisible"
:footer="null"
>
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:scroll="{ x: 1000 }"
size="small"
tool-class="ele-toolbar-form"
class="compact-history-table"
bordered
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'createTime'">
<span>{{ toDateString(record.createTime, 'MM-DD HH:mm') }}</span>
</template>
<template v-if="column.key === 'username'">
<span>{{ record.username || '-' }}</span>
</template>
<template v-if="column.key === 'interfaceName'">
<a-tag :color="getInterfaceColor(record.interfaceName)" size="small">
{{ getInterfaceName(record.interfaceName) }}
</a-tag>
</template>
<template v-if="column.key === 'dataCount'">
<a-tag v-if="record.dataCount > 0" color="blue" size="small">
{{ record.dataCount }}
</a-tag>
<span v-else class="text-gray"></span>
</template>
<template v-if="column.key === 'processingTime'">
<a-tag v-if="record.processingTime" color="green" size="small">
{{ formatProcessingTime(record.processingTime) }}
</a-tag>
<span v-else class="text-gray">-</span>
</template>
<template v-if="column.key === 'requestData'">
<a-tooltip :title="getRequestDataPreview(record)">
<a-button type="link" size="small" class="p-0 h-auto">
查看
</a-button>
</a-tooltip>
</template>
<template v-if="column.key === 'action'">
<a-button
type="primary"
size="small"
@click="handleSelect(record)"
:disabled="!hasValidData(record)"
>
选择
</a-button>
</template>
</template>
</ele-pro-table>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import { type EleProTable, toDateString } from 'ele-admin-pro';
import type {
ColumnItem,
DatasourceFunction
} from 'ele-admin-pro/es/ele-pro-table/types';
import { pageAiHistory } from '@/api/ai/aiHistory';
import type { AiHistoryParam } from '@/api/ai/aiHistory/model';
const props = defineProps<{
visible: boolean;
interfaceName?: string;
projectId?: number;
}>();
const emit = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'select', record: any): void;
}>();
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
const datasource: DatasourceFunction = async ({ page, limit, orders }) => {
const params: AiHistoryParam = { page, limit };
if (orders) {
Object.assign(params, orders);
}
console.log(props,'props');
// 使用传入的接口名称进行过滤
if (props.interfaceName) {
params.interfaceName = props.interfaceName;
}
if (props.projectId) {
params.projectId = props.projectId;
}
try {
const result = await pageAiHistory(params);
const processedList = result.list.map(record => {
let dataCount = 0;
let processingTime = '';
try {
if (record.responseData) {
const responseData = JSON.parse(record.responseData);
if (responseData.data && Array.isArray(responseData.data)) {
dataCount = responseData.data.length;
} else if (responseData.data?.data && Array.isArray(responseData.data.data)) {
dataCount = responseData.data.data.length;
}
processingTime = responseData.processing_time || responseData.generated_time || '';
}
} catch (error) {
console.warn('解析响应数据失败:', error);
}
return { ...record, dataCount, processingTime };
});
return { total: result.total, list: processedList };
} catch (error) {
console.error('获取历史记录失败:', error);
return { total: 0, list: [] };
}
};
// 监听 visible 变化,当弹窗显示时刷新表格
watch(() => props.visible, (newVal) => {
if (newVal && tableRef.value) {
// 延迟一下确保表格已经渲染
setTimeout(() => {
tableRef.value?.reload();
}, 100);
}
});
const columns = ref<ColumnItem[]>([
{
title: '序号',
key: 'index',
width: 50,
align: 'center',
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
},
{
title: '接口',
key: 'interfaceName',
width: 120,
align: 'center'
},
{
title: '数据量',
key: 'dataCount',
width: 70,
align: 'center'
},
{
title: '耗时',
key: 'processingTime',
width: 80,
align: 'center'
},
{
title: '用户',
dataIndex: 'username',
key: 'username',
width: 80,
align: 'center'
},
{
title: '参数',
key: 'requestData',
width: 60,
align: 'center'
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
width: 110,
align: 'center',
sorter: true
},
{
title: '操作',
key: 'action',
width: 60,
align: 'center',
fixed: 'right'
}
]);
const getInterfaceName = (interfaceName: string) => {
const nameMap: Record<string, string> = {
'/api/ai/auditContent3/generateTripleOneTable': '三重一大',
'/api/ai/auditContent3/generateDecisionTable': '重大经济决策调查表'
};
return nameMap[interfaceName] || interfaceName.split('/').pop() || interfaceName;
};
const getInterfaceColor = (interfaceName: string) => {
const colorMap: Record<string, string> = {
'/api/ai/auditContent3/generateTripleOneTable': 'blue',
'/api/ai/auditContent3/generateDecisionTable': 'green'
};
return colorMap[interfaceName] || 'default';
};
const formatProcessingTime = (time: string) => {
if (time.includes('ms')) {
const ms = parseInt(time);
if (!isNaN(ms)) return `${(ms / 1000).toFixed(1)}s`;
}
if (time.includes('CST')) {
return new Date(time).toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit'
});
}
return time.length > 8 ? time.substring(0, 7) + '...' : time;
};
const getRequestDataPreview = (record: any) => {
try {
if (record.requestData) {
const requestData = JSON.parse(record.requestData);
const preview: string[] = [];
if (requestData.kbIds) preview.push(`知识库: ${requestData.kbIds}`);
if (requestData.libraryIds) preview.push(`项目库: ${requestData.libraryIds}`);
if (requestData.suggestion) preview.push(`要求: ${requestData.suggestion.substring(0, 20)}...`);
if (requestData.docList?.length > 0) preview.push(`文档: ${requestData.docList.length}`);
if (requestData.fileList?.length > 0) preview.push(`文件: ${requestData.fileList.length}`);
return preview.join('\n') || '无参数';
}
} catch (error) {
console.warn('解析请求数据失败:', error);
}
return '解析失败';
};
const hasValidData = (record: any) => {
try {
if (record.responseData) {
const responseData = JSON.parse(record.responseData);
const hasData = (responseData.data && Array.isArray(responseData.data) && responseData.data.length > 0) ||
(responseData.data?.data && Array.isArray(responseData.data.data) && responseData.data.data.length > 0);
return hasData && responseData.success !== false;
}
} catch (error) {
console.warn('检查数据有效性失败:', error);
}
return false;
};
const handleSelect = (record: any) => {
if (!hasValidData(record)) return;
emit('select', record);
};
</script>
<style scoped>
.text-gray {
color: rgba(0, 0, 0, 0.25);
}
.compact-history-table :deep(.ant-table-thead > tr > th) {
padding: 8px 4px;
}
.compact-history-table :deep(.ant-table-tbody > tr > td) {
padding: 8px 4px;
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<a-space>
<div
v-for="(item, index) in btns"
:key="index"
class="btn"
:class="[title === item ? 'btn-green' : 'btn-gray']"
@click="onChange(item)"
>{{ item }}</div
>
</a-space>
</template>
<script setup lang="ts">
defineProps({
title: {
type: String,
default: ''
},
btns: {
type: Array,
default: () => []
}
});
const emits = defineEmits(['change']);
const onChange = (title: string) => {
emits('change', title);
};
</script>
<style lang="less" scoped>
@import '../style/style.scss';
</style>

View File

@@ -0,0 +1,54 @@
export default [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '重大经济决策事项',
dataIndex: 'name',
key: 'name'
},
{
title: '会议时间',
dataIndex: 'content',
key: 'content',
width: 120
},
{
title: '决策事项金额',
dataIndex: 'amount',
key: 'amount',
width: 120
},
{
title: '程序程序',
dataIndex: 'progress',
key: 'progress',
width: 300
},
{
title: '执行情况(是/否)',
dataIndex: 'done',
key: 'done'
},
{
title: '执行效果(是否实现决策目标)',
children: [
{
title: '好',
dataIndex: 'goods'
},
{
title: '一般',
dataIndex: 'normal'
},
{
title: '差',
dataIndex: 'bad'
}
]
}
];

View File

@@ -0,0 +1,85 @@
// 导入Vue相关依赖
import { ref } from 'vue';
// 当前章节的ref
const currentSection = ref(0);
// 滚动到指定章节
export const scrollToSection = (index: number) => {
const element = document.getElementById(`section-${index}`);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
};
// 获取滚动容器
export const getScrollContainer = () => {
const drawer = document.querySelector('.ant-drawer-body');
return drawer || document.querySelector('.ant-modal-body') || document.documentElement;
};
// 监听滚动位置更新当前章节
export const handleScroll = () => {
const scrollContainer = getScrollContainer();
if (!scrollContainer) return;
const containerScrollTop = scrollContainer.scrollTop || document.documentElement.scrollTop;
// 获取所有章节元素
const sections = Array.from(document.querySelectorAll('.audit-section'));
for (let i = sections.length - 1; i >= 0; i--) {
const section = sections[i];
const sectionTop = section.offsetTop;
if (sectionTop - 100 <= containerScrollTop) {
currentSection.value = i;
break;
}
}
};
// 检查章节是否有内容
export const hasContent = (section: any) => {
if (!section) return false;
if (section.mode === 'table') {
return section.data && section.data.length > 0;
} else if (section.textareaList) {
return section.textareaList.some(textarea => textarea.content && textarea.content.trim());
} else {
return !!section.content?.trim();
}
};
// 构建导出数据
export const buildExportData = () => {
// 这里根据实际情况构建数据
// 由于navigationItems现在是函数返回的ref需要先获取值
const items = navigationItems ? navigationItems.value : [];
const exportData: any = {
companyName: form?.name || '',
auditTime: form?.expirationTime || ''
};
// 收集所有表格数据
items.forEach((item, index) => {
if (item.mode === 'table' && item.data && item.data.length > 0) {
const currentTable = item.tableOptions[item.currentTableIndex];
if (currentTable) {
exportData[`table${index + 1}_${currentTable.value}`] = item.data;
}
} else if (item.content) {
exportData[`content${index + 1}`] = item.content;
}
});
return exportData;
};
// 导出currentSection以便其他文件使用
export { currentSection };

View File

@@ -0,0 +1,48 @@
import { ref } from 'vue';
import { getTableConfig } from './tableCommon';
const navigationItems = ref([]);
// 初始化导航项
export function initNavigationItems() {
navigationItems.value = Array.from({ length: 11 }, (_, index) => {
const config = getTableConfig(index);
return {
number: getChineseNumber(index + 1),
name: `审计内容${index + 1}`,
title: config?.title || `审计内容${index + 1}`,
description: '点击"AI生成"按钮让AI为您生成该部分内容或直接在下方编辑',
generating: false,
suggestion: '',
mode: 'table',
showFileSelect: true, // 所有项都显示文件选择
data: [],
extraData: [],
currentTableIndex: 0, // 当前选中的表格索引
columns: null,
extraTableTitle: config?.extraTableTitle || null,
extraColumns: config?.extraColumns || [],
tableOptions: config?.options || [],
count: index + 1,
tableType: config?.type || `auditContent${index + 1}`,
content: '',
rows: 8
};
});
return navigationItems;
}
// 获取中文数字
function getChineseNumber(num) {
const chineseNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一'];
return chineseNumbers[num - 1] || num;
}
// 导出初始化的导航项
export default function useNavigationItems() {
if (navigationItems.value.length === 0) {
initNavigationItems();
}
return navigationItems;
}

View File

@@ -0,0 +1,81 @@
// 党风廉政建设责任制表格列
export const partyConductColumns = [
{
title: '大类',
dataIndex: 'category',
key: 'category',
align: 'center',
width: 120,
customCell: (_, index) => {
if (index === 0) return { rowSpan: 37 };
else if (index === 37) return { rowSpan: 3 };
else if (index === 40) return { rowSpan: 3 };
return { rowSpan: 0 };
}
},
{
title: '子类',
dataIndex: 'subCategory',
key: 'subCategory',
align: 'center',
width: 120,
customCell: (_, index) => {
if (index === 0) return { rowSpan: 17 };
else if (index === 17) return { rowSpan: 8 };
else if (index === 25) return { rowSpan: 8 };
else if (index === 33) return { rowSpan: 4 };
else if (index === 37) return { rowSpan: 3 };
else if (index === 40) return { rowSpan: 3 };
return { rowSpan: 0 };
}
},
{
title: '小类',
dataIndex: 'detailCategory',
key: 'detailCategory',
align: 'center',
width: 120,
customCell: (_, index) => {
if ([4, 17, 22, 25, 26, 27, 28, 29, 32, 33, 34, 37, 38, 39, 42].includes(index))
return { rowSpan: 1 };
else if ([18, 20, 23, 30, 35, 40].includes(index)) return { rowSpan: 2 };
if (index === 0) return { rowSpan: 4 };
else if (index === 5) return { rowSpan: 4 };
else if (index === 9) return { rowSpan: 5 };
else if (index === 14) return { rowSpan: 3 };
return { rowSpan: 0 };
}
},
{
title: '内容',
dataIndex: 'content',
key: 'content',
align: 'center',
width: 200
},
{
title: '执行情况',
dataIndex: 'executionStatus',
key: 'executionStatus',
align: 'center',
width: 300
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
// ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
partyConductColumns
};

View File

@@ -0,0 +1,94 @@
// 历史审计问题整改表格列
export const auditHistoryColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '审计年度',
dataIndex: 'auditYear',
key: 'auditYear',
align: 'center',
width: 100
},
{
title: '审计类型',
dataIndex: 'auditType',
key: 'auditType',
align: 'center',
width: 120
},
{
title: '审计发现的问题',
dataIndex: 'problemFound',
key: 'problemFound',
align: 'center',
width: 250,
// ellipsis: true
},
{
title: '整改要求',
dataIndex: 'rectificationRequirement',
key: 'rectificationRequirement',
align: 'center',
width: 200,
// ellipsis: true
},
{
title: '整改措施',
dataIndex: 'rectificationMeasures',
key: 'rectificationMeasures',
align: 'center',
width: 200,
// ellipsis: true
},
{
title: '整改完成情况',
dataIndex: 'rectificationStatus',
key: 'rectificationStatus',
align: 'center',
width: 120
},
{
title: '整改完成时间',
dataIndex: 'completionDate',
key: 'completionDate',
align: 'center',
width: 120
},
{
title: '整改责任人',
dataIndex: 'responsiblePerson',
key: 'responsiblePerson',
align: 'center',
width: 120
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 150
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
// ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
auditHistoryColumns
};

View File

@@ -0,0 +1,446 @@
// 创建测试结果渲染函数
export function createTestResultRender() {
return ({ text }) => {
if (text === '通过') return '<span style="color: #52c41a">通过</span>';
if (text === '不通过') return '<span style="color: #ff4d4f">不通过</span>';
return text || '待检查';
};
}
// 贯彻决策部署表格列
export const default1Columns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '单位名称',
dataIndex: 'unitName',
key: 'unitName',
align: 'center'
},
{
title: '规格',
dataIndex: 'specification',
key: 'specification',
align: 'center'
},
{
title: '经费来源',
dataIndex: 'fundSource',
key: 'fundSource',
align: 'center'
},
{
title: '编制数额',
dataIndex: 'establishmentNum',
key: 'establishmentNum',
align: 'center'
},
{
title: '编制数额',
align: 'center',
children: [
{
title: '其他编制',
dataIndex: 'otherEstablishment',
key: 'otherEstablishment',
align: 'center'
},
{
title: '事业编制',
dataIndex: 'institutionEstablishment',
key: 'institutionEstablishment',
align: 'center'
}
]
},
{
title: '实用人员',
align: 'center',
children: [
{
title: '参公人数',
dataIndex: 'publicServantNum',
key: 'publicServantNum',
align: 'center'
},
{
title: '聘用人数',
dataIndex: 'employedNum',
key: 'employedNum',
align: 'center'
}
]
},
{
title: '领导职数',
align: 'center',
children: [
{
title: '处级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'departmentApproved',
key: 'departmentApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'departmentActual',
key: 'departmentActual',
align: 'center'
}
]
},
{
title: '科级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'sectionApproved',
key: 'sectionApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'sectionActual',
key: 'sectionActual',
align: 'center'
}
]
},
{
title: '股级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'teamApproved',
key: 'teamApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'teamActual',
key: 'teamActual',
align: 'center'
}
]
}
]
},
{
title: '参公单位非领导职数',
align: 'center',
children: [
{
title: '处级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'nonLeaderDeptApproved',
key: 'nonLeaderDeptApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'nonLeaderDeptActual',
key: 'nonLeaderDeptActual',
align: 'center'
}
]
},
{
title: '科级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'nonLeaderSectionApproved',
key: 'nonLeaderSectionApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'nonLeaderSectionActual',
key: 'nonLeaderSectionActual',
align: 'center'
}
]
},
{
title: '股级',
align: 'center',
children: [
{
title: '核定数',
dataIndex: 'nonLeaderTeamApproved',
key: 'nonLeaderTeamApproved',
align: 'center'
},
{
title: '实有数',
dataIndex: 'nonLeaderTeamActual',
key: 'nonLeaderTeamActual',
align: 'center'
}
]
}
]
},
{
title: '岗位设置',
align: 'center',
children: [
{
title: '管理岗位',
dataIndex: 'managementPosition',
key: 'managementPosition',
align: 'center'
},
{
title: '专业技术岗位',
dataIndex: 'technicalPosition',
key: 'technicalPosition',
align: 'center'
},
{
title: '工勤技能岗位',
dataIndex: 'workerPosition',
key: 'workerPosition',
align: 'center'
}
]
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
align: 'center',
width: 100
}
];
// 领导班子名单列
export const leaderListColumns = [
{
title: '单位',
dataIndex: 'unit',
key: 'unit',
align: 'center',
width: 120
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
align: 'center',
width: 100
},
{
title: '部门',
dataIndex: 'department',
key: 'department',
align: 'center',
width: 120
},
{
title: '职务',
align: 'center',
children: [
{
title: '党内',
dataIndex: 'partyPosition',
key: 'partyPosition',
align: 'center',
width: 100
},
{
title: '行政',
dataIndex: 'adminPosition',
key: 'adminPosition',
align: 'center',
width: 120
}
]
},
{
title: '任职期间',
dataIndex: 'tenurePeriod',
key: 'tenurePeriod',
align: 'center',
width: 150
},
{
title: '主要工作责任',
dataIndex: 'mainResponsibilities',
key: 'mainResponsibilities',
align: 'center',
width: 200
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 120
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
align: 'center',
width: 100
}
];
// 决策支出表列
export const expenseColumns = [
{
title: '支出类型',
dataIndex: 'expenseType',
key: 'expenseType',
align: 'center',
width: 120
},
{
title: '年份',
dataIndex: 'year',
key: 'year',
align: 'center',
width: 100
},
{
title: '决算报表数(元)',
dataIndex: 'finalStatementAmount',
key: 'finalStatementAmount',
align: 'center',
width: 150
},
{
title: '年初预算数(元)',
dataIndex: 'initialBudgetAmount',
key: 'initialBudgetAmount',
align: 'center',
width: 150
},
{
title: '增减情况(%',
dataIndex: 'changePercentage',
key: 'changePercentage',
align: 'center',
width: 120
},
{
title: '占年初预算比例(%',
dataIndex: 'budgetRatio',
key: 'budgetRatio',
align: 'center',
width: 120
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 150
},
{
title: '数据来源',
dataIndex: 'dataSource',
key: 'dataSource',
align: 'center',
width: 180
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
align: 'center',
width: 100
}
];
// 八项规定表格列
export const eightRegColumns = [
{
title: '审计内容',
dataIndex: 'title',
key: 'title',
align: 'center',
width: 120
},
{
title: '审计内容',
dataIndex: 'content',
key: 'content',
align: 'center',
width: 200
},
{
title: '检查的证据及测试内容',
dataIndex: 'testContent',
key: 'testContent',
align: 'center',
width: 300
},
{
title: '测试结果',
dataIndex: 'result',
key: 'result',
align: 'center',
width: 100,
customRender: createTestResultRender()
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 140
// }
];
export default {
default1Columns,
leaderListColumns,
expenseColumns,
eightRegColumns,
createTestResultRender
};

View File

@@ -0,0 +1,62 @@
// 创建测试结果渲染函数
export function createTestResultRender() {
return ({ text }) => {
if (text === '通过') return '<span style="color: #52c41a">通过</span>';
if (text === '不通过') return '<span style="color: #ff4d4f">不通过</span>';
return text || '待检查';
};
}
// 审计内容2表格列
export const strategyAuditColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 60,
align: 'center'
},
{
title: '审计内容',
dataIndex: 'auditContent',
key: 'auditContent',
width: 150,
align: 'center',
// ellipsis: true
},
{
title: '检查的证据及测试内容',
dataIndex: 'checkEvidence',
key: 'checkEvidence',
width: 350,
align: 'center',
// ellipsis: true
},
{
title: '测试结果',
dataIndex: 'testResult',
key: 'testResult',
width: 100,
align: 'center',
customRender: createTestResultRender()
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
width: 200,
align: 'center',
// ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// width: 100,
// align: 'center'
// }
];
export default {
strategyAuditColumns,
createTestResultRender
};

View File

@@ -0,0 +1,140 @@
import { createTestResultRender } from './table2Columns';
// 三重一大表格列
export const tripleOneColumns = [
{
title: '',
dataIndex: 'category',
key: 'category',
width: 120,
align: 'center'
},
{
title: '政策内容',
dataIndex: 'policyContent',
key: 'policyContent',
align: 'center'
},
{
title: '集团制度',
dataIndex: 'groupSystem',
key: 'groupSystem',
align: 'center'
},
{
title: '公司制度',
dataIndex: 'companyFormulation',
key: 'companyFormulation',
align: 'center'
},
{
title: '公司执行情况',
dataIndex: 'checkEvidence',
key: 'checkEvidence',
align: 'center'
},
{
title: '执行结果',
dataIndex: 'testResult',
key: 'testResult',
align: 'center',
width: 80,
customRender: createTestResultRender()
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
align: 'center',
width: 100
}
];
// 重大经济决策表格列
export const decisionTableColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '重大经济决策事项',
dataIndex: 'decisionItem',
key: 'decisionItem',
width: 200,
align: 'center'
},
{
title: '会议时间',
dataIndex: 'meetingTime',
key: 'meetingTime',
align: 'center',
width: 120
},
{
title: '决策事项金额',
dataIndex: 'decisionAmount',
key: 'decisionAmount',
width: 120,
align: 'center'
},
{
title: '程序',
dataIndex: 'procedure',
key: 'procedure',
align: 'center'
},
{
title: '执行情况(是/否)',
dataIndex: 'executionStatus',
key: 'executionStatus',
width: 80,
align: 'center'
},
{
title: '执行效果(是否实现决策目标)',
children: [
{
title: '好',
dataIndex: 'goods',
key: 'goods',
width: 60,
align: 'center'
},
{
title: '一般',
dataIndex: 'normal',
key: 'normal',
width: 60,
align: 'center'
},
{
title: '差',
dataIndex: 'bad',
key: 'bad',
width: 60,
align: 'center'
}
]
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 140
// }
];
export default {
tripleOneColumns,
decisionTableColumns
};

View File

@@ -0,0 +1,87 @@
export const columns0 = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '年度',
dataIndex: 'year',
key: 'year',
align: 'center',
width: 120
},
{
title: '上级主管部门下达目标责任制',
align: 'center',
children: [
{
title: '下达文件',
dataIndex: 'superiorFile',
key: 'superiorFile',
align: 'center',
width: 200
},
{
title: '完成情况',
dataIndex: 'superiorCompletion',
key: 'superiorCompletion',
align: 'center',
width: 120
},
{
title: '未完成原因',
dataIndex: 'superiorReason',
key: 'superiorReason',
align: 'center',
width: 200
},
]
},
{
title: '单位自定下达目标责任制',
align: 'center',
children: [
{
title: '计划文件',
dataIndex: 'selfPlan',
key: 'selfPlan',
align: 'center',
width: 200
},
{
title: '完成情况',
dataIndex: 'selfCompletion',
key: 'selfCompletion',
align: 'center',
width: 120
},
{
title: '未完成原因',
dataIndex: 'selfReason',
key: 'selfReason',
align: 'center',
width: 200
},
]
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 200
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
}
];
export default { columns0 };

View File

@@ -0,0 +1,178 @@
export const budgetManageColumns = [
{
title: '预算科目名称',
dataIndex: 'budgetSubject',
key: 'budgetSubject',
align: 'center',
width: 150
},
{
title: '指标来源',
dataIndex: 'indicatorSource',
key: 'indicatorSource',
align: 'center',
width: 120
},
{
title: '合计',
dataIndex: 'total',
key: 'total',
align: 'center',
width: 100
},
{
title: '上年结余',
dataIndex: 'lastYearBalance',
key: 'lastYearBalance',
align: 'center',
width: 100
},
{
title: '年初部门预算',
dataIndex: 'initialBudget',
key: 'initialBudget',
align: 'center',
width: 120
},
{
title: '追加(减)预算',
dataIndex: 'additionalBudget',
key: 'additionalBudget',
align: 'center',
width: 120
},
{
title: '指标运用',
align: 'center',
children: [
{
title: '合计',
dataIndex: 'indicatorUseTotal',
key: 'indicatorUseTotal',
align: 'center',
width: 80
},
{
title: '拨款小计',
dataIndex: 'appropriationSubtotal',
key: 'appropriationSubtotal',
align: 'center',
width: 100
},
{
title: '拨款',
dataIndex: 'appropriation',
key: 'appropriation',
align: 'center',
width: 80
},
{
title: '工资统发',
dataIndex: 'salaryPayment',
key: 'salaryPayment',
align: 'center',
width: 100
},
{
title: '政府采购',
dataIndex: 'govProcurement',
key: 'govProcurement',
align: 'center',
width: 100
}
]
},
{
title: '指标结余',
dataIndex: 'indicatorBalance',
key: 'indicatorBalance',
align: 'center',
width: 100
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
{
title: '操作',
key: 'action',
align: 'center',
width: 100
}
];
// 预算执行情况列
export const budgetExecutionColumns = [
{
title: '项目',
dataIndex: 'project',
key: 'project',
align: 'center',
width: 150
},
{
title: '上年结转',
dataIndex: 'lastYearCarryOver',
key: 'lastYearCarryOver',
align: 'center',
width: 100
},
{
title: '本年预算小计',
dataIndex: 'currentYearBudgetTotal',
key: 'currentYearBudgetTotal',
align: 'center',
width: 120
},
{
title: '年初批复预算数',
dataIndex: 'initialApprovedBudget',
key: 'initialApprovedBudget',
align: 'center',
width: 120
},
{
title: '追加预算数',
dataIndex: 'additionalBudgetAmount',
key: 'additionalBudgetAmount',
align: 'center',
width: 100
},
{
title: '实际拨款数',
dataIndex: 'actualAppropriation',
key: 'actualAppropriation',
align: 'center',
width: 120
},
{
title: '指标结余',
dataIndex: 'indicatorBalance',
key: 'indicatorBalance',
align: 'center',
width: 100
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
budgetManageColumns,
budgetExecutionColumns
};

View File

@@ -0,0 +1,118 @@
export const stateAssetsManageColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 60,
align: 'center'
},
{
title: '国有资产名称',
dataIndex: 'assetName',
key: 'assetName',
align: 'center',
width: 120
},
{
title: '取得方式',
dataIndex: 'acquisitionMethod',
key: 'acquisitionMethod',
align: 'center',
width: 100
},
{
title: '资产价值',
dataIndex: 'assetValue',
key: 'assetValue',
align: 'center',
width: 100
},
{
title: '资产地址',
dataIndex: 'assetAddress',
key: 'assetAddress',
align: 'center',
width: 150
},
{
title: '面积',
dataIndex: 'area',
key: 'area',
align: 'center',
width: 80
},
{
title: '承租方',
dataIndex: 'tenant',
key: 'tenant',
align: 'center',
width: 100
},
{
title: '合同金额',
dataIndex: 'contractAmount',
key: 'contractAmount',
align: 'center',
width: 100
},
{
title: '租赁起止时间',
dataIndex: 'leasePeriod',
key: 'leasePeriod',
align: 'center',
width: 150
},
{
title: '租金缴纳时间',
dataIndex: 'rentPaymentTime',
key: 'rentPaymentTime',
align: 'center',
width: 120
},
{
title: '是否上平台',
dataIndex: 'platformLease',
key: 'platformLease',
align: 'center',
width: 100
},
{
title: '审批文件',
dataIndex: 'approvalDoc',
key: 'approvalDoc',
align: 'center',
width: 150
},
{
title: '纳入预算',
dataIndex: 'inBudget',
key: 'inBudget',
align: 'center',
width: 80
},
{
title: '备注',
dataIndex: 'remark',
key: 'remark',
align: 'center',
width: 150
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
stateAssetsManageColumns
};

View File

@@ -0,0 +1,55 @@
import { createTestResultRender } from './table2Columns';
// 重大投资情况审计表格列
export const investmentSituationColumns = [
{
title: '',
dataIndex: 'category',
key: 'category',
width: 150,
align: 'center',
},
{
title: '审计内容',
dataIndex: 'auditContent',
key: 'auditContent',
align: 'center',
width: 300,
// ellipsis: true
},
{
title: '检查的证据及测试内容',
dataIndex: 'checkEvidence',
key: 'checkEvidence',
align: 'center',
width: 400,
// ellipsis: true
},
{
title: '测试结果',
dataIndex: 'testResult',
key: 'testResult',
align: 'center',
width: 100,
customRender: createTestResultRender()
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
// ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
// 删除不再需要的单独列配置
export default {
investmentSituationColumns
};

View File

@@ -0,0 +1,83 @@
import { createTestResultRender } from './table2Columns';
// 内部控制测试表格列
export const internalControlTestColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '控制环节',
dataIndex: 'controlLink',
key: 'controlLink',
align: 'center',
width: 150
},
{
title: '控制要求',
dataIndex: 'controlRequirement',
key: 'controlRequirement',
align: 'center',
width: 200
},
{
title: '控制活动描述',
dataIndex: 'controlActivity',
key: 'controlActivity',
align: 'center',
width: 300,
// ellipsis: true
},
{
title: '控制职责岗位',
dataIndex: 'controlPosition',
key: 'controlPosition',
align: 'center',
width: 150
},
{
title: '测试步骤',
dataIndex: 'testSteps',
key: 'testSteps',
align: 'center',
width: 250,
// ellipsis: true
},
{
title: '测试结果',
dataIndex: 'testResult',
key: 'testResult',
align: 'center',
width: 100,
customRender: createTestResultRender()
},
{
title: '检查证据',
dataIndex: 'checkEvidence',
key: 'checkEvidence',
align: 'center',
width: 250,
// ellipsis: true
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
// ellipsis: true
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
internalControlTestColumns
};

View File

@@ -0,0 +1,55 @@
export const personnelEstablishmentColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '审计内容',
dataIndex: 'auditContent',
key: 'auditContent',
align: 'center',
width: 200,
},
{
title: '审计目标',
dataIndex: 'auditTarget',
key: 'auditTarget',
align: 'center',
width: 250,
},
{
title: '审计证据',
dataIndex: 'auditEvidence',
key: 'auditEvidence',
align: 'center',
width: 300,
},
{
title: '生成结果',
dataIndex: 'generationResult',
key: 'generationResult',
align: 'center',
width: 300,
},
{
title: '工作底稿索引',
dataIndex: 'workPaperIndex',
key: 'workPaperIndex',
align: 'center',
width: 200,
},
// {
// title: '操作',
// key: 'action',
// align: 'center',
// width: 100
// }
];
export default {
personnelEstablishmentColumns
};

View File

@@ -0,0 +1,60 @@
export const benefitEstablishmentColumns = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 80,
align: 'center'
},
{
title: '凭证号',
dataIndex: 'voucher',
key: 'voucher',
align: 'center'
},
{
title: '支出日期',
dataIndex: 'expenditureDate',
key: 'expenditureDate',
align: 'center'
},
{
title: '用途',
dataIndex: 'usage',
key: 'usage',
align: 'center'
},
{
title: '收款方',
dataIndex: 'payee',
key: 'payee',
align: 'center'
},
{
title: '金额(元)',
dataIndex: 'amount',
key: 'amount',
align: 'center'
},
{
title: '归属部门/人员',
dataIndex: 'belongTo',
key: 'belongTo',
align: 'center'
},
{
title: '款项性质说明',
dataIndex: 'natureDesc',
key: 'natureDesc',
align: 'center'
},
{
title: '违规说明',
dataIndex: 'violationDesc',
key: 'violationDesc',
align: 'center'
}
];
export default benefitEstablishmentColumns;

View File

@@ -0,0 +1,482 @@
import table9ExtraColumns from './table9ExtraColumns';
export const tableConfigs = {
// 审计内容1表格配置
auditContent1: {
type: 'auditContent1',
title: '贯彻决策部署情况',
options: [
// { title: '贯彻决策部署', value: 'default1', columns: () => import('./table1Columns').then(m => m.default1Columns) },
{
title: '领导班子名单',
value: 'leaderList',
columns: () =>
import('./table1Columns').then((m) => m.leaderListColumns)
},
{
title: '决策支出表',
value: 'expense',
columns: () => import('./table1Columns').then((m) => m.expenseColumns)
},
{
title: '八项规定',
value: 'eightReg',
columns: () => import('./table1Columns').then((m) => m.eightRegColumns)
}
],
// 接口映射
interfaceMap: {
default1: '/api/ai/auditContent1/generateDefault1Table',
leaderList: '/api/ai/auditContent1/generateLeaderListTable',
expense: '/api/ai/auditContent1/generateExpenseTable',
eightReg: '/api/ai/auditContent1/generateEightRegTable'
}
},
// 审计内容2表格配置
auditContent2: {
type: 'auditContent2',
title: '单位发展战略执行',
options: [
{
title: '单位发展战略执行',
value: 'strategyAudit',
columns: () =>
import('./table2Columns').then((m) => m.strategyAuditColumns)
}
],
interfaceMap: {
strategyAudit: '/api/ai/auditContent2/generateStrategyAuditTable'
}
},
// 审计内容3表格配置
auditContent3: {
type: 'auditContent3',
title: '重大经济决策调查',
options: [
{
title: '重大经济决策调查表',
value: 'decisionTable',
columns: () =>
import('./table3Columns').then((m) => m.decisionTableColumns)
},
{
title: '三重一大',
value: 'tripleOne',
columns: () => import('./table3Columns').then((m) => m.tripleOneColumns)
}
],
interfaceMap: {
tripleOne: '/api/ai/auditContent3/generateTripleOneTable',
decisionTable: '/api/ai/auditContent3/generateDecisionTable'
}
},
// 审计内容4表格配置
auditContent4: {
type: 'auditContent4',
title: '目标完成情况',
options: [
{
title: '目标责任完成情况表',
value: 'target',
columns: () => import('./table4Columns').then((m) => m.columns0)
}
],
interfaceMap: {
target: '/api/ai/auditContent4/generateTargetTable'
}
},
// 审计内容5表格配置
auditContent5: {
type: 'auditContent5',
title: '财务管理情况',
options: [
{
title: '预算管理审计',
value: 'budgetManage',
columns: () =>
import('./table5Columns').then((m) => m.budgetManageColumns)
},
{
title: '预算执行情况审计',
value: 'budgetExecution',
columns: () =>
import('./table5Columns').then((m) => m.budgetExecutionColumns)
}
],
interfaceMap: {
budgetManage: '/api/ai/auditContent5/generateBudgetManageTable',
budgetExecution: '/api/ai/auditContent5/generateBudgetExecutionTable'
}
},
// 审计内容6表格配置
auditContent6: {
type: 'auditContent6',
title: '国资管理情况',
options: [
{
title: '国有资产管理审计',
value: 'assets',
columns: () =>
import('./table6Columns').then((m) => m.stateAssetsManageColumns)
}
],
interfaceMap: {
assets: '/api/ai/auditContent6/generateAssetsTable'
}
},
auditContent7: {
type: 'auditContent7',
title: '重大投资情况',
options: [
{
title: '重大投资情况审计',
value: 'investmentSituation',
columns: () =>
import('./table7Columns').then((m) => m.investmentSituationColumns)
}
],
interfaceMap: {
investmentSituation:
'/api/ai/auditContent7/generateInvestmentSituationTable'
}
},
auditContent8: {
type: 'auditContent8',
title: '治理结构与内部控制',
options: [
{
title: '内部控制测试',
value: 'internalControl',
columns: () =>
import('./table8Columns').then((m) => m.internalControlTestColumns)
}
],
interfaceMap: {
internalControl: '/api/ai/auditContent8/generateInternalControlTable'
}
},
auditContent9: {
type: 'auditContent9',
title: '人员编制管理',
options: [
{
title: '人员编制管理审计',
value: 'personnel',
columns: () =>
import('./table9Columns').then((m) => m.personnelEstablishmentColumns)
}
],
extraTableTitle: '福利费超范围支出明细清单',
extraColumns: table9ExtraColumns,
interfaceMap: {
personnel: '/api/ai/auditContent9/generatePersonnelTable'
}
},
auditContent10: {
type: 'auditContent10',
title: '廉政情况',
options: [
{
title: '党风廉政建设责任制审计',
value: 'partyConduct',
columns: () =>
import('./table10Columns').then((m) => m.partyConductColumns)
}
],
interfaceMap: {
partyConduct: '/api/ai/auditContent10/generatePartyConductTable'
}
},
auditContent11: {
type: 'auditContent11',
title: '历史审计问题整改',
options: [
{
title: '历史审计问题整改',
value: 'history',
columns: () =>
import('./table11Columns').then((m) => m.auditHistoryColumns)
}
],
interfaceMap: {
history: '/api/ai/auditContent11/generateHistoryTable'
}
}
};
// 获取表格配置
export function getTableConfig(sectionIndex: number) {
const configKeys = Object.keys(tableConfigs);
if (sectionIndex >= 0 && sectionIndex < configKeys.length) {
return tableConfigs[configKeys[sectionIndex]];
}
return null;
}
// 获取所有表格配置
export function getAllTableConfigs() {
return tableConfigs;
}
// 通过接口名称查找表格配置
export function findTableConfigByInterface(interfaceName: string) {
for (const [key, config] of Object.entries(tableConfigs)) {
for (const [tableValue, tableInterface] of Object.entries(
config.interfaceMap || {}
)) {
if (tableInterface === interfaceName) {
return {
sectionType: key,
tableValue,
config
};
}
}
}
return null;
}
// 解析workPaperIndex字符串为对象数组
function parseWorkPaperIndex(workPaperIndex) {
if (!workPaperIndex || !Array.isArray(workPaperIndex)) return [];
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') {
const parts = item.split('||');
if (parts.length >= 3) {
return {
fileId: parts[0] || '',
fileName: parts[1] || '',
fileUrl: parts[2] || ''
};
}
}
// 兼容旧格式
return {
fileId: '',
fileName: typeof item === 'string' ? item : '',
fileUrl: ''
};
});
}
// 通用数据映射函数
export function createDataMapper(type: string) {
const mappers = {
default1: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
leaderList: (data) =>
data.map((item, index) => ({
key: index,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
expense: (data) =>
data.map((item, index) => ({
key: index,
...item,
finalStatementAmount: formatAmount(item.finalStatementAmount),
initialBudgetAmount: formatAmount(item.initialBudgetAmount),
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
eightReg: (data) =>
data.map((item, index) => ({
key: index,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
strategyAudit: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
tripleOne: (data) =>
data.map((item, index) => ({
key: index,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
decisionTable: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
goods: item.executionEffect?.good || item.goods || '否',
normal: item.executionEffect?.normal || item.normal || '否',
bad: item.executionEffect?.bad || item.bad || '否',
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
budgetManage: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
...item,
budgetSubject: item.budgetSubject || '-',
indicatorSource: item.indicatorSource,
total: formatAmount(item.indicatorSourceTotal),
lastYearBalance: formatAmount(item.indicatorSourceLastYearBalance),
initialBudget: formatAmount(item.indicatorSourceInitialBudget),
additionalBudget: formatAmount(item.indicatorSourceAdditionalBudget),
indicatorUseTotal: formatAmount(item.indicatorUseTotal),
appropriationSubtotal: formatAmount(
item.indicatorUseAppropriationSubtotal
),
appropriation: formatAmount(item.indicatorUseAppropriation),
salaryPayment: formatAmount(item.indicatorUseSalaryPayment),
govProcurement: formatAmount(item.indicatorUseGovProcurement),
indicatorBalance: formatAmount(item.indicatorBalance),
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
investmentSituation: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
category: item.category || '未分类',
auditContent: item.auditContent || item.content || '',
checkEvidence: item.checkEvidence || item.evidence || '',
testResult: item.testResult || '待检查',
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
})),
personnel: (data) => {
const mainData = data.map((item, index) => {
const { ext, ...rest } = item;
return {
key: index,
index: item.index || index + 1,
...rest,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
};
});
const extraData = data.reduce((acc, item) => {
if (item.ext && Array.isArray(item.ext)) {
return [...acc, ...item.ext];
}
return acc;
}, []);
return {
mainData: mainData,
extraData: extraData.map((item, idx) => ({
key: idx,
...item,
index: item.index || idx + 1
}))
};
},
// 其他类型的映射...
default: (data) =>
data.map((item, index) => ({
key: index,
index: index + 1,
...item,
workPaperIndex: parseWorkPaperIndex(item.workPaperIndex)
}))
};
return mappers[type] || mappers.default;
}
// 格式化金额
function formatAmount(amount) {
if (!amount) return '0.00';
if (typeof amount === 'string' && amount.includes(',')) return amount;
const num = parseFloat(amount);
return isNaN(num)
? '0.00'
: num.toLocaleString('zh-CN', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
}
// 生成接口映射
export const apiMethodMap = {
// 审计内容1
default1: {
generate: 'generateDefault1Table',
export: 'exportDefault1Table'
},
leaderList: {
generate: 'generateLeaderListTable',
export: 'exportLeaderListTable'
},
expense: { generate: 'generateExpenseTable', export: 'exportExpenseTable' },
eightReg: {
generate: 'generateEightRegTable',
export: 'exportEightRegTable'
},
// 审计内容2
strategyAudit: {
generate: 'generateStrategyAuditTable',
export: 'exportStrategyAuditTable'
},
// 审计内容3
tripleOne: {
generate: 'generateTripleOneTable',
export: 'exportTripleOneTable'
},
decisionTable: {
generate: 'generateDecisionTable',
export: 'exportDecisionTable'
},
// 审计内容4
target: { generate: 'generateTargetTable', export: 'exportTargetTable' },
// 审计内容5
budgetManage: {
generate: 'generateBudgetManageTable',
export: 'exportBudgetManageTable'
},
budgetExecution: {
generate: 'generateBudgetExecutionTable',
export: 'exportBudgetExecutionTable'
},
// 审计内容6
assets: { generate: 'generateAssetsTable', export: 'exportAssetsTable' },
// 审计内容7
investmentSituation: {
generate: 'generateInvestmentSituationTable',
export: 'exportInvestmentSituationTable'
},
// 审计内容8
internalControl: {
generate: 'generateInternalControlTable',
export: 'exportInternalControlTable'
},
// 审计内容9
personnel: {
generate: 'generatePersonnelTable',
export: 'exportPersonnelTable'
},
// 审计内容10
partyConduct: {
generate: 'generatePartyConductTable',
export: 'exportPartyConductTable'
},
// 审计内容11
history: { generate: 'generateHistoryTable', export: 'exportHistoryTable' },
// 默认
default: { generate: 'generateDefaultTable', export: 'exportDefaultTable' }
};
export default {
tableConfigs,
getTableConfig,
getAllTableConfigs,
findTableConfigByInterface,
createDataMapper,
apiMethodMap
};

View File

@@ -0,0 +1,60 @@
<!-- 搜索表单 -->
<template>
<a-space style="flex-wrap: wrap">
<a-button
v-if="hasRole('superAdmin')"
@click="openUrl('/count')"
>统计
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import {watch,nextTick} from 'vue';
import {CmsWebsite} from '@/api/cms/cmsWebsite/model';
import {openUrl} from "@/utils/common";
import {message} from 'ant-design-vue';
import {removeSiteInfoCache} from "@/api/cms/cmsWebsite";
import {hasRole} from "@/utils/permission";
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
website?: CmsWebsite;
count?: 0;
}>(),
{}
);
const emit = defineEmits<{
(e: 'add'): void;
}>();
const add = () => {
emit('add');
};
// 清除缓存
const clearSiteInfoCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId') + "*").then(
(msg) => {
if (msg) {
message.success(msg);
}
}
);
};
nextTick(() => {
if(localStorage.getItem('NotActive')){
// IsActive.value = false
}
})
watch(
() => props.selection,
() => {
}
);
</script>

View File

@@ -0,0 +1,812 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="1000"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑项目' : '添加项目'"
:body-style="{ paddingBottom: '28px' }"
:confirm-loading="loading"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:rules="rules"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="被审计单位" name="name">
<SelectCompany v-model:value="form.name" @done="onCompany" />
</a-form-item>
<a-form-item label="项目类型" name="type">
<DictSelect
dict-code="Type"
:width="200"
:show-search="true"
placeholder="项目类型"
v-model:value="form.type"
@done="chooseType"
/>
</a-form-item>
<a-form-item label="报告时间" name="expirationTime">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入报告时间"
v-model:value="form.expirationTime"
/>
</a-form-item>
<a-form-item label="项目名称" name="code">
<a-input
allow-clear
style="width: 400px"
placeholder="请输入项目名称"
v-model:value="form.code"
/>
</a-form-item>
<a-form-item label="案引号" name="caseIndex">
<a-input
allow-clear
style="width: 400px"
placeholder="请输入项目名称"
v-model:value="form.caseIndex"
/>
</a-form-item>
<!-- 行业库 -->
<a-form-item label="行业库" name="bizLibIds">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.bizLibIds"
mode="multiple"
style="width: 100%"
placeholder="选择行业库"
:options="bizLibList"
></a-select>
</a-form-item>
<!-- 公共库 -->
<a-form-item label="公共库" name="pubLibIds">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.pubLibIds"
mode="multiple"
style="width: 100%"
placeholder="选择公共库"
:options="pubLibList"
></a-select>
</a-form-item>
<a-divider orientation="left" style="margin-top: 50px">项目信息</a-divider>
<a-form-item label="开票单位/汇款人" name="itemName">
<a-input
allow-clear
style="width: 400px"
placeholder="请输入项目信息-开票单位/汇款人"
v-model:value="form.itemName"
/>
</a-form-item>
<a-form-item label="所属年度" name="itemYear">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入项目信息-年度"
v-model:value="form.itemYear"
/>
</a-form-item>
<a-form-item label="类型" name="itemType">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入类型"
v-model:value="form.itemType"
/>
</a-form-item>
<a-form-item label="审计意见" name="itemOpinion">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入审计意见"
v-model:value="form.itemOpinion"
/>
</a-form-item>
<a-form-item label="年末资产总额(万元)" name="totalAssets">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入年末资产总额(万元)"
v-model:value="form.totalAssets"
/>
</a-form-item>
<a-form-item label="合同金额" name="contractPrice">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入合同金额"
v-model:value="form.contractPrice"
/>
</a-form-item>
<a-form-item label="实收金额" name="payPrice">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入实收金额"
v-model:value="form.payPrice"
/>
</a-form-item>
<a-divider orientation="left" style="margin-top: 50px">到账信息</a-divider>
<a-form-item label="银行" name="bankName">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入银行名称"
v-model:value="form.bankName"
/>
</a-form-item>
<a-form-item label="日期" name="bankPayTime">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入到账日期"
v-model:value="form.bankPayTime"
/>
</a-form-item>
<a-form-item label="金额" name="bankPrice">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入到账金额"
v-model:value="form.bankPrice"
/>
</a-form-item>
<a-divider orientation="left" style="margin-top: 50px">开票信息</a-divider>
<a-form-item label="日期" name="invoiceTime">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入开票日期"
v-model:value="form.invoiceTime"
/>
</a-form-item>
<a-form-item label="金额" name="invoicePrice">
<a-input-number
allow-clear
style="width: 200px"
placeholder="请输入开票金额"
v-model:value="form.invoicePrice"
/>
</a-form-item>
<a-form-item label="类型" name="invoiceType">
<DictSelect
dict-code="InvoiceType"
:width="200"
:show-search="true"
placeholder="项目类型"
v-model:value="form.invoiceType"
/>
</a-form-item>
<a-divider orientation="left" style="margin-top: 50px">其他设置</a-divider>
<a-form-item label="报告份数" name="reportNum">
<a-input
allow-clear
style="width: 200px"
placeholder="请输入报告份数"
v-model:value="form.reportNum"
/>
</a-form-item>
<a-form-item label="底稿人员" name="draftUserId">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.draftUserId"
mode="tags"
style="width: 100%"
placeholder="选择底稿人员"
:options="userList"
@search="handleSearch"
@change="handleDraftUserId"
></a-select>
</a-form-item>
<a-form-item label="参与成员" name="userIds" extra="配置项目权限->参与成员可见">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.userIds"
mode="tags"
style="width: 100%"
placeholder="选择参与成员"
:options="userList"
@change="handleUsers"
></a-select>
</a-form-item>
<a-form-item label="签字注会" name="signUserId">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.signUserId"
mode="tags"
style="width: 100%"
placeholder="选择参与成员"
:options="userList"
@change="handleSignUser"
></a-select>
</a-form-item>
<a-form-item label="展业人员" name="saleUserId">
<a-select
show-search
:allow-clear="true"
optionFilterProp="label"
v-model:value="form.saleUserId"
mode="tags"
style="width: 100%"
placeholder="选择参与成员"
:options="userList"
@change="handleSaleUser"
></a-select>
</a-form-item>
<a-form-item label="电子底稿完成情况" name="electron">
<a-space direction="vertical">
<a-radio-group v-model:value="form.electron">
<a-radio :value="1">未完成</a-radio>
<a-radio :value="0">已完成</a-radio>
</a-radio-group>
</a-space>
</a-form-item>
<a-form-item label="纸质底稿完成情况" name="paper">
<a-space direction="vertical">
<a-radio-group v-model:value="form.paper">
<a-radio :value="1">未完成</a-radio>
<a-radio :value="0">已完成</a-radio>
</a-radio-group>
</a-space>
</a-form-item>
<a-form-item label="报告完成状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="1">未完成</a-radio>
<a-radio :value="0">已完成</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
placeholder="请输入排序号"
v-model:value="form.sortNumber"
/>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入描述"
v-model:value="form.comments"
/>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import {ref, reactive, watch} from 'vue';
import {Form, message} from 'ant-design-vue';
import {assignObject, uuid} from 'ele-admin-pro';
import {addPwlProject, updatePwlProject} from '@/api/pwl/pwlProject';
import {PwlProject} from '@/api/pwl/pwlProject/model';
import {useThemeStore} from '@/store/modules/theme';
import {storeToRefs} from 'pinia';
import {ItemType} from 'ele-admin-pro/es/ele-image-upload/types';
import {FormInstance} from 'ant-design-vue/es/form';
import DictSelect from "@/components/DictSelect/index.vue";
import {DictData} from "@/api/system/dict-data/model";
import {pageUsers} from "@/api/system/user";
import {User} from "@/api/system/user/model";
import {hasRole} from "@/utils/permission";
import SelectCompany from "@/components/SelectCompany/index.vue";
import {Company} from "@/api/system/company/model";
import {OaCompany} from "@/api/oa/oaCompany/model";
import {listPwlProjectLibrary} from '@/api/pwl/pwlProjectLibrary';
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const {styleResponsive} = storeToRefs(themeStore);
// 资料库响应式数据
const bizLibList = ref<any[]>([]);
const pubLibList = ref<any[]>([]);
// 存储完整的资料库数据,用于查找名称
const allLibraries = ref<any[]>([]);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: PwlProject | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const userList = ref<User[]>([]);
const draftUser = ref<any[]>([]);
const users = ref<any[]>([]);
const signUser = ref<any[]>([]);
const saleUser = ref<any[]>([]);
const keywords = ref<string>();
// 用户信息
const form = reactive<PwlProject>({
id: undefined,
name: undefined,
code: undefined,
caseIndex: undefined,
parentId: undefined,
type: undefined,
image: undefined,
qrcode: undefined,
url: undefined,
images: undefined,
files: undefined,
content: undefined,
totalAssets: undefined,
contractPrice: undefined,
payPrice: undefined,
price: undefined,
recommend: undefined,
expirationTime: undefined,
itemName: undefined,
itemYear: undefined,
itemType: undefined,
itemOpinion: undefined,
bankName: undefined,
bankPayTime: undefined,
bankPrice: undefined,
invoiceType: undefined,
invoiceTime: undefined,
invoicePrice: undefined,
reportNum: undefined,
draftUserId: [],
draftUser: [],
userIds: [],
users: [],
signUserId: [],
signUser: [],
saleUserId: [],
saleUser: [],
bizLibIds: [], // 修改为bizLibIds
bizLibName: [],
pubLibIds: [], // 修改为pubLibIds
pubLibName: [],
libraryIds: undefined,
analysisLibrary: undefined,
projectLibrary: undefined,
comments: undefined,
electron: undefined,
paper: undefined,
status: undefined,
deleted: undefined,
userId: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
sortNumber: 100,
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单验证规则
const rules = reactive({
type: [
{
required: true,
type: 'string',
message: '请选择项目类型',
trigger: 'change'
}
],
name: [
{
required: true,
type: 'string',
message: '请填写项目名称',
trigger: 'blur'
}
],
draftUserId: [
{
required: true,
type: 'array',
message: '请选择底稿人员',
trigger: 'change'
}
],
userIds: [
{
required: true,
type: 'array',
message: '请选择参与成员',
trigger: 'change'
}
],
saleUserId:[
{
required: true,
type: 'array',
message: '请选择展业人员',
trigger: 'change'
}
],
electron: [
{
required: true,
type: 'number',
message: '电子底稿是否已完成',
trigger: 'change'
}
],
paper: [
{
required: true,
type: 'number',
message: '纸质底稿是否已完成',
trigger: 'change'
}
],
status: [
{
required: true,
type: 'number',
message: '请选择项目状态',
trigger: 'change'
}
]
});
const chooseType = (data: DictData) => {
console.log(data);
form.type = data.dictDataName;
}
// const chooseImage = (data: FileRecord) => {
// images.value.push({
// uid: data.id,
// url: data.path,
// status: 'done'
// });
// form.image = data.path;
// };
//
// const onDeleteItem = (index: number) => {
// images.value.splice(index, 1);
// form.image = '';
// };
const onCompany = (item: OaCompany) => {
console.log('选择的公司:', item);
form.name = item.companyName;
form.companyId = item.companyId;
form.kbId = item.kbId;
form.libraryIds = item.libraryIds;
// 需要等待资料库列表加载完成后再设置回显
fetchLibraries().then(() => {
setLibraryEcho();
});
console.log('设置后的form.name:', form.name);
};
const handleSearch = (item) => {
keywords.value = item
fetchUsers();
}
const handleDraftUserId = (userId: number, item: User[]) => {
draftUser.value = [];
item.map(d => {
draftUser.value.push(d.realName);
});
console.log(userId)
}
const handleUsers = (userId: number, item: User[]) => {
users.value = [];
item.map(d => {
users.value.push(d.realName);
});
console.log(userId)
}
const handleSignUser = (userId: number, item: User[]) => {
signUser.value = [];
item.map(d => {
signUser.value.push(d.realName);
});
console.log(userId)
}
const handleSaleUser = (userId: number, item: User[]) => {
saleUser.value = [];
item.map(d => {
saleUser.value.push(d.realName);
});
console.log(userId)
}
// 获取用户列表
const fetchUsers = () => {
pageUsers({keywords: keywords.value,limit: 100}).then(res => {
userList.value = res?.list.map(d => {
d.label = d.realName;
d.value = d.userId;
return d;
}) || [];
})
};
// 获取资料库列表
const fetchLibraries = () => {
return listPwlProjectLibrary().then(res => {
allLibraries.value = res;
// 过滤行业库 (type='biz')
bizLibList.value = res
.filter(item => item.type === 'biz')
.map(item => ({
label: item.name,
value: String(item.id) // 将 ID 转换为字符串
}));
// 过滤公共库 (type='pub')
pubLibList.value = res
.filter(item => item.type === 'pub')
.map(item => ({
label: item.name,
value: String(item.id) // 将 ID 转换为字符串
}));
return res;
});
};
// 根据ID获取资料库名称
const getLibraryNamesByIds = (ids: string[]) => {
return ids.map(id => {
const library = allLibraries.value.find(lib => lib.id == id);
return library ? library.name : '';
}).filter(name => name !== '');
};
const {resetFields} = useForm(form, rules);
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
// 合并行业库和公共库的ID到libraryIds
const allLibraryIds = [
...(form.bizLibIds || []),
...(form.pubLibIds || [])
]
const formData = {
...form,
draftUserId: JSON.stringify(form.draftUserId),
draftUser: JSON.stringify(Array.from(new Set(draftUser.value))),
userIds: JSON.stringify(form.userIds),
users: JSON.stringify(Array.from(new Set(users.value))),
signUserId: JSON.stringify(form.signUserId),
signUser: JSON.stringify(Array.from(new Set(signUser.value))),
saleUserId: JSON.stringify(form.saleUserId),
saleUser: JSON.stringify(Array.from(new Set(saleUser.value))),
libraryIds: allLibraryIds.join(','), // 保存为逗号分隔的字符串
};
if (!hasRole('superAdmin')) {
form.payPrice = undefined;
}
if (!hasRole('admin')) {
form.contractPrice = undefined;
form.bankName = undefined;
form.bankPayTime = undefined;
form.bankPrice = undefined;
form.invoiceType = undefined;
form.invoiceTime = undefined;
form.invoicePrice = undefined;
}
console.log(formData);
const saveOrUpdate = isUpdate.value ? updatePwlProject : addPwlProject;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {
loading.value = false;
});
};
// 设置资料库回显
const setLibraryEcho = () => {
// 优先使用form中的libraryIds回退到props.data.libraryIds
const libraryIdsSource = form.libraryIds || props.data?.libraryIds;
if (libraryIdsSource) {
// 如果是字符串,按逗号分割成数组
if (typeof libraryIdsSource === 'string') {
const libraryIdsArray = libraryIdsSource.split(',').filter(id => id.trim() !== '');
// 清空当前的选择
form.bizLibIds = [];
form.pubLibIds = [];
// 根据资料库类型分别设置回显
libraryIdsArray.forEach(id => {
// 检查是否在行业库列表中
const isBizLib = bizLibList.value.some(lib => lib.value === String(id)); // 转换为字符串比较
// 检查是否在公共库列表中
const isPubLib = pubLibList.value.some(lib => lib.value === String(id)); // 转换为字符串比较
if (isBizLib) {
form.bizLibIds.push(String(id)); // 确保存储为字符串
}
if (isPubLib) {
form.pubLibIds.push(String(id)); // 确保存储为字符串
}
});
} else {
// 如果是数组,直接使用相同的逻辑
form.bizLibIds = [];
form.pubLibIds = [];
libraryIdsSource.forEach(id => {
const isBizLib = bizLibList.value.some(lib => lib.value === String(id));
const isPubLib = pubLibList.value.some(lib => lib.value === String(id));
if (isBizLib) {
form.bizLibIds.push(String(id));
}
if (isPubLib) {
form.pubLibIds.push(String(id));
}
});
}
} else {
form.bizLibIds = [];
form.pubLibIds = [];
}
};
watch(
() => props.visible,
async (visible) => {
if (visible) {
fetchUsers();
// 等待资料库列表加载完成后再设置回显
await fetchLibraries();
images.value = [];
if (props.data) {
assignObject(form, props.data);
if (props.data.image) {
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
// 处理底稿人员
if (props.data.draftUserId) {
form.draftUserId = JSON.parse(props.data.draftUserId);
} else {
form.draftUserId = [];
}
if (props.data.draftUser) {
const arr = JSON.parse(props.data.draftUser) || [];
arr.map(d => {
draftUser.value.push(d);
})
} else {
draftUser.value = [];
}
// 处理参与成员
if (props.data.userIds) {
form.userIds = JSON.parse(props.data.userIds) || [];
} else {
form.userIds = [];
}
if (props.data.users) {
const arr = JSON.parse(props.data.users) || [];
arr.map(d => {
users.value.push(d);
})
} else {
users.value = [];
}
// 处理签字注会
if (props.data.signUserId) {
form.signUserId = JSON.parse(props.data.signUserId) || [];
} else {
form.signUserId = [];
}
if (props.data.signUser) {
const arr = JSON.parse(props.data.signUser) || [];
arr.map(d => {
signUser.value.push(d);
})
} else {
signUser.value = [];
}
// 处理展业人员
if (props.data.saleUserId) {
form.saleUserId = JSON.parse(props.data.saleUserId) || [];
} else {
form.saleUserId = [];
}
if (props.data.saleUser) {
const arr = JSON.parse(props.data.saleUser) || [];
arr.map(d => {
saleUser.value.push(d);
})
} else {
saleUser.value = [];
}
// 设置资料库回显
setLibraryEcho();
isUpdate.value = true;
} else {
isUpdate.value = false;
// 新增时清空资料库选择
form.bizLibIds = [];
form.pubLibIds = [];
}
} else {
resetFields();
}
},
{immediate: true}
);
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
<a-button
danger
v-if="hasRole('superAdmin')"
type="primary"
class="ele-btn-icon"
:disabled="selection?.length === 0"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
<DictSelect
dict-code="Type"
:width="200"
:show-search="true"
placeholder="项目类型"
v-model:value="where.type"
@done="chooseType"
/>
<a-date-picker
v-model:value="where.itemYear"
value-format="YYYY"
picker="year"
@change="onYear"
/>
<a-input-search
allow-clear
placeholder="请输入关键词"
v-model:value="where.keywords"
@pressEnter="search"
@search="search"
/>
<a-button @click="reset">重置</a-button>
<div
class="border-blue-400 text-blue-400 rounded border border-solid py-1 px-2 text-sm cursor-pointer"
type="text"
v-if="hasRole('superAdmin')"
@click="handleExport"
>导出xls</div
>
<div
class="border-green-600 text-green-600 rounded border border-solid py-1 px-2 text-sm cursor-pointer"
type="text"
v-if="hasRole('superAdmin')"
@click="openImport"
>导入xls</div
>
</a-space>
<!-- 导入弹窗 -->
<Import v-model:visible="showImport" @done="search" />
</template>
<script lang="ts" setup>
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch, ref } from 'vue';
import { hasRole } from '@/utils/permission';
import dayjs from 'dayjs';
import { message } from 'ant-design-vue';
import { utils, writeFile } from 'xlsx';
import { PwlProject, PwlProjectParam } from '@/api/pwl/pwlProject/model';
import useSearch from '@/utils/use-search';
import { listPwlProject } from '@/api/pwl/pwlProject';
import Import from './Import.vue';
import DictSelect from '@/components/DictSelect/index.vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
// 日期范围选择
const dateRange = ref<[string, string]>(['', '']);
const loading = ref(false);
const projectList = ref<PwlProject[]>([]);
const xlsFileName = ref<string>();
// 是否显示用户导入弹窗
const showImport = ref(false);
// 表单数据
const { where, resetFields } = useSearch<PwlProjectParam>({
id: undefined,
type: undefined,
itemYear: undefined,
keywords: undefined
});
/* 打开编辑弹窗 */
const openImport = () => {
showImport.value = true;
};
const chooseType = (e) => {
console.log(e, 'yyyy');
where.type = e.label;
search();
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
const onYear = (date: any, dateString: string) => {
where.itemYear = dateString;
search();
};
/* 搜索 */
const search = () => {
const [d1, d2] = dateRange.value ?? [];
emit('search', {
...where,
createTimeStart: d1 ? d1 + ' 00:00:00' : '',
createTimeEnd: d2 ? d2 + ' 23:59:59' : ''
});
};
/* 重置 */
const reset = () => {
resetFields();
search();
};
// 导出
const handleExport = async () => {
loading.value = true;
const array: (string | number)[][] = [
[
'报告时间',
'审计单位',
'项目名称',
'项目信息-开票单位/汇款人',
'项目信息-所属年度',
'项目信息-类型',
'项目信息-审计意见',
'年末资产总额(万元)',
'合同金额',
'实收金额',
'到账信息-银行',
'到账信息-日期',
'到账信息-金额',
'开票信息-日期',
'开票信息-金额',
'开票信息-发票类型',
'报告份数',
'底稿人员',
'参与人员',
'签字注会',
'展业人员',
'底稿情况'
]
];
// 按搜索结果导出
where.sceneType = 'Content';
await listPwlProject(where)
.then((list) => {
projectList.value = list;
list?.forEach((d: PwlProject) => {
array.push([
`${d.expirationTime || ''}`,
`${d.name || ''}`,
`${d.code || ''}`,
`${d.itemName || ''}`,
`${d.itemYear || ''}`,
`${d.itemType || ''}`,
`${d.itemOpinion || ''}`,
`${d.totalAssets || ''}`,
// `${d.comments || ''}`,
`${d.contractPrice || ''}`,
`${d.payPrice || ''}`,
`${d.bankName || ''}`,
`${d.bankPayTime || ''}`,
`${d.bankPrice || ''}`,
`${d.invoiceTime || ''}`,
`${d.invoicePrice || ''}`,
`${d.invoiceType || ''}`,
`${d.reportNum || ''}`,
`${d.draftUser ? JSON.parse(d.draftUser).join(',') : ''}`,
`${d.users ? JSON.parse(d.users).join(',') : ''}`,
`${d.signUser ? JSON.parse(d.signUser).join(',') : ''}`,
`${d.saleUser ? JSON.parse(d.saleUser).join(',') : ''}`,
`${d.files || ''}`
]);
});
const sheetName = `导出项目列表${dayjs(new Date()).format('YYYYMMDD')}`;
const workbook = {
SheetNames: [sheetName],
Sheets: {}
};
const sheet = utils.aoa_to_sheet(array);
workbook.Sheets[sheetName] = sheet;
// 设置列宽
sheet['!cols'] = [];
message.loading('正在导出...');
setTimeout(() => {
writeFile(
workbook,
`${
where.createTimeEnd ? xlsFileName.value + '_' : ''
}${sheetName}.xlsx`
);
loading.value = false;
}, 1000);
})
.catch((msg) => {
message.error(msg);
loading.value = false;
})
.finally(() => {});
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,456 @@
.audit-content {
.audit-section {
.child-title {
font-weight: 600;
font-size: 15px;
color: #333;
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #1890ff;
display: flex;
justify-content: space-between;
align-items: center;
.ant-btn {
margin-left: 0;
}
}
}
}
.question-prompt {
color: #1677ff;
font-weight: 500;
margin-bottom: 8px;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
&::before {
content: '✍️';
margin-right: 6px;
font-size: 16px;
}
&:hover {
color: #0958d9;
}
}
.suggestion-textarea {
&:focus {
border-color: #1677ff !important;
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1) !important;
}
&:hover {
border-color: #4096ff !important;
}
}
/* textarea内嵌按钮样式 */
.textarea-with-button {
position: relative;
.suggestion-textarea-inner {
padding-right: 70px !important;
border-radius: 6px;
&:focus {
border-color: #1677ff !important;
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.1) !important;
}
&:hover {
border-color: #4096ff !important;
}
}
.send-button-inner {
position: absolute;
right: 8px;
bottom: 8px;
z-index: 10;
padding: 4px 12px;
height: 28px;
font-size: 12px;
&:hover {
transform: none;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.3);
}
}
}
:deep(.export-button) {
border: 2px solid #ff4d4f !important;
border-radius: 20px !important;
&:hover,
&:focus {
border-color: #ff7875 !important;
box-shadow: 0 0 8px rgba(255, 77, 79, 0.3);
}
}
/* 统一设置所有按钮为圆角 */
:deep(.ant-btn) {
border-radius: 20px !important;
transition: all 0.3s ease;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
}
/* 发送按钮特殊样式 */
:deep(.ant-btn-primary) {
border-radius: 20px !important;
&:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
}
/* 生成全部方案按钮橙色样式 */
:deep(.generate-all-button) {
background-color: #ff7b00 !important;
border-color: #ff7b00 !important;
&:hover {
background-color: #e56500 !important;
border-color: #e56500 !important;
box-shadow: 0 4px 12px rgba(255, 123, 0, 0.4) !important;
}
&:focus {
background-color: #e56500 !important;
border-color: #e56500 !important;
box-shadow: 0 4px 12px rgba(255, 123, 0, 0.4) !important;
}
}
.navigation-container {
.nav-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 20px;
}
.nav-button {
height: 48px;
border-radius: 20px !important;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: flex-start;
padding: 0 16px;
text-align: left;
.nav-number {
font-weight: bold;
margin-right: 8px;
min-width: 20px;
}
.nav-text {
flex: 1;
font-size: 14px;
}
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&.active {
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.3);
}
}
.progress-container {
display: flex;
align-items: center;
gap: 12px;
.progress-bar {
flex: 1;
height: 6px;
background: #f0f0f0;
border-radius: 3px;
overflow: hidden;
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #1890ff, #52c41a);
border-radius: 3px;
transition: width 0.3s ease;
}
}
.progress-text {
font-size: 12px;
color: #666;
min-width: 40px;
text-align: center;
}
}
}
.audit-content {
.audit-section {
scroll-margin-top: 20px;
.section-description {
color: #999999;
font-size: 14px;
margin-bottom: 8px;
padding: 12px;
background: #f8f9fa;
border-radius: 6px;
}
.child-section {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px dashed #e8e8e8;
&:last-child {
border-bottom: none;
margin-bottom: 0;
}
}
.child-title {
font-weight: 600;
font-size: 15px;
color: #333;
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #1890ff;
}
.content-textarea {
border-radius: 6px;
transition: all 0.3s ease;
&:focus {
border-color: #1890ff;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
}
}
}
:deep(.ant-card-head) {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border-radius: 6px 6px 0 0;
}
:deep(.ant-card-body) {
padding: 20px;
}
:deep(.ant-back-top) {
right: 30px;
bottom: 30px;
}
.btn {
padding: 5px 10px;
color: white;
border-radius: 999px;
cursor: pointer;
}
.btn-gray {
background-color: #bbbbbb;
}
.btn-green {
background-color: #479b33;
}
/* 选择文件按钮样式 */
.select-file-btn {
color: #1890ff;
font-size: 12px;
padding: 2px 8px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: #fff;
&:hover {
color: #40a9ff;
border-color: #40a9ff;
}
}
/* 文档选择弹窗样式 */
.doc-select-container {
height: 550px;
}
.doc-layout {
display: flex;
height: 100%;
gap: 16px;
}
.dir-tree-panel {
width: 280px;
border: 1px solid #e8e8e8;
border-radius: 6px;
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.dir-header {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 500;
}
.tree-container {
flex: 1;
padding: 8px;
overflow: auto;
}
.doc-list-panel {
flex: 1;
display: flex;
flex-direction: column;
border: 1px solid #e8e8e8;
border-radius: 6px;
min-width: 0;
overflow: hidden;
}
.doc-header {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
background: #fafafa;
}
.doc-actions {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.doc-tips {
color: #1890ff;
font-size: 14px;
font-weight: 500;
}
.doc-content {
flex: 1;
padding: 16px;
display: flex;
flex-direction: column;
overflow: hidden;
min-width: 0;
}
/* 树节点激活样式 */
:deep(.active-dir) {
color: #1890ff;
font-weight: 500;
}
:deep(.ant-tree-node-content-wrapper) {
border-radius: 4px;
transition: all 0.3s;
}
:deep(.ant-tree-node-content-wrapper:hover) {
background-color: #f5f5f5;
}
:deep(.ant-tree .ant-tree-treenode-selected .ant-tree-node-content-wrapper) {
background-color: #e6f7ff;
}
/* 优化表格样式 */
:deep(.doc-select-modal .ant-modal-body) {
padding: 16px;
}
:deep(.doc-content .ant-table-wrapper) {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
:deep(.doc-content .ant-spin-nested-loading) {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
:deep(.doc-content .ant-spin-container) {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
min-width: 0;
}
:deep(.doc-content .ant-table) {
width: 100%;
flex: 1;
}
:deep(.doc-content .ant-table-container) {
flex: 1;
display: flex;
flex-direction: column;
}
:deep(.doc-content .ant-table-body) {
flex: 1;
}
:deep(.doc-content .ant-table-thead > tr > th) {
background: #fafafa;
font-weight: 600;
white-space: nowrap;
}
:deep(.doc-content .ant-table-tbody > tr > td) {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 文件名列特殊处理,允许换行 */
:deep(.doc-content .ant-table-tbody > tr > td:first-child) {
white-space: normal;
word-break: break-word;
line-height: 1.4;
max-width: 400px;
}
/* 分页样式调整 */
:deep(.doc-content .ant-pagination) {
margin-top: 16px;
margin-bottom: 0;
}
:deep(.doc-content .ant-table-pagination) {
margin-top: 16px;
margin-bottom: 0;
flex-shrink: 0;
}

View File

@@ -0,0 +1,723 @@
<template>
<a-page-header :title="getPageTitle()" @back="() => $router.go(-1)">
<template #subTitle>
<div style="width: 300px;margin-left: 100px">
<a-steps :current="1" size="small">
<a-step title="单位信息" status="wait" />
<a-step title="项目管理" />
</a-steps>
</div>
</template>
<template #extra>
<Extra />
</template>
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="id"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
:scroll="{ x: 4000 }"
tool-class="ele-toolbar-form"
class="sys-org-table"
bordered
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'image'">
<a-image v-if="record.image" :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">已完成</a-tag>
<a-tag v-if="record.status === 1" color="red">未完成</a-tag>
</template>
<template v-if="column.key === 'draftUser'">
<a-space direction="vertical" v-if="record.draftUser">
<a-tag
v-for="(item, index) in JSON.parse(record.draftUser)"
:key="index"
>{{ item }}</a-tag
>
</a-space>
</template>
<template v-if="column.key === 'users'">
<a-space direction="vertical" v-if="record.users">
<a-tag
v-for="(item, index) in JSON.parse(record.users)"
:key="index"
>{{ item }}</a-tag
>
</a-space>
</template>
<template v-if="column.key === 'signUser'">
<a-space direction="vertical" v-if="record.signUser">
<a-tag
v-for="(item, index) in JSON.parse(record.signUser)"
:key="index"
>{{ item }}</a-tag
>
</a-space>
</template>
<template v-if="column.key === 'saleUser'">
<a-space direction="vertical" v-if="record.saleUser">
<a-tag
v-for="(item, index) in JSON.parse(record.saleUser)"
:key="index"
>{{ item }}</a-tag
>
</a-space>
</template>
<template v-if="column.key === 'electron'">
<span>电子</span>
<a-tag v-if="record.electron === 0" color="green">已完成</a-tag>
<a-tag v-else color="red">未完成</a-tag>
<span>纸质</span>
<a-tag v-if="record.paper === 0" color="green">已完成</a-tag>
<a-tag v-else color="red">未完成</a-tag>
</template>
<template v-if="column.key === 'createTime'">
<a-tooltip
:title="`创建于:${record.createTime}`"
class="flex flex-col"
>
<a-space>
<span>{{ toDateString(record.createTime, 'YYYY-MM-dd') }}</span>
<a-avatar :src="record.avatar" size="small" />
</a-space>
</a-tooltip>
</template>
<template v-if="column.key === 'action'">
<div>
<a-space>
<a class="action-btn bg-blue-500" @click="openReport(record)"
>1生成审计方案</a
>
<a class="action-btn bg-green-600" @click="openReportContent(record)">2审计内容</a>
<a class="action-btn bg-red-600" @click="openAuditCheck(record)"
>3审计核查</a
>
</a-space>
</div>
<div class="mt-2">
<a-space>
<a class="edit-btn" @click="openEdit(record)">修改</a>
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="remove-btn">删除</a>
</a-popconfirm>
</a-space>
</div>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<Edit v-model:visible="showEdit" :data="current" @done="reload" />
<!-- 生成报告 -->
<Report v-model:visible="showReport" :data="current" @done="reload" />
<ReportContent v-model:visible="showReportContent" :data="current" @done="reload" />
<!-- 审计核查弹窗 -->
<AuditCheck
v-model:visible="showAuditCheck"
:data="current"
@done="reload"
/>
<!-- 添加文档管理弹窗 -->
<a-modal
v-model:visible="showDocManage"
:title="`文档管理 - ${currentDocType} - ${current?.name || ''}`"
width="800px"
:footer="null"
>
<div style="margin-bottom: 16px">
<a-button type="primary" @click="openImport">新增文档</a-button>
</div>
<a-table
:dataSource="docList"
:columns="docColumns"
:loading="docLoading"
rowKey="id"
:pagination="{
current: currentPage,
pageSize: 10,
total: total,
showSizeChanger: false,
showTotal: (total) => `共 ${total} 条`
}"
@change="
(pag) => {
currentPage = pag.current;
loadDocuments();
}
"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-popconfirm
title="确定要删除此文档吗?"
@confirm="deleteDoc(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-modal>
<!-- 导入弹窗 -->
<Import
v-model:visible="showImport"
@done="loadDocuments"
:kbId="currentKbId"
/>
</a-page-header>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import { toDateString } from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import Edit from './components/pwlProjectEdit.vue';
import Report from './components/report.vue';
import ReportContent from './components/reportContent.vue';
import {
pagePwlProject,
removePwlProject,
removeBatchPwlProject
} from '@/api/pwl/pwlProject';
import type { PwlProject, PwlProjectParam } from '@/api/pwl/pwlProject/model';
import { getPageTitle } from '@/utils/common';
import Extra from './components/extra.vue';
import Import from '@/views/oa/oaCompany/components/Import.vue';
import {
getKnowledgeBaseDocuments,
deleteKnowledgeBaseDocument
} from '@/api/ai/knowledgeBase';
import AuditCheck from './components/auditCheck.vue';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<PwlProject[]>([]);
// 当前编辑数据
const current = ref<PwlProject | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示报告弹窗
const showReport = ref(false);
// 是否显示审计核查弹窗
const showAuditCheck = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// const draftUser = ref<string[]>([]);
// const users = ref<string[]>([]);
// const signUser = ref<string[]>([]);
// const saleUser = ref<string[]>([]);
// 加载状态
const loading = ref(true);
// 文档管理相关响应式变量
const showDocManage = ref(false); // 是否显示文档管理弹窗
const showImport = ref(false); // 是否显示导入弹窗
const currentKbId = ref(''); // 当前知识库ID
const currentDocType = ref(''); // 当前文档类型(材料分析/项目文档)
const docList = ref<any[]>([]); // 文档列表数据
const docLoading = ref(false); // 文档加载状态
const currentPage = ref(1);
const total = ref(0);
// 文档表格列配置
const docColumns = ref([
{
title: '文件名',
dataIndex: 'name',
key: 'fileName'
},
{
title: '文件大小',
dataIndex: 'size',
key: 'fileSize'
},
{
title: '上传时间',
dataIndex: 'gmtModified',
key: 'createTime',
customRender: ({ text }) => toDateString(text)
},
{
title: '操作',
key: 'action',
width: 100
}
]);
// 打开审计核查弹窗
const openAuditCheck = (record: PwlProject) => {
current.value = record;
showAuditCheck.value = true;
};
// 打开材料分析
const openCaseManagement = (record: PwlProject) => {
if (!record.analysisLibrary) {
message.warning('当前记录没有关联材料分析知识库');
return;
}
currentKbId.value = record.analysisLibrary;
currentDocType.value = '材料分析';
currentPage.value = 1;
showDocManage.value = true;
loadDocuments();
};
// 打开项目文档
const openDocumentManagement = (record: PwlProject) => {
if (!record.projectLibrary) {
message.warning('当前记录没有关联项目文档知识库');
return;
}
currentKbId.value = record.projectLibrary;
currentDocType.value = '项目文档';
currentPage.value = 1;
showDocManage.value = true;
loadDocuments();
};
// 加载文档列表
const loadDocuments = async () => {
docLoading.value = true;
try {
const response = await getKnowledgeBaseDocuments(
currentKbId.value,
10,
currentPage.value
);
docList.value = Array.isArray(response?.list) ? response.list : [];
total.value = response?.count || 0;
} catch (error) {
message.error('加载文档列表失败');
console.error('加载文档错误:', error);
} finally {
docLoading.value = false;
}
};
// 删除文档
const deleteDoc = async (record: any) => {
try {
await deleteKnowledgeBaseDocument(currentKbId.value, record.id);
// 立即本地删除
const index = docList.value.findIndex((item) => item.id === record.id);
if (index > -1) {
docList.value.splice(index, 1);
total.value -= 1;
}
message.success('删除成功');
} catch (error) {
message.error('删除失败');
console.error(error);
}
};
// 打开导入弹窗
const openImport = () => {
showImport.value = true;
};
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
return pagePwlProject({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: '序号',
key: 'index',
width: 48,
fixed: 'left',
align: 'center',
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
},
{
title: '被审计单位',
dataIndex: 'name',
key: 'name',
width: 240,
fixed: 'left',
align: 'center'
},
{
title: '项目名称',
dataIndex: 'code',
key: 'code',
width: 240,
sorter: true,
align: 'center'
},
{
title: '案引号',
dataIndex: 'caseIndex',
key: 'caseIndex',
width: 240,
sorter: true,
align: 'center'
},
{
title: '项目完成进度',
dataIndex: 'status',
key: 'status',
width: 120,
align: 'center'
},
{
title: '报告时间',
dataIndex: 'expirationTime',
key: 'expirationTime',
align: 'center',
width: 120
},
// {
// title: '类型',
// dataIndex: 'type',
// key: 'type',
// align: 'center',
// customRender: ({text}) => ['审字', '专审', '验证', '咨询'][text]
// },
{
title: '项目信息',
dataIndex: 'itemName',
key: 'itemName',
align: 'center',
children: [
{
title: '开票单位/汇款人',
dataIndex: 'itemName',
key: 'itemName',
align: 'center',
width: 180
},
{
title: '所属年度',
dataIndex: 'itemYear',
key: 'itemYear',
align: 'center'
},
{
title: '类型',
dataIndex: 'itemType',
key: 'itemType',
align: 'center'
},
{
title: '审计意见',
dataIndex: 'itemOpinion',
key: 'itemOpinion',
align: 'center'
}
]
},
{
title: '年末资产总额(万元)',
dataIndex: 'totalAssets',
key: 'totalAssets',
align: 'center',
width: 170
},
{
title: '合同金额',
dataIndex: 'contractPrice',
key: 'contractPrice',
align: 'center'
},
{
title: '实收金额',
dataIndex: 'payPrice',
key: 'payPrice',
align: 'center',
width: 120
},
{
title: '到账信息',
dataIndex: 'itemName',
key: 'itemName',
align: 'center',
children: [
{
title: '银行',
dataIndex: 'bankName',
key: 'bankName',
align: 'center',
width: 120
},
{
title: '日期',
dataIndex: 'bankPayTime',
key: 'bankPayTime',
align: 'center',
width: 120
},
{
title: '金额',
dataIndex: 'bankPrice',
key: 'bankPrice',
align: 'center',
width: 120
}
]
},
{
title: '开票信息',
dataIndex: 'invoice',
key: 'invoice',
align: 'center',
children: [
{
title: '日期',
dataIndex: 'invoiceTime',
key: 'invoiceTime',
align: 'center',
width: 120
},
{
title: '金额',
dataIndex: 'invoicePrice',
key: 'invoicePrice',
align: 'center',
width: 120
},
{
title: '发票类型',
dataIndex: 'invoiceType',
key: 'invoiceType',
align: 'center',
width: 120
}
]
},
{
title: '报告份数',
dataIndex: 'reportNum',
key: 'reportNum',
align: 'center',
width: 90
},
{
title: '底稿人员',
dataIndex: 'draftUser',
key: 'draftUser',
align: 'center',
width: 90
},
{
title: '参与成员',
dataIndex: 'users',
key: 'users',
align: 'center',
width: 180
},
{
title: '签字注会',
dataIndex: 'signUser',
key: 'signUser',
align: 'center',
width: 90
},
{
title: '展业人员',
dataIndex: 'saleUser',
key: 'saleUser',
align: 'center',
width: 90
},
{
title: '底稿完成情况',
dataIndex: 'electron',
key: 'electron',
align: 'center',
width: 120
},
{
title: '备注',
dataIndex: 'comments',
key: 'comments',
align: 'center',
width: 180
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
sorter: true,
width: 180,
ellipsis: true,
customRender: ({ text }) => toDateString(text, 'yyyy-MM-dd HH:mm')
},
{
title: '操作',
key: 'action',
width: 260,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: PwlProjectParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: PwlProject) => {
current.value = row ?? null;
showEdit.value = true;
};
const openReport = (row?: PwlProject) => {
current.value = row ?? null;
showReport.value = true;
};
const showReportContent = ref(false)
const openReportContent = (row?: PwlProject) => {
current.value = row ?? null;
showReportContent.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
/* 删除单个 */
const remove = (row: PwlProject) => {
const hide = message.loading('请求中..', 0);
removePwlProject(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchPwlProject(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: PwlProject) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'PwlProject'
};
</script>
<style lang="less" scoped>
.action-btn {
border-radius: 3px;
padding: 3px;
color: white;
font-size: 12px;
}
.edit-btn {
border-radius: 3px;
padding: 3px 10px;
color: #3B82F6;
border: 1px solid #3B82F6;
font-size: 12px;
}
.remove-btn {
border-radius: 3px;
padding: 3px 10px;
color: red;
border: 1px solid red;
font-size: 12px;
}
</style>
<style scoped>
/* 修改已完成步骤的连接线颜色 */
.ant-steps-item-wait > .ant-steps-item-container > .ant-steps-item-tail::after {
background-color: red !important;
}
</style>

View File

@@ -0,0 +1,312 @@
<!-- 编辑弹窗 -->
<template>
<ele-modal
:width="800"
:visible="visible"
:maskClosable="false"
:maxable="maxable"
:title="isUpdate ? '编辑资料库' : '添加资料库'"
:body-style="{ paddingBottom: '28px' }"
@update:visible="updateVisible"
@ok="save"
>
<a-form
ref="formRef"
:model="form"
:label-col="styleResponsive ? { md: 4, sm: 5, xs: 24 } : { flex: '90px' }"
:wrapper-col="
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
"
>
<a-form-item label="资料库名称" name="name">
<a-input
allow-clear
placeholder="请输入资料库名称"
v-model:value="form.name"
/>
</a-form-item>
<a-form-item label="类型" name="type">
<DictSelect
dict-code="LibraryType"
:width="200"
:show-search="true"
placeholder="项目类型"
v-model:value="form.type"
:field-names="{
label: 'dictDataName',
value: 'dictDataCode'
}"
/>
</a-form-item>
<!-- <a-form-item label="关联知识库" name="kbId">-->
<!-- <SelectDocsBook-->
<!-- v-model:value="kbDisplayName"-->
<!-- placeholder="请选择关联知识库"-->
<!-- @done="onSelectKnowledgeBase"-->
<!-- />-->
<!-- </a-form-item>-->
<a-form-item
label="资料库图标"
name="image">
<SelectFile
:placeholder="`请选择图片`"
:limit="1"
:data="images"
@done="chooseImage"
@del="onDeleteImage"
/>
</a-form-item>
<a-form-item label="资料库描述" name="content">
<a-textarea
:rows="3"
:maxlength="500"
placeholder="请输入资料库描述"
v-model:value="form.content"
/>
</a-form-item>
<a-form-item label="关联文件" name="files">
<SelectFile
:placeholder="'请选择关联文件'"
:limit="10"
:data="fileList"
@done="onSelectFile"
@del="onDeleteFile"
/>
</a-form-item>
<a-form-item label="备注" name="comments">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注信息"
v-model:value="form.comments"
/>
</a-form-item>
<a-form-item label="是否推荐" name="recommend">
<a-radio-group v-model:value="form.recommend">
<a-radio :value="1">推荐</a-radio>
<a-radio :value="0">不推荐</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="排序" name="sort">
<a-input-number
v-model:value="form.sort"
:min="0"
:max="9999"
placeholder="数字越小越靠前"
style="width: 100%"
/>
</a-form-item>
<a-form-item label="状态" name="status">
<a-radio-group v-model:value="form.status">
<a-radio :value="0">显示</a-radio>
<a-radio :value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</ele-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { Form, message } from 'ant-design-vue';
import { assignObject, uuid } from 'ele-admin-pro';
import { addPwlProjectLibrary, updatePwlProjectLibrary } from '@/api/pwl/pwlProjectLibrary';
import { PwlProjectLibrary } from '@/api/pwl/pwlProjectLibrary/model';
import { useThemeStore } from '@/store/modules/theme';
import { storeToRefs } from 'pinia';
import { ItemType } from 'ele-admin-pro/es/ele-image-upload/types';
import { FormInstance } from 'ant-design-vue/es/form';
import { FileRecord } from '@/api/system/file/model';
import SelectDocsBook from '@/components/SelectDocsBook/index.vue';
import SelectFile from '@/components/SelectFile/index.vue';
import DictSelect from "@/components/DictSelect/index.vue";
// 是否是修改
const isUpdate = ref(false);
const useForm = Form.useForm;
// 是否开启响应式布局
const themeStore = useThemeStore();
const { styleResponsive } = storeToRefs(themeStore);
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 修改回显的数据
data?: PwlProjectLibrary | null;
}>();
const emit = defineEmits<{
(e: 'done'): void;
(e: 'update:visible', visible: boolean): void;
}>();
// 提交状态
const loading = ref(false);
// 是否显示最大化切换按钮
const maxable = ref(true);
// 表格选中数据
const formRef = ref<FormInstance | null>(null);
const images = ref<ItemType[]>([]);
const fileList = ref<ItemType[]>([]);
const kbDisplayName = ref<string>('');
// 类型选项
const typeOptions = [
{ label: '项目案例库', value: 'biz' },
{ label: '公共知识库', value: 'pub' }
];
// 表单数据
const form = reactive<PwlProjectLibrary>({
id: undefined,
name: undefined,
type: undefined,
kbId: undefined,
sort: 0,
image: undefined,
content: undefined,
files: undefined,
recommend: 0,
deleted: undefined,
userId: undefined,
tenantId: undefined,
createTime: undefined,
updateTime: undefined,
status: 0,
comments: ''
});
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 选择图片
const chooseImage = (data: FileRecord) => {
images.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
form.image = data.path;
};
// 删除图片
const onDeleteImage = (index: number) => {
images.value.splice(index, 1);
form.image = '';
};
// 选择文件
const onSelectFile = (data: FileRecord) => {
fileList.value.push({
uid: data.id,
url: data.path,
status: 'done'
});
// 更新 form.files 字段
form.files = fileList.value.map(file => file.url).join(',');
};
// 删除文件
const onDeleteFile = (index: number) => {
fileList.value.splice(index, 1);
// 更新 form.files 字段
form.files = fileList.value.map(file => file.url).join(',');
};
// 选择知识库
const onSelectKnowledgeBase = (data: any) => {
form.kbId = data.id;
kbDisplayName.value = data.name;
};
/* 保存编辑 */
const save = () => {
if (!formRef.value) {
return;
}
formRef.value
.validate()
.then(() => {
loading.value = true;
const formData = {
...form
};
const saveOrUpdate = isUpdate.value ? updatePwlProjectLibrary : addPwlProjectLibrary;
saveOrUpdate(formData)
.then((msg) => {
loading.value = false;
message.success(msg);
updateVisible(false);
emit('done');
})
.catch((e) => {
loading.value = false;
message.error(e.message);
});
})
.catch(() => {});
};
watch(
() => props.visible,
(visible) => {
if (visible) {
images.value = [];
fileList.value = [];
kbDisplayName.value = '';
if (props.data) {
assignObject(form, props.data);
// 回显图片
if(props.data.image){
images.value.push({
uid: uuid(),
url: props.data.image,
status: 'done'
})
}
// 回显文件
if(props.data.files){
const files = props.data.files.split(',');
files.forEach((file) => {
if(file.trim()) {
fileList.value.push({
uid: uuid(),
url: file.trim(),
status: 'done'
})
}
});
}
// 回显知识库名称 - 这里可以根据实际需求通过API获取知识库名称
if(props.data.kbId) {
kbDisplayName.value = props.data.kbId;
}
isUpdate.value = true;
} else {
// 重置表单默认值
form.sort = 0;
form.recommend = 0;
form.status = 0;
isUpdate.value = false;
}
}
},
{ immediate: true }
);
</script>

View File

@@ -0,0 +1,42 @@
<!-- 搜索表单 -->
<template>
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
</a-space>
</template>
<script lang="ts" setup>
import { PlusOutlined } from '@ant-design/icons-vue';
import type { GradeParam } from '@/api/user/grade/model';
import { watch } from 'vue';
const props = withDefaults(
defineProps<{
// 选中的角色
selection?: [];
}>(),
{}
);
const emit = defineEmits<{
(e: 'search', where?: GradeParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchMove'): void;
}>();
// 新增
const add = () => {
emit('add');
};
watch(
() => props.selection,
() => {}
);
</script>

View File

@@ -0,0 +1,402 @@
<template>
<div class="page">
<div class="ele-body">
<a-card :bordered="false" :body-style="{ padding: '16px' }">
<ele-pro-table
ref="tableRef"
row-key="pwlProjectLibraryId"
:columns="columns"
:datasource="datasource"
:customRow="customRow"
tool-class="ele-toolbar-form"
class="sys-org-table"
>
<template #toolbar>
<search
@search="reload"
:selection="selection"
@add="openEdit"
@remove="removeBatch"
@batchMove="openMove"
/>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'type'">
<a-tag v-if="record.type === 'biz'" color="blue">行业库</a-tag>
<a-tag v-if="record.type === 'pub'" color="green">公共库</a-tag>
<span v-if="!record.type || (record.type !== 'biz' && record.type !== 'pub')">未设置</span>
</template>
<template v-if="column.key === 'image'">
<a-image :src="record.image" :width="50" />
</template>
<template v-if="column.key === 'status'">
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
<a-tag v-if="record.status === 1" color="red">隐藏</a-tag>
</template>
<template v-if="column.key === 'action'">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical" />
<!-- 文档管理按钮 -->
<a
:class="{ 'disabled-text': !record.kbId }"
:style="!record.kbId ? 'cursor: not-allowed; color: #999' : ''"
@click="record.kbId && openDocManage(record)"
>文档管理</a>
<a-divider type="vertical"/>
<a-popconfirm
title="确定要删除此记录吗?"
@confirm="remove(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</ele-pro-table>
</a-card>
<!-- 编辑弹窗 -->
<PwlProjectLibraryEdit v-model:visible="showEdit" :data="current" @done="reload" />
<!-- 文档管理弹窗 -->
<a-modal
v-model:visible="showDocManage"
:title="`文档管理 - ${currentKbName}`"
width="800px"
:footer="null"
>
<div style="margin-bottom: 16px;">
<a-button type="primary" @click="openImport">新增文档</a-button>
</div>
<a-table
:dataSource="docList"
:columns="docColumns"
:loading="docLoading"
rowKey="id"
:pagination="{
current: currentPage,
pageSize: 10,
total: total,
showSizeChanger: false,
showTotal: (total) => `共 ${total} 条`
}"
@change="(pag) => { currentPage = pag.current; loadDocuments(); }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<a-space>
<a-popconfirm
title="确定要删除此文档吗?"
@confirm="deleteDoc(record)"
>
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</template>
</a-table>
</a-modal>
<!-- 导入弹窗 -->
<Import v-model:visible="showImport" @done="loadDocuments" :kbId="currentKbId"/>
</div>
</div>
</template>
<script lang="ts" setup>
import { createVNode, ref } from 'vue';
import { message, Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import type { EleProTable } from 'ele-admin-pro';
import {toDateString} from 'ele-admin-pro';
import type {
DatasourceFunction,
ColumnItem
} from 'ele-admin-pro/es/ele-pro-table/types';
import Search from './components/search.vue';
import PwlProjectLibraryEdit from './components/pwlProjectLibraryEdit.vue';
import { pagePwlProjectLibrary, removePwlProjectLibrary, removeBatchPwlProjectLibrary } from '@/api/pwl/pwlProjectLibrary';
import type { PwlProjectLibrary, PwlProjectLibraryParam } from '@/api/pwl/pwlProjectLibrary/model';
import Import from '@/views/oa/oaCompany/components/Import.vue';
import {getKnowledgeBaseDocuments, deleteKnowledgeBaseDocument} from '@/api/ai/knowledgeBase';
// 表格实例
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
// 表格选中数据
const selection = ref<PwlProjectLibrary[]>([]);
// 当前编辑数据
const current = ref<PwlProjectLibrary | null>(null);
// 是否显示编辑弹窗
const showEdit = ref(false);
// 是否显示批量移动弹窗
const showMove = ref(false);
// 加载状态
const loading = ref(true);
// 文档管理相关响应式变量
const showDocManage = ref(false); // 是否显示文档管理弹窗
const showImport = ref(false); // 是否显示导入弹窗
// 新增分页状态变量
const currentPage = ref(1);
const total = ref(0);
// 文档管理相关变量
const currentKbId = ref(''); // 当前知识库ID
const currentKbName = ref(''); // 当前知识库名称
const docList = ref<any[]>([]); // 文档列表数据
const docLoading = ref(false); // 文档加载状态
// 文档表格列配置
const docColumns = ref([
{
title: '文件名',
dataIndex: 'name', // 改为接口返回的name字段
key: 'fileName',
},
{
title: '文件大小',
dataIndex: 'size', // 改为接口返回的size字段
key: 'fileSize',
},
{
title: '上传时间',
dataIndex: 'gmtModified', // 改为接口返回的gmtModified字段
key: 'createTime',
customRender: ({ text }) => toDateString(text) // 添加时间格式化
},
{
title: '操作',
key: 'action',
width: 100,
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({
page,
limit,
where,
orders,
filters
}) => {
if (filters) {
where.status = filters.status;
}
return pagePwlProjectLibrary({
...where,
...orders,
page,
limit
});
};
// 表格列配置
const columns = ref<ColumnItem[]>([
{
title: 'ID',
dataIndex: 'id',
key: 'id',
align: 'center',
width: 90,
},
{
title: '资料库',
dataIndex: 'name',
key: 'name',
align: 'center',
},
{
title: '类型',
dataIndex: 'type',
key: 'type',
align: 'center',
},
// {
// title: '关联知识库ID',
// dataIndex: 'kbId',
// key: 'kbId',
// align: 'center',
// },
// {
// title: '资料库图标',
// dataIndex: 'image',
// key: 'image',
// align: 'center',
// },
// {
// title: '是否推荐',
// dataIndex: 'recommend',
// key: 'recommend',
// align: 'center',
// },
// {
// title: '状态',
// dataIndex: 'status',
// key: 'status',
// align: 'center',
// },
// {
// title: '备注',
// dataIndex: 'comments',
// key: 'comments',
// align: 'center',
// },
{
title: '排序',
dataIndex: 'sort',
key: 'sort',
align: 'center',
},
{
title: '操作',
key: 'action',
width: 180,
fixed: 'right',
align: 'center',
hideInSetting: true
}
]);
/* 搜索 */
const reload = (where?: PwlProjectLibraryParam) => {
selection.value = [];
tableRef?.value?.reload({ where: where });
};
/* 打开编辑弹窗 */
const openEdit = (row?: PwlProjectLibrary) => {
current.value = row ?? null;
showEdit.value = true;
};
/* 打开批量移动弹窗 */
const openMove = () => {
showMove.value = true;
};
// 打开文档管理弹窗
const openDocManage = (record: PwlProjectLibrary) => {
currentKbId.value = record.kbId; // 使用record中的kbId
currentKbName.value = record.name; // 使用单位名称作为知识库名称
currentPage.value = 1;
showDocManage.value = true;
loadDocuments();
};
// 加载文档列表
const loadDocuments = async () => {
docLoading.value = true;
try {
const response = await getKnowledgeBaseDocuments(
currentKbId.value,
10,
currentPage.value
);
docList.value = Array.isArray(response?.list) ? response.list : [];
total.value = response?.count || 0;
} catch (error) {
message.error('加载文档列表失败');
console.error('加载文档错误:', error);
} finally {
docLoading.value = false;
}
};
// 删除文档
const deleteDoc = async (record: any) => {
try {
// 执行删除操作
await deleteKnowledgeBaseDocument(currentKbId.value, record.id);
// 立即本地删除(核心修改)
const index = docList.value.findIndex(item => item.id === record.id);
if (index > -1) {
docList.value.splice(index, 1);
total.value -= 1;
}
message.success('删除成功');
// 阿里云异步删除,需等待异步删除完成再重新查询
// await loadDocuments();
} catch (error) {
message.error('删除失败');
console.error(error);
}
};
// 打开导入弹窗
const openImport = () => {
showImport.value = true;
};
/* 删除单个 */
const remove = (row: PwlProjectLibrary) => {
const hide = message.loading('请求中..', 0);
removePwlProjectLibrary(row.id)
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
};
/* 批量删除 */
const removeBatch = () => {
if (!selection.value.length) {
message.error('请至少选择一条数据');
return;
}
Modal.confirm({
title: '提示',
content: '确定要删除选中的记录吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = message.loading('请求中..', 0);
removeBatchPwlProjectLibrary(selection.value.map((d) => d.id))
.then((msg) => {
hide();
message.success(msg);
reload();
})
.catch((e) => {
hide();
message.error(e.message);
});
}
});
};
/* 查询 */
const query = () => {
loading.value = true;
};
/* 自定义行属性 */
const customRow = (record: PwlProjectLibrary) => {
return {
// 行点击事件
onClick: () => {
// console.log(record);
},
// 行双击事件
onDblclick: () => {
openEdit(record);
}
};
};
query();
</script>
<script lang="ts">
export default {
name: 'PwlProjectLibrary'
};
</script>
<style lang="less" scoped></style>