2080 lines
53 KiB
Vue
2080 lines
53 KiB
Vue
<!-- 用户编辑弹窗 -->
|
||
<template>
|
||
<a-drawer
|
||
:width="`95%`"
|
||
:visible="visible"
|
||
:confirm-loading="loading"
|
||
:maxable="maxAble"
|
||
title="AI审计内容生成器"
|
||
:body-style="{ paddingBottom: '8px', background: '#f3f3f3' }"
|
||
@update:visible="updateVisible"
|
||
:maskClosable="false"
|
||
:footer="null"
|
||
@ok="save"
|
||
>
|
||
<a-card title="基本信息" style="margin-bottom: 20px" :bordered="false">
|
||
<a-descriptions>
|
||
<a-descriptions-item
|
||
label="公司名称"
|
||
:labelStyle="{ width: '90px', color: '#808080' }"
|
||
>
|
||
<span @click="copyText(form.name)">{{ form.name }}</span>
|
||
</a-descriptions-item>
|
||
<a-descriptions-item
|
||
label="被审计人"
|
||
:labelStyle="{ width: '90px', color: '#808080' }"
|
||
>
|
||
<span>{{ form.nickname || '暂无' }}</span>
|
||
</a-descriptions-item>
|
||
<a-descriptions-item
|
||
label="审计时间"
|
||
:labelStyle="{ width: '90px', color: '#808080' }"
|
||
>
|
||
<span>{{ form.expirationTime }}</span>
|
||
</a-descriptions-item>
|
||
</a-descriptions>
|
||
</a-card>
|
||
|
||
<a-card
|
||
style="margin-bottom: 20px; text-align: center; background: transparent"
|
||
:bordered="false"
|
||
>
|
||
<a-space>
|
||
<a-button
|
||
size="large"
|
||
type="primary"
|
||
@click="handleGenerateAll"
|
||
:loading="generatingAll"
|
||
class="generate-all-button"
|
||
>
|
||
<template #icon>
|
||
<UngroupOutlined />
|
||
</template>
|
||
生成全部方案
|
||
</a-button>
|
||
<a-button size="large">
|
||
<template #icon>
|
||
<DownloadOutlined />
|
||
</template>
|
||
保存草稿
|
||
</a-button>
|
||
<a-button size="large">
|
||
<template #icon>
|
||
<RedoOutlined />
|
||
</template>
|
||
加载草稿
|
||
</a-button>
|
||
<a-button
|
||
size="large"
|
||
type="danger"
|
||
class="export-button"
|
||
@click="handleExport"
|
||
>
|
||
<template #icon>
|
||
<UploadOutlined />
|
||
</template>
|
||
下载文件
|
||
</a-button>
|
||
</a-space>
|
||
</a-card>
|
||
|
||
<!-- 快速导航 -->
|
||
<a-card style="margin-bottom: 20px" :bordered="false">
|
||
<template #title>
|
||
<div
|
||
style="
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
"
|
||
>
|
||
<span>快速导航</span>
|
||
<a-tooltip title="快捷键:Ctrl+1~9 快速跳转,Ctrl+↑↓ 上下导航">
|
||
<QuestionCircleOutlined style="color: #999; cursor: help" />
|
||
</a-tooltip>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="navigation-container">
|
||
<div class="nav-grid">
|
||
<a-button
|
||
v-for="(item, index) in navigationItems"
|
||
:key="index"
|
||
:type="currentSection === index ? 'primary' : 'default'"
|
||
size="small"
|
||
@click="scrollToSection(index)"
|
||
class="nav-button"
|
||
:class="{ active: currentSection === index }"
|
||
>
|
||
<span class="nav-number">{{ item.number }}</span>
|
||
<span class="nav-text">{{ item.name }}</span>
|
||
</a-button>
|
||
</div>
|
||
|
||
<!-- 进度指示器 -->
|
||
<div class="progress-container">
|
||
<div class="progress-bar">
|
||
<div
|
||
class="progress-fill"
|
||
:style="{
|
||
width: `${(
|
||
((currentSection + 1) / navigationItems.length) * 100
|
||
)}%`
|
||
}"
|
||
></div>
|
||
</div>
|
||
<div class="progress-text">
|
||
{{ currentSection + 1 }} / {{ navigationItems.length }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
|
||
<!-- 审计方案内容 -->
|
||
<div class="audit-content">
|
||
<div
|
||
v-for="(item, index) in navigationItems"
|
||
:key="index"
|
||
:id="`section-${index}`"
|
||
class="audit-section"
|
||
>
|
||
<a-card style="margin-bottom: 20px" :bordered="false">
|
||
<template #title>
|
||
<span class="font-bold">{{ `${item.number}、${item.name}` }}</span>
|
||
<span class="ml-4 text-gray-400" v-if="item.extra3">{{ table3Title }}</span>
|
||
<span class="ml-4 text-gray-400" v-if="item.file3">
|
||
<!-- 修改选择文件组件 -->
|
||
<a-button
|
||
type="link"
|
||
@click="openDocSelect(index)"
|
||
class="select-file-btn"
|
||
>
|
||
<FolderOpenOutlined />
|
||
选择文件
|
||
</a-button>
|
||
</span>
|
||
</template>
|
||
<template #extra>
|
||
<a-button
|
||
type="primary"
|
||
@click="generateContent(index)"
|
||
:loading="item.generating"
|
||
>
|
||
<template #icon>
|
||
<RobotOutlined />
|
||
</template>
|
||
AI生成
|
||
</a-button>
|
||
</template>
|
||
<template v-if="item.extra3">
|
||
<div class="mb-2">
|
||
<a-space>
|
||
<div
|
||
class="btn"
|
||
:class="[
|
||
table3Title === '重大经济决策调查表'
|
||
? 'btn-green'
|
||
: 'btn-gray'
|
||
]"
|
||
@click="changeTable3('重大经济决策调查表')"
|
||
>重大经济决策调查表</div
|
||
>
|
||
<div
|
||
class="btn"
|
||
:class="[
|
||
table3Title === '三重一大' ? 'btn-green' : 'btn-gray'
|
||
]"
|
||
@click="changeTable3('三重一大')"
|
||
>三重一大</div
|
||
>
|
||
</a-space>
|
||
</div>
|
||
</template>
|
||
<a-table :columns="item.columns" bordered :data-source="item.data">
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'action'">
|
||
<a-button type="primary" danger>删除</a-button>
|
||
</template>
|
||
<template v-if="column.key === 'testResult'">
|
||
<span class="text-green-400" v-if="record.testResult === '通过'">通过</span>
|
||
<span class="text-red-400" v-else-if="record.testResult === '不通过'">不通过</span>
|
||
<span class="text-gray-400" v-else>{{ record.testResult || '待检查' }}</span>
|
||
</template>
|
||
<template v-if="column.key === 'workPaperIndex'">
|
||
<div v-for="(fileItem, fileIndex) in record.workPaperIndex" :key="fileIndex">
|
||
<img src="@/assets/word.png" style="width: 20px; height: 20px" alt="">
|
||
<span class="cursor-pointer text-wrap">{{ fileItem }}</span>
|
||
</div>
|
||
</template>
|
||
<template v-if="column.key === 'fileIndex'">
|
||
<div v-for="(fileItem, fileIndex) in record.fileIndex" :key="fileIndex">
|
||
<img src="@/assets/word.png" style="width: 20px; height: 20px" alt="">
|
||
<span class="cursor-pointer text-wrap">{{ fileItem }}</span>
|
||
</div>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
|
||
<div style="margin-top: 12px">
|
||
<div class="question-prompt">AI小助手</div>
|
||
<div class="textarea-with-button" style="width: 600px">
|
||
<a-textarea
|
||
v-model:value="item.suggestion"
|
||
:rows="3"
|
||
placeholder="请输入您的要求并回车..."
|
||
class="suggestion-textarea-inner"
|
||
@pressEnter="generateContent(index)"
|
||
/>
|
||
<a-button
|
||
type="danger"
|
||
size="small"
|
||
@click="generateContent(index)"
|
||
:loading="item.generating"
|
||
class="send-button-inner"
|
||
>
|
||
<template #icon>
|
||
<RobotOutlined />
|
||
</template>
|
||
发送
|
||
</a-button>
|
||
</div>
|
||
</div>
|
||
</a-card>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 返回顶部按钮 -->
|
||
<a-back-top :target="getScrollContainer" />
|
||
|
||
<!-- 文档选择弹窗 -->
|
||
<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>
|
||
</a-drawer>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, reactive, watch, onMounted, onUnmounted, computed } from 'vue';
|
||
import { Form, message } from 'ant-design-vue';
|
||
import { assignObject } from 'ele-admin-pro';
|
||
import { copyText } from '@/utils/common';
|
||
import { PwlProject } from '@/api/pwl/pwlProject/model';
|
||
import {
|
||
UngroupOutlined,
|
||
DownloadOutlined,
|
||
UploadOutlined,
|
||
RedoOutlined,
|
||
RobotOutlined,
|
||
QuestionCircleOutlined,
|
||
FolderOpenOutlined,
|
||
FileOutlined
|
||
} from '@ant-design/icons-vue';
|
||
import {
|
||
generateAuditReport,
|
||
downloadAuditReport
|
||
} from '@/api/ai/auditReport';
|
||
import { getPwlProjectLibraryByIds } from '@/api/pwl/pwlProjectLibrary';
|
||
import { generateTripleOneTable } from '@/api/ai/auditContent';
|
||
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 useForm = Form.useForm;
|
||
|
||
const props = defineProps<{
|
||
// 弹窗是否打开
|
||
visible: boolean;
|
||
// 修改回显的数据
|
||
data?: PwlProject | null;
|
||
}>();
|
||
|
||
let columns = ref([
|
||
{
|
||
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'
|
||
}
|
||
]
|
||
}
|
||
]);
|
||
|
||
// 是否显示最大化切换按钮
|
||
const maxAble = ref(true);
|
||
// 添加生成状态
|
||
const generatingAll = ref(false);
|
||
// 当前选中的章节
|
||
const currentSection = ref(0);
|
||
|
||
// 存储拼接后的 kbIds
|
||
const combinedKbIds = ref('');
|
||
|
||
// 九大审计项目导航配置
|
||
const table3Title = ref('重大经济决策调查表');
|
||
const navigationItems = ref([
|
||
{
|
||
number: '一',
|
||
name: '审计内容1',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
placeholder: '请输入审计依据内容...',
|
||
content: '',
|
||
suggestion: '',
|
||
formCommit: 10,
|
||
rows: 10,
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '二',
|
||
name: '审计内容2',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
placeholder: '请输入审计目标内容...',
|
||
content: '',
|
||
suggestion: '',
|
||
formCommit: 20,
|
||
rows: 6,
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '三',
|
||
name: '审计内容3',
|
||
title: '重大经济决策调查表',
|
||
description: '点击"AI生成"按钮生成重大经济决策调查表数据',
|
||
generating: false,
|
||
extra3: true,
|
||
file3: true,
|
||
columns: [
|
||
{
|
||
title: '序号',
|
||
dataIndex: 'index',
|
||
key: 'index',
|
||
width: 80,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '重大经济决策事项',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
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',
|
||
customRender: ({ text }) => text || '程序未记录'
|
||
},
|
||
{
|
||
title: '执行情况(是/否)',
|
||
dataIndex: 'executionStatus',
|
||
key: 'executionStatus',
|
||
width: 80,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '执行效果(是否实现决策目标)',
|
||
children: [
|
||
{
|
||
title: '好',
|
||
dataIndex: 'goods',
|
||
key: 'goods',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
},
|
||
{
|
||
title: '一般',
|
||
dataIndex: 'normal',
|
||
key: 'normal',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
},
|
||
{
|
||
title: '差',
|
||
dataIndex: 'bad',
|
||
key: 'bad',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
align: 'center',
|
||
width: 140
|
||
}
|
||
],
|
||
data: []
|
||
},
|
||
{
|
||
number: '四',
|
||
name: '审计内容4',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '五',
|
||
name: '审计内容5',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
generating: false,
|
||
columns: [
|
||
{
|
||
title: '预算科目名称',
|
||
dataIndex: 'index',
|
||
key: 'index',
|
||
width: 80,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '指标来源',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
children: [
|
||
{
|
||
title: '合计'
|
||
},
|
||
{
|
||
title: '上年结余'
|
||
},
|
||
{
|
||
title: '年初部门预算'
|
||
},
|
||
{
|
||
title: '追加(减)预算'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: '指标运用',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
children: [
|
||
{
|
||
title: '合计'
|
||
},
|
||
{
|
||
title: '拨款',
|
||
children: [
|
||
{
|
||
title: '小计'
|
||
},
|
||
{
|
||
title: '拨款',
|
||
children: [
|
||
{
|
||
title: '工资统发'
|
||
},
|
||
{
|
||
title: '政府采购'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: '财政管理转户'
|
||
},
|
||
{
|
||
title: '部门预算用于其他'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: '指标结余',
|
||
children: [
|
||
{
|
||
title: '小计'
|
||
},
|
||
{
|
||
title: '政府采购'
|
||
},
|
||
{
|
||
title: '应拨单位款'
|
||
},
|
||
{
|
||
title: '其他'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
{
|
||
number: '六',
|
||
name: '审计内容6',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '七',
|
||
name: '审计内容7',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
placeholder: '请输入审计技术方法内容...',
|
||
content: '',
|
||
suggestion: '',
|
||
formCommit: 70,
|
||
rows: 8,
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '八',
|
||
name: '审计内容8',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
placeholder: '请输入工作步骤与时间安排内容...',
|
||
content: '',
|
||
suggestion: '',
|
||
formCommit: 80,
|
||
rows: 10,
|
||
generating: false,
|
||
columns: columns.value
|
||
},
|
||
{
|
||
number: '九',
|
||
name: '审计内容9',
|
||
title: '',
|
||
description: '点击"AI生成"按钮让AI为您生成该部分内容,或直接在下方编辑',
|
||
placeholder: '请输入审计工作的组织实施内容...',
|
||
content: '',
|
||
suggestion: '',
|
||
formCommit: 90,
|
||
rows: 8,
|
||
generating: false,
|
||
columns: columns.value
|
||
}
|
||
]);
|
||
|
||
// 文档选择相关变量
|
||
const showDocSelect = ref(false);
|
||
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 lastSelectedDirKeys = ref<(string | number)[]>([]);
|
||
const lastSelectedFileKeys = ref<(string | number)[]>([]);
|
||
|
||
// 文档管理相关变量
|
||
const currentKbId = ref('');
|
||
const currentKbName = ref('');
|
||
const currentCompanyId = ref<number>();
|
||
const docList = ref<AiCloudFile[]>([]);
|
||
const docLoading = ref(false);
|
||
const allDirs = ref<AiCloudDoc[]>([]);
|
||
|
||
// 树形结构相关
|
||
const expandedKeys = ref<(string | number)[]>([]);
|
||
const selectedKeys = ref<(string | number)[]>([]);
|
||
const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
|
||
|
||
// 分页配置
|
||
const pagination = ref({
|
||
current: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
showSizeChanger: false,
|
||
showQuickJumper: true,
|
||
showTotal: (total: number) => `共 ${total} 条`,
|
||
pageSizeOptions: ['10', '20', '50', '100']
|
||
});
|
||
|
||
// 文档表格列配置
|
||
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 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 emit = defineEmits<{
|
||
(e: 'done'): void;
|
||
(e: 'update:visible', visible: boolean): void;
|
||
}>();
|
||
|
||
// 订单信息
|
||
const form = reactive<PwlProject>({
|
||
// ID
|
||
id: undefined,
|
||
// 项目名称
|
||
name: undefined,
|
||
// 项目标识
|
||
code: undefined,
|
||
// 上级id, 0是顶级
|
||
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: undefined,
|
||
// 底稿人员
|
||
draftUser: undefined,
|
||
// 参与成员
|
||
userIds: undefined,
|
||
users: undefined,
|
||
// 签字注会
|
||
signUserId: undefined,
|
||
signUser: undefined,
|
||
// 展业人员
|
||
saleUserId: undefined,
|
||
saleUser: undefined,
|
||
// 备注
|
||
comments: undefined,
|
||
// 排序(数字越小越靠前)
|
||
sortNumber: undefined,
|
||
// 状态, 0正常, 1冻结
|
||
status: undefined,
|
||
// 电子报告是否已完成
|
||
electron: undefined,
|
||
// 纸质报告是否已完成
|
||
paper: undefined,
|
||
// 是否删除, 0否, 1是
|
||
deleted: undefined,
|
||
// 创建人ID
|
||
userId: undefined,
|
||
// 创建人
|
||
realName: undefined,
|
||
// 租户id
|
||
tenantId: undefined,
|
||
// 创建时间
|
||
createTime: undefined,
|
||
// 修改时间
|
||
updateTime: undefined,
|
||
// 知识库id
|
||
kbId: undefined
|
||
});
|
||
|
||
// 请求状态
|
||
const loading = ref(true);
|
||
|
||
const { resetFields } = useForm(form);
|
||
|
||
/* 更新visible */
|
||
const updateVisible = (value: boolean) => {
|
||
emit('update:visible', value);
|
||
};
|
||
|
||
/* 滚动到指定章节 */
|
||
const scrollToSection = (index: number) => {
|
||
currentSection.value = index;
|
||
const element = document.getElementById(`section-${index}`);
|
||
if (element) {
|
||
element.scrollIntoView({
|
||
behavior: 'smooth',
|
||
block: 'start'
|
||
});
|
||
}
|
||
};
|
||
|
||
/* 获取滚动容器 */
|
||
const getScrollContainer = () => {
|
||
return document.querySelector('.ant-modal-body');
|
||
};
|
||
|
||
/* 批量生成全部内容 */
|
||
const handleGenerateAll2 = async () => {
|
||
generatingAll.value = true;
|
||
try {
|
||
// 使用Promise.all进行并行生成
|
||
const promises: Promise<void>[] = [];
|
||
|
||
navigationItems.value.forEach((item, index) => {
|
||
if (item.children) {
|
||
// 有子项的章节,为每个子项生成内容
|
||
item.children.forEach((child, childIndex) => {
|
||
promises.push(generateContent(index, childIndex));
|
||
});
|
||
} else {
|
||
// 没有子项的章节
|
||
promises.push(generateContent(index));
|
||
}
|
||
});
|
||
|
||
await Promise.all(promises);
|
||
message.success('全部内容生成完成');
|
||
} catch (error) {
|
||
console.error('批量生成失败:', error);
|
||
message.error('部分内容生成失败,请检查');
|
||
} finally {
|
||
generatingAll.value = false;
|
||
}
|
||
};
|
||
|
||
/* 批量生成全部内容 */
|
||
const handleGenerateAll = async () => {
|
||
generatingAll.value = true;
|
||
try {
|
||
// 改为顺序生成
|
||
for (const [index, item] of navigationItems.value.entries()) {
|
||
// 跳过未完成的依赖项
|
||
if (index === 4 && !hasContent(3)) {
|
||
message.warning('请先生成第四项「被审计单位基本情况」');
|
||
continue;
|
||
}
|
||
if (index === 5 && !hasContent(4)) {
|
||
message.warning('请先生成第五项「审计内容和重点及审计方法」');
|
||
continue;
|
||
}
|
||
|
||
if (item.children) {
|
||
for (const [childIndex] of item.children.entries()) {
|
||
await generateContent(index, childIndex);
|
||
}
|
||
} else {
|
||
await generateContent(index);
|
||
}
|
||
}
|
||
message.success('全部内容生成完成');
|
||
} catch (error) {
|
||
console.error('批量生成失败:', error);
|
||
message.error('部分内容生成失败,请检查');
|
||
} finally {
|
||
generatingAll.value = false;
|
||
}
|
||
};
|
||
|
||
// 新增内容检查方法
|
||
const hasContent = (index: number) => {
|
||
const section = navigationItems.value[index];
|
||
if (!section) return false;
|
||
|
||
if (section.children) {
|
||
return section.children.some((child) => child.content?.trim());
|
||
}
|
||
return !!section.content?.trim();
|
||
};
|
||
|
||
/* 新增:构建导出数据的公共方法 */
|
||
const buildExportData = () => {
|
||
const exportData: any = {
|
||
from00: form.code,
|
||
from10: navigationItems.value[0].content,
|
||
from20: navigationItems.value[1].content,
|
||
from30: navigationItems.value[2].content
|
||
};
|
||
|
||
// 被审计单位基本情况
|
||
const basicInfoSection = navigationItems.value[3];
|
||
if (basicInfoSection?.children) {
|
||
exportData.from41 = basicInfoSection.children[0].content;
|
||
exportData.from42 = basicInfoSection.children[1].content;
|
||
exportData.from43 = basicInfoSection.children[2].content;
|
||
exportData.from44 = [basicInfoSection.children[3].content];
|
||
exportData.from45 = basicInfoSection.children[4].content;
|
||
}
|
||
|
||
// 审计内容和重点及审计方法
|
||
const auditContentSection = navigationItems.value[4];
|
||
if (auditContentSection?.children) {
|
||
exportData.from51 = auditContentSection.children[0].content;
|
||
exportData.from52 = auditContentSection.children[1].content;
|
||
exportData.from53 = auditContentSection.children[2].content;
|
||
exportData.from54 = auditContentSection.children[3].content;
|
||
exportData.from55 = auditContentSection.children[4].content;
|
||
exportData.from56 = auditContentSection.children[5].content;
|
||
exportData.from57 = auditContentSection.children[6].content;
|
||
exportData.from58 = auditContentSection.children[7].content;
|
||
}
|
||
|
||
// 重要风险的识别及应对
|
||
const riskSection = navigationItems.value[5];
|
||
if (riskSection?.children) {
|
||
exportData.from61 = riskSection.children[0].content;
|
||
exportData.from62 = riskSection.children[1].content;
|
||
}
|
||
|
||
// 其他部分
|
||
exportData.from70 = navigationItems.value[6].content;
|
||
exportData.from80 = navigationItems.value[7].content;
|
||
exportData.from90 = navigationItems.value[8].content;
|
||
|
||
return exportData;
|
||
};
|
||
|
||
/* 修改导出方法使用公共构建方法 */
|
||
const handleExport = async () => {
|
||
try {
|
||
const exportData = buildExportData();
|
||
const blob = await downloadAuditReport(exportData);
|
||
|
||
// 创建下载链接
|
||
const url = window.URL.createObjectURL(new Blob([blob]));
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.setAttribute('download', `审计报告_${form.name}.docx`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
window.URL.revokeObjectURL(url);
|
||
} catch (error) {
|
||
console.error('导出失败:', error);
|
||
message.error('文档导出失败');
|
||
}
|
||
};
|
||
|
||
/* AI生成内容 */
|
||
const generateContent = async (sectionIndex: number, childIndex?: number) => {
|
||
// 特殊处理审计内容3(重大经济决策调查表)
|
||
if (sectionIndex === 2) {
|
||
await generateContent3();
|
||
return;
|
||
}
|
||
|
||
const section = navigationItems.value[sectionIndex];
|
||
|
||
// 处理主项生成(当主项没有formCommit时)
|
||
if (childIndex === undefined && !section.formCommit && section.children) {
|
||
section.generating = true;
|
||
try {
|
||
await Promise.all(
|
||
section.children.map((_, i) => generateContent(sectionIndex, i))
|
||
);
|
||
} finally {
|
||
section.generating = false;
|
||
}
|
||
return;
|
||
}
|
||
|
||
// 处理单个项生成
|
||
let item: any;
|
||
let formCommit: number;
|
||
const isChild = childIndex !== undefined;
|
||
|
||
if (isChild) {
|
||
item = section.children[childIndex];
|
||
formCommit = item.formCommit;
|
||
} else {
|
||
item = section;
|
||
formCommit = item.formCommit;
|
||
}
|
||
|
||
// 新增:构建上下文数据
|
||
const contextData = await buildExportData(); // 复用导出数据的构建逻辑
|
||
|
||
item.generating = true;
|
||
try {
|
||
const result = await generateAuditReport({
|
||
kbId: form.kbId || '',
|
||
kbIds: combinedKbIds.value,
|
||
formCommit: formCommit,
|
||
history: item.content || '',
|
||
suggestion: item.suggestion || '',
|
||
// 新增:传递完整的上下文数据
|
||
...contextData
|
||
});
|
||
item.content = result;
|
||
} finally {
|
||
item.generating = false;
|
||
}
|
||
};
|
||
|
||
// 三重一大生成方法
|
||
const generateContent3 = async () => {
|
||
const section = navigationItems.value[2];
|
||
section.generating = true;
|
||
|
||
// 构建history - 将当前已有的数据作为历史记录
|
||
const history = section.data && section.data.length > 0
|
||
? JSON.stringify(section.data, null, 2)
|
||
: '';
|
||
|
||
// 获取用户输入的建议
|
||
const suggestion = section.suggestion || '';
|
||
|
||
console.log('生成三重一大参数:', {
|
||
kbIds: props.data?.kbId,
|
||
libraryIds: props.data?.libraryIds,
|
||
projectLibrary: props.data?.projectLibrary,
|
||
history: history ? `历史数据(${section.data.length}条)` : '无',
|
||
suggestion: suggestion || '无',
|
||
docList: checkedDirKeys.value, // 修复:使用勾选的目录keys
|
||
fileList: selectedFileKeys.value // 修复:使用勾选的文件keys
|
||
});
|
||
|
||
try {
|
||
// 构建请求参数对象
|
||
const requestData = {
|
||
kbIds: props.data?.kbId || '',
|
||
libraryIds: props.data?.libraryIds || '',
|
||
projectLibrary: props.data?.projectLibrary || '',
|
||
history: history,
|
||
suggestion: suggestion,
|
||
docList: checkedDirKeys.value, // 修复:传入目录id数组
|
||
fileList: selectedFileKeys.value // 修复:传入文件id数组
|
||
};
|
||
|
||
// 调用三重一大生成接口
|
||
const result = await generateTripleOneTable(requestData);
|
||
|
||
console.log('三重一大接口返回结果:', result);
|
||
|
||
if (result.code === 0 && result.data && result.data.success) {
|
||
const tableData = result.data.data.map((item, index) => ({
|
||
key: index,
|
||
category: item.category || '',
|
||
policyContent: item.policyContent || '',
|
||
groupSystem: item.groupSystem || '',
|
||
companyFormulation: item.companyFormulation || '',
|
||
checkEvidence: item.checkEvidence || '',
|
||
testResult: item.testResult || '待检查',
|
||
workPaperIndex: item.workPaperIndex || []
|
||
}));
|
||
|
||
section.data = tableData;
|
||
|
||
// 生成成功后清空建议输入框
|
||
section.suggestion = '';
|
||
|
||
message.success(`成功生成 ${tableData.length} 条三重一大制度对比记录`);
|
||
} else {
|
||
const errorMsg = result.data?.error || result.message || '生成失败';
|
||
throw new Error(errorMsg);
|
||
}
|
||
} catch (error) {
|
||
console.error('生成三重一大制度对比分析表失败:', error);
|
||
message.error('生成失败: ' + (error.message || '未知错误'));
|
||
} finally {
|
||
section.generating = false;
|
||
}
|
||
};
|
||
|
||
/* 监听滚动位置更新当前章节 */
|
||
const handleScroll = () => {
|
||
const sections = navigationItems.value.map((_, index) =>
|
||
document.getElementById(`section-${index}`)
|
||
);
|
||
|
||
const scrollContainer = getScrollContainer();
|
||
if (!scrollContainer) return;
|
||
|
||
const containerTop = scrollContainer.getBoundingClientRect().top;
|
||
|
||
for (let i = sections.length - 1; i >= 0; i--) {
|
||
const section = sections[i];
|
||
if (section) {
|
||
const sectionTop = section.getBoundingClientRect().top - containerTop;
|
||
if (sectionTop <= 100) {
|
||
// 100px 的偏移量
|
||
currentSection.value = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
/* 键盘快捷键处理 */
|
||
const handleKeydown = (event: KeyboardEvent) => {
|
||
// 只在弹窗打开时处理快捷键
|
||
if (!props.visible) return;
|
||
|
||
// Ctrl/Cmd + 数字键 1-9 快速跳转
|
||
if (
|
||
(event.ctrlKey || event.metaKey) &&
|
||
event.key >= '1' &&
|
||
event.key <= '9'
|
||
) {
|
||
event.preventDefault();
|
||
const index = parseInt(event.key) - 1;
|
||
if (index < navigationItems.value.length) {
|
||
scrollToSection(index);
|
||
}
|
||
}
|
||
|
||
// 上下箭头键导航
|
||
if (event.key === 'ArrowUp' && event.ctrlKey) {
|
||
event.preventDefault();
|
||
const prevIndex = Math.max(0, currentSection.value - 1);
|
||
scrollToSection(prevIndex);
|
||
} else if (event.key === 'ArrowDown' && event.ctrlKey) {
|
||
event.preventDefault();
|
||
const nextIndex = Math.min(
|
||
navigationItems.value.length - 1,
|
||
currentSection.value + 1
|
||
);
|
||
scrollToSection(nextIndex);
|
||
}
|
||
};
|
||
|
||
/* 保存编辑 */
|
||
const save = () => {};
|
||
|
||
// 打开文档选择弹窗
|
||
const openDocSelect = (sectionIndex: number) => {
|
||
currentSectionIndex.value = sectionIndex;
|
||
showDocSelect.value = true;
|
||
|
||
// 恢复上次选择的状态
|
||
checkedDirKeys.value = [...lastSelectedDirKeys.value];
|
||
selectedFileKeys.value = [...lastSelectedFileKeys.value];
|
||
selectedDocList.value = lastSelectedDirKeys.value.map(key => key.toString());
|
||
selectedFileList.value = lastSelectedFileKeys.value.map(key => key.toString());
|
||
|
||
// 加载文档目录
|
||
loadAllCloudDocs();
|
||
};
|
||
|
||
// 新增:文档选择弹窗关闭处理
|
||
const handleDocSelectCancel = () => {
|
||
// 保存当前选择状态
|
||
lastSelectedDirKeys.value = [...checkedDirKeys.value];
|
||
lastSelectedFileKeys.value = [...selectedFileKeys.value];
|
||
showDocSelect.value = false;
|
||
};
|
||
|
||
// 加载所有目录
|
||
const loadAllCloudDocs = async () => {
|
||
try {
|
||
const params = {
|
||
companyId: currentCompanyId.value
|
||
};
|
||
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)[], { node }: any) => {
|
||
selectedKeys.value = keys;
|
||
pagination.value.current = 1;
|
||
loadCloudFiles();
|
||
};
|
||
|
||
// 新增:目录勾选处理
|
||
const onDirCheck = (checkedKeys: (string | number)[], { checked }: any) => {
|
||
checkedDirKeys.value = checkedKeys;
|
||
selectedDocList.value = checkedKeys.map(key => key.toString());
|
||
};
|
||
|
||
// 异步加载子节点
|
||
const onLoadData = (node: any) => {
|
||
return new Promise<void>((resolve) => {
|
||
resolve();
|
||
});
|
||
};
|
||
|
||
// 表格分页变化处理
|
||
const handleTableChange = (pag: any) => {
|
||
pagination.value.current = pag.current;
|
||
pagination.value.pageSize = pag.pageSize;
|
||
loadCloudFiles();
|
||
};
|
||
|
||
// 文件选择变化
|
||
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 confirmSelection = () => {
|
||
// 保存当前选择状态
|
||
lastSelectedDirKeys.value = [...checkedDirKeys.value];
|
||
lastSelectedFileKeys.value = [...selectedFileKeys.value];
|
||
|
||
message.success(`已选择 ${checkedDirKeys.value.length} 个目录和 ${selectedFileKeys.value.length} 个文件`);
|
||
showDocSelect.value = false;
|
||
};
|
||
|
||
// 加载文档列表
|
||
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 = 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 file3List = ref([])
|
||
const changeTable3 = (title) => {
|
||
table3Title.value = title;
|
||
if (title === '三重一大') {
|
||
navigationItems.value[2].columns = [
|
||
{
|
||
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',
|
||
customRender: ({ text }) => {
|
||
if (text === '通过') return '<span style="color: #52c41a">通过</span>';
|
||
if (text === '不通过') return '<span style="color: #ff4d4f">不通过</span>';
|
||
return text;
|
||
}
|
||
},
|
||
{
|
||
title: '工作底稿索引',
|
||
dataIndex: 'workPaperIndex',
|
||
key: 'workPaperIndex',
|
||
align: 'center',
|
||
width: 140,
|
||
ellipsis: true,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
align: 'center',
|
||
width: 100,
|
||
}
|
||
];
|
||
// 清空数据,准备生成新的三重一大数据
|
||
navigationItems.value[2].data = [];
|
||
} else {
|
||
navigationItems.value[2].columns = [
|
||
{
|
||
title: '序号',
|
||
dataIndex: 'index',
|
||
key: 'index',
|
||
width: 80,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '重大经济决策事项',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
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',
|
||
customRender: ({ text }) => text || '程序未记录'
|
||
},
|
||
{
|
||
title: '执行情况(是/否)',
|
||
dataIndex: 'executionStatus',
|
||
key: 'executionStatus',
|
||
width: 80,
|
||
align: 'center'
|
||
},
|
||
{
|
||
title: '执行效果(是否实现决策目标)',
|
||
children: [
|
||
{
|
||
title: '好',
|
||
dataIndex: 'goods',
|
||
key: 'goods',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
},
|
||
{
|
||
title: '一般',
|
||
dataIndex: 'normal',
|
||
key: 'normal',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
},
|
||
{
|
||
title: '差',
|
||
dataIndex: 'bad',
|
||
key: 'bad',
|
||
width: 60,
|
||
align: 'center',
|
||
customRender: ({ text }) => (text === '是' ? '✅' : '')
|
||
}
|
||
]
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
align: 'center',
|
||
width: 140
|
||
}
|
||
];
|
||
// 恢复重大经济决策调查表的默认数据
|
||
navigationItems.value[2].data = [
|
||
{
|
||
index: 1,
|
||
name: '千汇公司废水处理站扩容升级改造项目',
|
||
meetingTime: '2024-01-03',
|
||
decisionAmount: '190万元',
|
||
procedure:
|
||
'经专题会议研究并组织外部专家二次评勖审(《2023年第九次总办会会议材料汇总》),形成方案后提交总经理办公会审鈀还户,以记名投票方式全票通过。议案履行了可行性论证、专家咨询、集体讨论和会议决策程序,符台《广西千汇食品有限公司总经理办公会议事规则(试行)》第十条、第十八条关于重大项目投资业务决策程序的要求。',
|
||
executionStatus: '是',
|
||
goods: '是'
|
||
}
|
||
];
|
||
}
|
||
};
|
||
|
||
// 组件挂载时添加滚动监听和键盘监听
|
||
onMounted(() => {
|
||
const scrollContainer = getScrollContainer();
|
||
if (scrollContainer) {
|
||
scrollContainer.addEventListener('scroll', handleScroll);
|
||
}
|
||
document.addEventListener('keydown', handleKeydown);
|
||
});
|
||
|
||
// 组件卸载时移除监听
|
||
onUnmounted(() => {
|
||
const scrollContainer = getScrollContainer();
|
||
if (scrollContainer) {
|
||
scrollContainer.removeEventListener('scroll', handleScroll);
|
||
}
|
||
document.removeEventListener('keydown', handleKeydown);
|
||
});
|
||
|
||
watch(
|
||
() => props.visible,
|
||
async (visible) => {
|
||
if (visible) {
|
||
if (props.data) {
|
||
loading.value = true;
|
||
assignObject(form, props.data);
|
||
|
||
// 获取知识库并拼接 kbIds
|
||
if (props.data.libraryIds?.length > 0) {
|
||
try {
|
||
const result = await getPwlProjectLibraryByIds(
|
||
props.data.libraryIds
|
||
);
|
||
const kbIds = result
|
||
.map((lib) => lib.kbId)
|
||
.filter((kbId) => kbId);
|
||
if (form.kbId) {
|
||
kbIds.unshift(form.kbId);
|
||
}
|
||
combinedKbIds.value = kbIds.join(',');
|
||
} catch (error) {
|
||
console.error('获取知识库失败:', error);
|
||
combinedKbIds.value = form.kbId || '';
|
||
}
|
||
} else {
|
||
combinedKbIds.value = form.kbId || '';
|
||
}
|
||
|
||
// 设置当前公司ID用于文档管理
|
||
currentCompanyId.value = props.data.companyId;
|
||
|
||
// 重置到第一个章节
|
||
currentSection.value = 0;
|
||
}
|
||
} else {
|
||
resetFields();
|
||
combinedKbIds.value = ''; // 关闭时清空
|
||
}
|
||
}
|
||
);
|
||
</script>
|
||
|
||
<script lang="ts">
|
||
import * as MenuIcons from '@/layout/menu-icons';
|
||
|
||
export default {
|
||
name: 'PwlProjectInfo',
|
||
components: MenuIcons
|
||
};
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.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;
|
||
}
|
||
</style> |