|
|
@ -10,13 +10,14 @@ |
|
|
<div class="page-header"> |
|
|
<div class="page-header"> |
|
|
<h2>两书一单电子台账</h2> |
|
|
<h2>两书一单电子台账</h2> |
|
|
<div class="header-buttons"> |
|
|
<div class="header-buttons"> |
|
|
<a-button type="primary" @click="openAddDocumentDialog('risk_tip')"> |
|
|
<a-button v-privilege="'two-books:add'" type="primary" @click="openAddDocumentDialog('risk_tip')"> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
风险防范提示书 |
|
|
风险防范提示书 |
|
|
</a-button> |
|
|
</a-button> |
|
|
<a-button |
|
|
<a-button |
|
|
type="primary" |
|
|
type="primary" |
|
|
style="background: #52c41a; border-color: #52c41a;" |
|
|
style="background: #52c41a; border-color: #52c41a;" |
|
|
|
|
|
v-privilege="'two-books:add'" |
|
|
@click="openAddDocumentDialog('risk_suggestion')" |
|
|
@click="openAddDocumentDialog('risk_suggestion')" |
|
|
> |
|
|
> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
@ -25,6 +26,7 @@ |
|
|
<a-button |
|
|
<a-button |
|
|
type="primary" |
|
|
type="primary" |
|
|
style="background: #faad14; border-color: #faad14;" |
|
|
style="background: #faad14; border-color: #faad14;" |
|
|
|
|
|
v-privilege="'two-books:add'" |
|
|
@click="openAddDocumentDialog('risk_alert')" |
|
|
@click="openAddDocumentDialog('risk_alert')" |
|
|
> |
|
|
> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
<template #icon><PlusOutlined /></template> |
|
|
@ -92,16 +94,16 @@ |
|
|
<div class="stat-label">备案单</div> |
|
|
<div class="stat-label">备案单</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="stat-item stat-total"> |
|
|
<div class="stat-item stat-total"> |
|
|
<div class="stat-num">{{ filteredList.length }}</div> |
|
|
<div class="stat-num">{{ total }}</div> |
|
|
<div class="stat-label">总计</div> |
|
|
<div class="stat-label">总计</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<!-- 卡片列表 --> |
|
|
<!-- 卡片列表 --> |
|
|
<div class="card-grid"> |
|
|
<a-spin :spinning="loading"><div class="card-grid"> |
|
|
<div |
|
|
<div |
|
|
v-for="doc in pagedList" |
|
|
v-for="doc in documentList" |
|
|
:key="doc.docNo" |
|
|
:key="doc.id" |
|
|
class="doc-card" |
|
|
class="doc-card" |
|
|
:class="'card-' + doc.docType" |
|
|
:class="'card-' + doc.docType" |
|
|
@click="viewDocument(doc)" |
|
|
@click="viewDocument(doc)" |
|
|
@ -159,31 +161,34 @@ |
|
|
</a-button> |
|
|
</a-button> |
|
|
</a-tooltip> |
|
|
</a-tooltip> |
|
|
<a-tooltip title="下载文档" placement="top"> |
|
|
<a-tooltip title="下载文档" placement="top"> |
|
|
<a-button type="link" size="small" @click.stop="downloadDocument(doc)"> |
|
|
<a-button type="link" size="small" v-privilege="'two-books:download'" @click.stop="downloadDocument(doc)"> |
|
|
<template #icon><DownloadOutlined /></template> |
|
|
<template #icon><DownloadOutlined /></template> |
|
|
</a-button> |
|
|
</a-button> |
|
|
</a-tooltip> |
|
|
</a-tooltip> |
|
|
<a-tooltip title="编辑" placement="top"> |
|
|
<a-tooltip title="编辑" placement="top"> |
|
|
<a-button type="link" size="small" @click.stop="editDocument(doc)"> |
|
|
<a-button type="link" size="small" v-privilege="'two-books:edit'" @click.stop="editDocument(doc)"> |
|
|
<template #icon><EditOutlined /></template> |
|
|
<template #icon><EditOutlined /></template> |
|
|
</a-button> |
|
|
</a-button> |
|
|
</a-tooltip> |
|
|
</a-tooltip> |
|
|
|
|
|
<a-button type="link" danger size="small" v-privilege="'two-books:delete'" @click.stop="removeDocument(doc)"><DeleteOutlined /></a-button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
</a-spin> |
|
|
<!-- 空状态 --> |
|
|
<!-- 空状态 --> |
|
|
<a-empty v-if="filteredList.length === 0" description="暂无文档数据" /> |
|
|
<a-empty v-if="!loading && total === 0" description="暂无文档数据" /> |
|
|
|
|
|
|
|
|
<!-- 分页 --> |
|
|
<!-- 分页 --> |
|
|
<div class="pagination-wrapper" v-if="filteredList.length > 0"> |
|
|
<div class="pagination-wrapper" v-if="total > 0"> |
|
|
<a-pagination |
|
|
<a-pagination |
|
|
v-model:current="pagination.currentPage" |
|
|
v-model:current="pagination.currentPage" |
|
|
v-model:pageSize="pagination.pageSize" |
|
|
v-model:pageSize="pagination.pageSize" |
|
|
:pageSizeOptions="['8', '12', '24', '48']" |
|
|
:pageSizeOptions="['8', '12', '24', '48']" |
|
|
:total="filteredList.length" |
|
|
:total="total" |
|
|
:showTotal="(total) => `共 ${total} 条`" |
|
|
:showTotal="(total) => `共 ${total} 条`" |
|
|
|
|
|
@change="onPageChange" |
|
|
show-size-changer |
|
|
show-size-changer |
|
|
show-quick-jumper |
|
|
show-quick-jumper |
|
|
/> |
|
|
/> |
|
|
@ -192,49 +197,42 @@ |
|
|
<!-- 文档对话框 --> |
|
|
<!-- 文档对话框 --> |
|
|
<a-modal |
|
|
<a-modal |
|
|
v-model:open="documentDialogVisible" |
|
|
v-model:open="documentDialogVisible" |
|
|
:title="isEditMode ? '编辑文档' : '新增文档'" |
|
|
:title="readOnly ? '查看文书' : (isEditMode ? '编辑文书' : '新增文书')" |
|
|
width="700px" |
|
|
width="700px" |
|
|
:footer="null" |
|
|
:footer="null" |
|
|
:destroyOnClose="true" |
|
|
:destroyOnClose="true" |
|
|
|
|
|
:maskClosable="false" |
|
|
|
|
|
:closable="!saving && !uploading" |
|
|
|
|
|
:keyboard="!saving && !uploading" |
|
|
> |
|
|
> |
|
|
<a-form :model="currentDocument" layout="vertical"> |
|
|
<a-form ref="formRef" :model="currentDocument" layout="vertical" :disabled="readOnly || saving"> |
|
|
<a-form-item label="文档编号" v-if="isEditMode"> |
|
|
<a-form-item label="文档编号" v-if="isEditMode"> |
|
|
<a-input v-model:value="currentDocument.docNo" disabled /> |
|
|
<a-input v-model:value="currentDocument.docNo" disabled /> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="文档类型" required> |
|
|
<a-form-item label="文档类型" name="docType" :rules="required('文档类型')"> |
|
|
<a-select v-model:value="currentDocument.docType" style="width: 100%;"> |
|
|
<a-select v-model:value="currentDocument.docType" style="width: 100%;"> |
|
|
<a-select-option value="risk_tip">风险防范提示书</a-select-option> |
|
|
<a-select-option value="risk_tip">风险防范提示书</a-select-option> |
|
|
<a-select-option value="risk_suggestion">风险防范建议书</a-select-option> |
|
|
<a-select-option value="risk_suggestion">风险防范建议书</a-select-option> |
|
|
<a-select-option value="risk_alert">风险预警备案单</a-select-option> |
|
|
<a-select-option value="risk_alert">风险预警备案单</a-select-option> |
|
|
</a-select> |
|
|
</a-select> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="企业名称" required> |
|
|
<a-form-item label="企业名称" name="enterpriseName" :rules="required('企业名称')"> |
|
|
<a-select |
|
|
<a-input v-model:value="currentDocument.enterpriseName" :maxlength="200" placeholder="请输入企业全称" /> |
|
|
v-model:value="currentDocument.enterpriseName" |
|
|
|
|
|
show-search |
|
|
|
|
|
allow-clear |
|
|
|
|
|
style="width: 100%;" |
|
|
|
|
|
placeholder="请选择或输入企业名称" |
|
|
|
|
|
> |
|
|
|
|
|
<a-select-option v-for="e in enterpriseList" :key="e.id" :value="e.name"> |
|
|
|
|
|
{{ e.name }} |
|
|
|
|
|
</a-select-option> |
|
|
|
|
|
</a-select> |
|
|
|
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="风险类型" required> |
|
|
<a-form-item label="风险类型" name="riskType" :rules="required('风险类型')"> |
|
|
<a-input v-model:value="currentDocument.riskType" placeholder="请输入风险类型" /> |
|
|
<a-input v-model:value="currentDocument.riskType" :maxlength="100" placeholder="请输入风险类型" /> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="状态" required> |
|
|
<a-form-item label="状态" name="status" :rules="required('状态')"> |
|
|
<a-select v-model:value="currentDocument.status" style="width: 100%;"> |
|
|
<a-select v-model:value="currentDocument.status" style="width: 100%;"> |
|
|
<a-select-option value="pending">待处理</a-select-option> |
|
|
<a-select-option value="pending">待处理</a-select-option> |
|
|
<a-select-option value="processing">处理中</a-select-option> |
|
|
<a-select-option value="processing">处理中</a-select-option> |
|
|
<a-select-option value="completed">已完成</a-select-option> |
|
|
<a-select-option value="completed">已完成</a-select-option> |
|
|
</a-select> |
|
|
</a-select> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="生成人" required> |
|
|
<a-form-item label="生成人" name="generator" :rules="required('生成人')"> |
|
|
<a-input v-model:value="currentDocument.generator" placeholder="请输入生成人" /> |
|
|
<a-input v-model:value="currentDocument.generator" :maxlength="100" placeholder="请输入生成人" /> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="生成时间" required> |
|
|
<a-form-item label="生成时间" name="generateTime" :rules="required('生成时间')"> |
|
|
<a-date-picker |
|
|
<a-date-picker |
|
|
v-model:value="currentDocument.generateTime" |
|
|
v-model:value="currentDocument.generateTime" |
|
|
show-time |
|
|
show-time |
|
|
@ -243,459 +241,151 @@ |
|
|
style="width: 100%;" |
|
|
style="width: 100%;" |
|
|
/> |
|
|
/> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="文档标题" required> |
|
|
<a-form-item label="文档标题" name="title" :rules="required('文档标题')"> |
|
|
<a-input v-model:value="currentDocument.title" placeholder="请输入文档标题" /> |
|
|
<a-input v-model:value="currentDocument.title" :maxlength="200" placeholder="请输入文档标题" /> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="文档内容"> |
|
|
<a-form-item label="文档内容" name="content" :rules="required('文档内容')"> |
|
|
<a-textarea |
|
|
<a-textarea |
|
|
v-model:value="currentDocument.content" |
|
|
v-model:value="currentDocument.content" :maxlength="20000" |
|
|
:rows="4" |
|
|
:rows="4" |
|
|
placeholder="请输入文档内容" |
|
|
placeholder="请输入文档内容" |
|
|
/> |
|
|
/> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
<a-form-item label="附件上传"> |
|
|
<a-form-item label="附件上传"> |
|
|
<a-upload |
|
|
<a-upload v-if="!readOnly" |
|
|
:fileList="currentDocument.attachments" |
|
|
:fileList="currentDocument.attachments" :beforeUpload="handleBeforeUpload" |
|
|
:beforeUpload="handleBeforeUpload" |
|
|
:disabled="saving || uploading > 0" :showUploadList="{showRemoveIcon: !saving}" |
|
|
:maxCount="3" |
|
|
accept=".pdf,.doc,.docx,.xls,.xlsx" @remove="handleRemove"> |
|
|
@remove="handleRemove" |
|
|
<a-button :loading="uploading > 0" :disabled="currentDocument.attachments.length >= 3"><UploadOutlined />上传附件</a-button> |
|
|
> |
|
|
|
|
|
<a-button type="primary"> |
|
|
|
|
|
<template #icon><UploadOutlined /></template> |
|
|
|
|
|
选择文件 |
|
|
|
|
|
</a-button> |
|
|
|
|
|
</a-upload> |
|
|
</a-upload> |
|
|
<div class="upload-tip">支持 PDF/Word/Excel,最多3个文件</div> |
|
|
<div v-if="currentDocument.id"> |
|
|
|
|
|
<div v-for="file in currentDocument.attachments" :key="file.fileId"> |
|
|
|
|
|
{{ file.name }} |
|
|
|
|
|
<a-button v-if="file.persisted" :disabled="false" type="link" v-privilege="'two-books:download'" @click="downloadAttachment(file)">下载</a-button> |
|
|
|
|
|
<span v-else>(保存后可下载)</span> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="upload-tip">支持PDF/Word/Excel,每个不超过20MB,最多3个;移除附件在保存文书后生效。</div> |
|
|
</a-form-item> |
|
|
</a-form-item> |
|
|
</a-form> |
|
|
</a-form> |
|
|
|
|
|
|
|
|
<div class="dialog-footer"> |
|
|
<div class="dialog-footer"> |
|
|
<a-button @click="documentDialogVisible = false">取消</a-button> |
|
|
<a-button :disabled="saving || uploading > 0" @click="closeDialog">关闭</a-button> |
|
|
<a-button type="primary" @click="saveDocument">保存</a-button> |
|
|
<a-button v-if="!readOnly" type="primary" :loading="saving" :disabled="uploading > 0" @click="saveDocument">保存</a-button> |
|
|
</div> |
|
|
</div> |
|
|
</a-modal> |
|
|
</a-modal> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<script setup> |
|
|
<script setup> |
|
|
import { ref, reactive, computed } from 'vue'; |
|
|
import { ref, reactive, onMounted } from 'vue'; |
|
|
import { message } from 'ant-design-vue'; |
|
|
import { message, Modal } from 'ant-design-vue'; |
|
|
import { |
|
|
import dayjs from 'dayjs'; |
|
|
PlusOutlined, |
|
|
import { PlusOutlined, FileTextOutlined, BulbOutlined, AlertOutlined, SearchOutlined, ReloadOutlined, |
|
|
FileTextOutlined, |
|
|
BankOutlined, WarningOutlined, EyeOutlined, DownloadOutlined, EditOutlined, UploadOutlined, DeleteOutlined } from '@ant-design/icons-vue'; |
|
|
BulbOutlined, |
|
|
import { twoBooksApi } from '/@/api/business/case-management/two-books-api'; |
|
|
AlertOutlined, |
|
|
import { useUserStore } from '/@/store/modules/system/user'; |
|
|
SearchOutlined, |
|
|
import { smartSentry } from '/@/lib/smart-sentry'; |
|
|
ReloadOutlined, |
|
|
|
|
|
BankOutlined, |
|
|
const searchForm = reactive({ docNo: '', docType: undefined, enterpriseName: '', status: undefined }); |
|
|
WarningOutlined, |
|
|
const pagination = reactive({ currentPage: 1, pageSize: 12 }); |
|
|
EyeOutlined, |
|
|
const documentList = ref([]); |
|
|
DownloadOutlined, |
|
|
const total = ref(0); |
|
|
EditOutlined, |
|
|
const loading = ref(false); |
|
|
UploadOutlined, |
|
|
const saving = ref(false); |
|
|
BellOutlined, |
|
|
const uploading = ref(0); |
|
|
ClockCircleOutlined, |
|
|
const stats = reactive({ tipCount: 0, suggestionCount: 0, alertCount: 0 }); |
|
|
CheckCircleOutlined, |
|
|
|
|
|
FileProtectOutlined, |
|
|
|
|
|
} from '@ant-design/icons-vue'; |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 状态定义 ======================== |
|
|
|
|
|
const searchForm = reactive({ |
|
|
|
|
|
docNo: '', |
|
|
|
|
|
docType: undefined, |
|
|
|
|
|
enterpriseName: '', |
|
|
|
|
|
status: undefined, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const pagination = reactive({ |
|
|
|
|
|
currentPage: 1, |
|
|
|
|
|
pageSize: 12, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const documentDialogVisible = ref(false); |
|
|
const documentDialogVisible = ref(false); |
|
|
|
|
|
const readOnly = ref(false); |
|
|
const isEditMode = ref(false); |
|
|
const isEditMode = ref(false); |
|
|
|
|
|
const formRef = ref(); |
|
|
const currentDocument = reactive({ |
|
|
const newDocument = () => ({ id: null, version: null, docNo: '', docType: 'risk_tip', enterpriseName: '', |
|
|
docNo: '', |
|
|
riskType: '', status: 'pending', generator: useUserStore().actualName || '', generateTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), |
|
|
docType: 'risk_tip', |
|
|
title: '', content: '', attachments: [] }); |
|
|
enterpriseName: undefined, |
|
|
const currentDocument = reactive(newDocument()); |
|
|
riskType: '', |
|
|
const required = (label) => [{ required: true, whitespace: true, message: `请填写${label}` }]; |
|
|
status: 'pending', |
|
|
let querySerial = 0; |
|
|
generator: '', |
|
|
async function queryData() { |
|
|
generateTime: null, |
|
|
const serial = ++querySerial; |
|
|
title: '', |
|
|
loading.value = true; |
|
|
content: '', |
|
|
try { |
|
|
attachments: [], |
|
|
const res = await twoBooksApi.query({ ...searchForm, pageNum: pagination.currentPage, pageSize: pagination.pageSize }); |
|
|
}); |
|
|
if (serial !== querySerial) return; |
|
|
|
|
|
documentList.value = res.data.list; total.value = res.data.total; Object.assign(stats, res.data.stats); |
|
|
// 企业列表 |
|
|
if (!documentList.value.length && total.value > 0 && pagination.currentPage > 1) { |
|
|
const enterpriseList = ref([ |
|
|
pagination.currentPage = Math.ceil(total.value / pagination.pageSize); return queryData(); |
|
|
{ id: 1, name: 'XX科技有限公司' }, |
|
|
|
|
|
{ id: 2, name: 'YY金融服务公司' }, |
|
|
|
|
|
{ id: 3, name: 'ZZ贸易集团' }, |
|
|
|
|
|
{ id: 4, name: 'AA智能制造有限公司' }, |
|
|
|
|
|
{ id: 5, name: 'BB电子商务有限公司' }, |
|
|
|
|
|
{ id: 6, name: 'CC物流运输有限公司' }, |
|
|
|
|
|
{ id: 7, name: 'DD房地产开发有限公司' }, |
|
|
|
|
|
{ id: 8, name: 'EE文化传媒有限公司' }, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
// ======================== Mock 数据(16条) ======================== |
|
|
|
|
|
const documentList = ref([ |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240612001', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: '关于XX科技有限公司网络安全风险防范提示书', |
|
|
|
|
|
enterpriseName: 'XX科技有限公司', |
|
|
|
|
|
riskType: '网络安全', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '张伟', |
|
|
|
|
|
generateTime: '2024-06-12 09:15:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240612002', |
|
|
|
|
|
docType: 'risk_suggestion', |
|
|
|
|
|
title: 'YY金融服务公司资金安全风险防范建议书', |
|
|
|
|
|
enterpriseName: 'YY金融服务公司', |
|
|
|
|
|
riskType: '资金安全', |
|
|
|
|
|
status: 'processing', |
|
|
|
|
|
generator: '李明', |
|
|
|
|
|
generateTime: '2024-06-11 14:30:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240611001', |
|
|
|
|
|
docType: 'risk_alert', |
|
|
|
|
|
title: 'ZZ贸易集团合同欺诈风险预警备案单', |
|
|
|
|
|
enterpriseName: 'ZZ贸易集团', |
|
|
|
|
|
riskType: '合同风险', |
|
|
|
|
|
status: 'pending', |
|
|
|
|
|
generator: '王芳', |
|
|
|
|
|
generateTime: '2024-06-11 10:20:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240611002', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: 'AA智能制造有限公司数据泄露风险提示书', |
|
|
|
|
|
enterpriseName: 'AA智能制造有限公司', |
|
|
|
|
|
riskType: '数据安全', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '赵强', |
|
|
|
|
|
generateTime: '2024-06-10 16:45:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240610001', |
|
|
|
|
|
docType: 'risk_suggestion', |
|
|
|
|
|
title: 'BB电商公司知识产权保护建议书', |
|
|
|
|
|
enterpriseName: 'BB电子商务有限公司', |
|
|
|
|
|
riskType: '知识产权', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '陈刚', |
|
|
|
|
|
generateTime: '2024-06-10 11:08:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240609001', |
|
|
|
|
|
docType: 'risk_alert', |
|
|
|
|
|
title: 'CC物流公司非法经营风险预警备案', |
|
|
|
|
|
enterpriseName: 'CC物流运输有限公司', |
|
|
|
|
|
riskType: '经营合规', |
|
|
|
|
|
status: 'processing', |
|
|
|
|
|
generator: '刘洋', |
|
|
|
|
|
generateTime: '2024-06-09 13:55:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240609002', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: 'DD房地产公司资金链断裂风险提示', |
|
|
|
|
|
enterpriseName: 'DD房地产开发有限公司', |
|
|
|
|
|
riskType: '财务风险', |
|
|
|
|
|
status: 'pending', |
|
|
|
|
|
generator: '孙丽', |
|
|
|
|
|
generateTime: '2024-06-09 09:30:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240608001', |
|
|
|
|
|
docType: 'risk_suggestion', |
|
|
|
|
|
title: 'EE传媒公司舆情风险防控建议书', |
|
|
|
|
|
enterpriseName: 'EE文化传媒有限公司', |
|
|
|
|
|
riskType: '舆情风险', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '周杰', |
|
|
|
|
|
generateTime: '2024-06-08 15:22:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240608002', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: 'XX科技员工信息安全管理提示书', |
|
|
|
|
|
enterpriseName: 'XX科技有限公司', |
|
|
|
|
|
riskType: '人员管理', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '吴敏', |
|
|
|
|
|
generateTime: '2024-06-07 10:18:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240607001', |
|
|
|
|
|
docType: 'risk_alert', |
|
|
|
|
|
title: 'YY金融公司洗钱嫌疑交易预警备案', |
|
|
|
|
|
enterpriseName: 'YY金融服务公司', |
|
|
|
|
|
riskType: '反洗钱', |
|
|
|
|
|
status: 'pending', |
|
|
|
|
|
generator: '郑凯', |
|
|
|
|
|
generateTime: '2024-06-07 14:50:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240606001', |
|
|
|
|
|
docType: 'risk_suggestion', |
|
|
|
|
|
title: 'ZZ贸易供应链中断风险建议', |
|
|
|
|
|
enterpriseName: 'ZZ贸易集团', |
|
|
|
|
|
riskType: '供应链', |
|
|
|
|
|
status: 'processing', |
|
|
|
|
|
generator: '钱进', |
|
|
|
|
|
generateTime: '2024-06-06 16:33:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240606002', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: 'AA制造环保合规风险提示书', |
|
|
|
|
|
enterpriseName: 'AA智能制造有限公司', |
|
|
|
|
|
riskType: '环保合规', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '冯雪', |
|
|
|
|
|
generateTime: '2024-06-05 08:45:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240605001', |
|
|
|
|
|
docType: 'risk_alert', |
|
|
|
|
|
title: 'BB电商虚假宣传风险预警备案', |
|
|
|
|
|
enterpriseName: 'BB电子商务有限公司', |
|
|
|
|
|
riskType: '广告合规', |
|
|
|
|
|
status: 'handled', |
|
|
|
|
|
generator: '蒋涛', |
|
|
|
|
|
generateTime: '2024-06-05 11:28:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240604001', |
|
|
|
|
|
docType: 'risk_tip', |
|
|
|
|
|
title: 'CC物流车辆安全事故防范提示', |
|
|
|
|
|
enterpriseName: 'CC物流运输有限公司', |
|
|
|
|
|
riskType: '安全生产', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '沈琳', |
|
|
|
|
|
generateTime: '2024-06-04 17:10:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240603001', |
|
|
|
|
|
docType: 'risk_suggestion', |
|
|
|
|
|
title: 'DD房产预售资金监管建议书', |
|
|
|
|
|
enterpriseName: 'DD房地产开发有限公司', |
|
|
|
|
|
riskType: '资金监管', |
|
|
|
|
|
status: 'completed', |
|
|
|
|
|
generator: '韩冰', |
|
|
|
|
|
generateTime: '2024-06-03 09:55:00', |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
docNo: 'DT20240602001', |
|
|
|
|
|
docType: 'risk_alert', |
|
|
|
|
|
title: 'EE传媒版权侵权风险预警备案', |
|
|
|
|
|
enterpriseName: 'EE文化传媒有限公司', |
|
|
|
|
|
riskType: '版权风险', |
|
|
|
|
|
status: 'pending', |
|
|
|
|
|
generator: '杨帆', |
|
|
|
|
|
generateTime: '2024-06-02 13:42:00', |
|
|
|
|
|
}, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 计算属性 ======================== |
|
|
|
|
|
// 统计数据 |
|
|
|
|
|
const stats = computed(() => ({ |
|
|
|
|
|
tipCount: documentList.value.filter((d) => d.docType === 'risk_tip').length, |
|
|
|
|
|
suggestionCount: documentList.value.filter((d) => d.docType === 'risk_suggestion').length, |
|
|
|
|
|
alertCount: documentList.value.filter((d) => d.docType === 'risk_alert').length, |
|
|
|
|
|
})); |
|
|
|
|
|
|
|
|
|
|
|
// 过滤后的列表 |
|
|
|
|
|
const filteredList = computed(() => { |
|
|
|
|
|
let list = documentList.value; |
|
|
|
|
|
if (searchForm.docNo) { |
|
|
|
|
|
list = list.filter((d) => d.docNo.toLowerCase().includes(searchForm.docNo.toLowerCase())); |
|
|
|
|
|
} |
|
|
|
|
|
if (searchForm.docType) { |
|
|
|
|
|
list = list.filter((d) => d.docType === searchForm.docType); |
|
|
|
|
|
} |
|
|
|
|
|
if (searchForm.enterpriseName) { |
|
|
|
|
|
list = list.filter((d) => d.enterpriseName.includes(searchForm.enterpriseName)); |
|
|
|
|
|
} |
|
|
|
|
|
if (searchForm.status) { |
|
|
|
|
|
list = list.filter((d) => d.status === searchForm.status); |
|
|
|
|
|
} |
|
|
|
|
|
return list; |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 分页后的列表 |
|
|
|
|
|
const pagedList = computed(() => { |
|
|
|
|
|
const start = (pagination.currentPage - 1) * pagination.pageSize; |
|
|
|
|
|
return filteredList.value.slice(start, start + pagination.pageSize); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 工具函数 ======================== |
|
|
|
|
|
const docIconMap = { |
|
|
|
|
|
risk_tip: FileTextOutlined, |
|
|
|
|
|
risk_suggestion: BulbOutlined, |
|
|
|
|
|
risk_alert: AlertOutlined, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const getDocIcon = (type) => { |
|
|
|
|
|
return docIconMap[type] || FileTextOutlined; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const getDocTypeText = (type) => { |
|
|
|
|
|
const map = { risk_tip: '提示书', risk_suggestion: '建议书', risk_alert: '备案单' }; |
|
|
|
|
|
return map[type] || type; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const getStatusText = (status) => { |
|
|
|
|
|
const map = { |
|
|
|
|
|
pending: '待处理', |
|
|
|
|
|
processing: '处理中', |
|
|
|
|
|
completed: '已完成', |
|
|
|
|
|
handled: '已处理', |
|
|
|
|
|
}; |
|
|
|
|
|
return map[status] || status; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const getStatusColor = (status) => { |
|
|
|
|
|
const map = { |
|
|
|
|
|
pending: 'orange', |
|
|
|
|
|
processing: 'blue', |
|
|
|
|
|
completed: 'green', |
|
|
|
|
|
handled: 'default', |
|
|
|
|
|
}; |
|
|
|
|
|
return map[status] || 'default'; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const formatTime = (time) => { |
|
|
|
|
|
if (!time) return ''; |
|
|
|
|
|
if (typeof time === 'string') return time.replace('T', ' ').substring(0, 16); |
|
|
|
|
|
// dayjs 对象 |
|
|
|
|
|
if (time && typeof time.format === 'function') { |
|
|
|
|
|
return time.format('YYYY-MM-DD HH:mm'); |
|
|
|
|
|
} |
|
|
|
|
|
return ''; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 搜索/重置 ======================== |
|
|
|
|
|
const handleSearch = () => { |
|
|
|
|
|
pagination.currentPage = 1; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleReset = () => { |
|
|
|
|
|
searchForm.docNo = ''; |
|
|
|
|
|
searchForm.docType = undefined; |
|
|
|
|
|
searchForm.enterpriseName = ''; |
|
|
|
|
|
searchForm.status = undefined; |
|
|
|
|
|
pagination.currentPage = 1; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 卡片操作 ======================== |
|
|
|
|
|
const viewDocument = (row) => { |
|
|
|
|
|
isEditMode.value = true; |
|
|
|
|
|
Object.assign(currentDocument, { |
|
|
|
|
|
docNo: row.docNo, |
|
|
|
|
|
docType: row.docType, |
|
|
|
|
|
enterpriseName: row.enterpriseName, |
|
|
|
|
|
riskType: row.riskType, |
|
|
|
|
|
status: row.status, |
|
|
|
|
|
generator: row.generator, |
|
|
|
|
|
generateTime: row.generateTime, |
|
|
|
|
|
title: row.title, |
|
|
|
|
|
content: row.content || '', |
|
|
|
|
|
attachments: [], |
|
|
|
|
|
}); |
|
|
|
|
|
documentDialogVisible.value = true; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const editDocument = (row) => { |
|
|
|
|
|
isEditMode.value = true; |
|
|
|
|
|
Object.assign(currentDocument, { |
|
|
|
|
|
docNo: row.docNo, |
|
|
|
|
|
docType: row.docType, |
|
|
|
|
|
enterpriseName: row.enterpriseName, |
|
|
|
|
|
riskType: row.riskType, |
|
|
|
|
|
status: row.status, |
|
|
|
|
|
generator: row.generator, |
|
|
|
|
|
generateTime: row.generateTime, |
|
|
|
|
|
title: row.title, |
|
|
|
|
|
content: row.content || '', |
|
|
|
|
|
attachments: [], |
|
|
|
|
|
}); |
|
|
|
|
|
documentDialogVisible.value = true; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const downloadDocument = (row) => { |
|
|
|
|
|
message.success(`正在下载: ${row.docNo}`); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const openAddDocumentDialog = (docType) => { |
|
|
|
|
|
isEditMode.value = false; |
|
|
|
|
|
const typeTextMap = { |
|
|
|
|
|
risk_tip: '风险防范提示书', |
|
|
|
|
|
risk_suggestion: '风险防范建议书', |
|
|
|
|
|
risk_alert: '风险预警备案单', |
|
|
|
|
|
}; |
|
|
|
|
|
const now = new Date(); |
|
|
|
|
|
const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`; |
|
|
|
|
|
Object.assign(currentDocument, { |
|
|
|
|
|
docNo: '', |
|
|
|
|
|
docType, |
|
|
|
|
|
enterpriseName: undefined, |
|
|
|
|
|
riskType: '', |
|
|
|
|
|
status: 'pending', |
|
|
|
|
|
generator: '', |
|
|
|
|
|
generateTime: dateStr, |
|
|
|
|
|
title: `${typeTextMap[docType]} - ${now.toLocaleDateString('zh-CN')}`, |
|
|
|
|
|
content: '', |
|
|
|
|
|
attachments: [], |
|
|
|
|
|
}); |
|
|
|
|
|
documentDialogVisible.value = true; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 文件上传 ======================== |
|
|
|
|
|
const handleBeforeUpload = (file) => { |
|
|
|
|
|
// 不实际上传,只加入文件列表 |
|
|
|
|
|
currentDocument.attachments = [...currentDocument.attachments, file]; |
|
|
|
|
|
return false; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const handleRemove = (file) => { |
|
|
|
|
|
currentDocument.attachments = currentDocument.attachments.filter( |
|
|
|
|
|
(item) => item.uid !== file.uid |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// ======================== 保存 ======================== |
|
|
|
|
|
const saveDocument = () => { |
|
|
|
|
|
if ( |
|
|
|
|
|
!currentDocument.docType || |
|
|
|
|
|
!currentDocument.enterpriseName || |
|
|
|
|
|
!currentDocument.riskType || |
|
|
|
|
|
!currentDocument.generator |
|
|
|
|
|
) { |
|
|
|
|
|
message.warning('请填写所有必填项'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (isEditMode.value) { |
|
|
|
|
|
const idx = documentList.value.findIndex((d) => d.docNo === currentDocument.docNo); |
|
|
|
|
|
if (idx !== -1) { |
|
|
|
|
|
documentList.value[idx] = { ...currentDocument }; |
|
|
|
|
|
} |
|
|
} |
|
|
message.success('编辑成功'); |
|
|
} catch (e) { smartSentry.captureError(e); } |
|
|
} else { |
|
|
finally { if (serial === querySerial) loading.value = false; } |
|
|
const now = new Date(); |
|
|
} |
|
|
const no = `DT${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}${String(documentList.value.length + 1).padStart(4, '0')}`; |
|
|
function handleSearch() { pagination.currentPage = 1; queryData(); } |
|
|
documentList.value.unshift({ ...currentDocument, docNo: no }); |
|
|
function handleReset() { Object.assign(searchForm, { docNo:'', docType:undefined, enterpriseName:'', status:undefined }); handleSearch(); } |
|
|
message.success('新增成功'); |
|
|
function onPageChange(page, size) { pagination.currentPage = page; pagination.pageSize = size; queryData(); } |
|
|
|
|
|
const getDocIcon = (type) => ({risk_tip:FileTextOutlined,risk_suggestion:BulbOutlined,risk_alert:AlertOutlined}[type] || FileTextOutlined); |
|
|
|
|
|
const getDocTypeText = (type) => ({risk_tip:'风险防范提示书',risk_suggestion:'风险防范建议书',risk_alert:'风险预警备案单'}[type] || type); |
|
|
|
|
|
const getStatusText = (status) => ({pending:'待处理',processing:'处理中',completed:'已完成'}[status] || status); |
|
|
|
|
|
const getStatusColor = (status) => ({pending:'orange',processing:'blue',completed:'green'}[status] || 'default'); |
|
|
|
|
|
const formatTime = (time) => time ? String(time).replace('T',' ').slice(0,16) : ''; |
|
|
|
|
|
async function openExisting(row, view) { |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await twoBooksApi.detail(row.id); |
|
|
|
|
|
Object.assign(currentDocument, newDocument(), res.data.document, { |
|
|
|
|
|
generateTime: dayjs(res.data.document.generateTime).format('YYYY-MM-DD HH:mm:ss'), |
|
|
|
|
|
attachments: res.data.attachments.map(f => ({...f,uid:String(f.fileId),status:'done',persisted:true})), |
|
|
|
|
|
}); |
|
|
|
|
|
readOnly.value = view; isEditMode.value = true; documentDialogVisible.value = true; |
|
|
|
|
|
} catch (e) { smartSentry.captureError(e); } |
|
|
|
|
|
} |
|
|
|
|
|
const viewDocument = row => openExisting(row,true); |
|
|
|
|
|
const editDocument = row => openExisting(row,false); |
|
|
|
|
|
function openAddDocumentDialog(docType) { |
|
|
|
|
|
Object.assign(currentDocument,newDocument(),{docType,title:getDocTypeText(docType)}); |
|
|
|
|
|
readOnly.value = false; isEditMode.value = false; documentDialogVisible.value = true; |
|
|
|
|
|
} |
|
|
|
|
|
function closeDialog() { if(!saving.value && !uploading.value) documentDialogVisible.value = false; } |
|
|
|
|
|
async function handleBeforeUpload(file) { |
|
|
|
|
|
if(currentDocument.attachments.length + uploading.value >= 3) { message.warning('最多3个附件'); return false; } |
|
|
|
|
|
if(!/\.(pdf|docx?|xlsx?)$/i.test(file.name) || file.size > 20*1024*1024 || !file.size) { |
|
|
|
|
|
message.warning('支持PDF/Word/Excel,单个文件须为1字节至20MB'); return false; |
|
|
} |
|
|
} |
|
|
documentDialogVisible.value = false; |
|
|
uploading.value++; |
|
|
}; |
|
|
try { |
|
|
|
|
|
const body = new FormData(); body.append('file',file); |
|
|
|
|
|
const res = await twoBooksApi.upload(body); |
|
|
|
|
|
currentDocument.attachments.push({fileId:res.data.fileId,uid:String(res.data.fileId),name:file.name,size:file.size,status:'done'}); |
|
|
|
|
|
} catch(e) { smartSentry.captureError(e); } |
|
|
|
|
|
finally { uploading.value--; } |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
function handleRemove(file) { |
|
|
|
|
|
if (readOnly.value || saving.value || uploading.value) return false; |
|
|
|
|
|
currentDocument.attachments = currentDocument.attachments.filter(f=>f.fileId!==file.fileId); |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
function downloadAttachment(file) { |
|
|
|
|
|
if(currentDocument.id) twoBooksApi.downloadAttachment(currentDocument.id,file.fileId); |
|
|
|
|
|
} |
|
|
|
|
|
const downloadDocument = row => twoBooksApi.download(row.id); |
|
|
|
|
|
async function saveDocument() { |
|
|
|
|
|
if(saving.value || uploading.value) return; |
|
|
|
|
|
try { await formRef.value.validate(); } catch { return; } |
|
|
|
|
|
saving.value = true; |
|
|
|
|
|
try { |
|
|
|
|
|
const {docType,enterpriseName,riskType,status,generator,generateTime,title,content,version} = currentDocument; |
|
|
|
|
|
const body = {docType,enterpriseName,riskType,status,generator,generateTime,title,content,version, |
|
|
|
|
|
attachmentIds:currentDocument.attachments.map(f=>f.fileId)}; |
|
|
|
|
|
if(currentDocument.id) await twoBooksApi.update(currentDocument.id,body); |
|
|
|
|
|
else await twoBooksApi.create(body); |
|
|
|
|
|
message.success('文书已保存'); documentDialogVisible.value = false; await queryData(); |
|
|
|
|
|
} catch(e) { smartSentry.captureError(e); } |
|
|
|
|
|
finally { saving.value = false; } |
|
|
|
|
|
} |
|
|
|
|
|
function removeDocument(row) { |
|
|
|
|
|
Modal.confirm({ title:'删除文书',content:`确认删除“${row.title}”?删除后不再显示在台账中。`, |
|
|
|
|
|
async onOk() { await twoBooksApi.remove(row.id,row.version); message.success('已删除'); await queryData(); }, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
onMounted(queryData); |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<style lang="less" scoped> |
|
|
<style lang="less" scoped> |
|
|
|