32 changed files with 5464 additions and 1278 deletions
@ -0,0 +1,98 @@ |
|||||
|
import { defHttp } from '@/utils/http/axios'; |
||||
|
import { ID, IDS, commonExport } from '@/api/base'; |
||||
|
import { ContractualCaseFilesVO, ContractualCaseFilesForm, ContractualCaseFilesQuery } from './model'; |
||||
|
|
||||
|
/** |
||||
|
* 查询合同案例文件列表 |
||||
|
* @param params |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesList(params?: ContractualCaseFilesQuery) { |
||||
|
return defHttp.get<ContractualCaseFilesVO[]>({ url: '/productManagement/ContractualCaseFiles/list', params }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 导出合同案例文件列表 |
||||
|
* @param params |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesExport(params?: ContractualCaseFilesQuery) { |
||||
|
return commonExport('/productManagement/ContractualCaseFiles/export', params ?? {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询合同案例文件详细 |
||||
|
* @param id id |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesInfo(id: ID) { |
||||
|
return defHttp.get<ContractualCaseFilesVO>({ url: '/productManagement/ContractualCaseFiles/' + id }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 新增合同案例文件 |
||||
|
* @param data |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesAdd(data: ContractualCaseFilesForm) { |
||||
|
return defHttp.postWithMsg<void>({ url: '/productManagement/ContractualCaseFiles', data }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同案例文件 |
||||
|
* @param data |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesUpdate(data: ContractualCaseFilesForm) { |
||||
|
return defHttp.putWithMsg<void>({ url: '/productManagement/ContractualCaseFiles', data }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同案例文件状态 |
||||
|
* @param id 主键 |
||||
|
* @param status 状态 |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesUpdateStatus(id: ID, status: string) { |
||||
|
return defHttp.putWithMsg<void>({ |
||||
|
url: '/productManagement/ContractualCaseFiles/updateStatus', |
||||
|
data: { id, isEffective: status } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除合同案例文件 |
||||
|
* @param id id |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesRemove(id: ID | IDS) { |
||||
|
return defHttp.deleteWithMsg<void>({ url: '/productManagement/ContractualCaseFiles/' + id },); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取案例条款列表 |
||||
|
* @param id 案例文件ID |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualCaseFilesArticles(id: ID) { |
||||
|
return defHttp.get<any[]>({ url: '/productManagement/ContractualCaseFiles/articles/' + id }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成案例PDF文档(查看详情) |
||||
|
* @param id 案例文件ID |
||||
|
* @returns PDF响应数据 |
||||
|
*/ |
||||
|
export function ContractualCaseFilesViewPdf(id: ID) { |
||||
|
return defHttp.get( |
||||
|
{ |
||||
|
url: '/productManagement/ContractualCaseFiles/view/' + id, |
||||
|
responseType: 'blob', |
||||
|
timeout: 1000 * 60 * 10, |
||||
|
headers: { |
||||
|
Accept: 'application/pdf', |
||||
|
} |
||||
|
}, |
||||
|
{ isReturnNativeResponse: true } |
||||
|
); |
||||
|
} |
@ -0,0 +1,76 @@ |
|||||
|
import type { BaseEntity, PageQuery } from '@/api/base'; |
||||
|
|
||||
|
export interface ContractualCaseFilesVO extends BaseEntity { |
||||
|
/** 主键ID */ |
||||
|
id?: string | number; |
||||
|
|
||||
|
/** 案例名称 */ |
||||
|
caseName?: string; |
||||
|
|
||||
|
/** 案例描述 */ |
||||
|
caseDescription?: string; |
||||
|
|
||||
|
/** 案例类型 */ |
||||
|
caseType?: string; |
||||
|
|
||||
|
/** 发布日期 */ |
||||
|
publishDate?: string; |
||||
|
|
||||
|
/** 是否有效(Y:有效 N:无效) */ |
||||
|
isEffective?: string; |
||||
|
|
||||
|
/** 处理状态 */ |
||||
|
progressStatus?: string; |
||||
|
|
||||
|
/** OSS文件ID */ |
||||
|
ossId?: string | number; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualCaseFilesForm extends BaseEntity { |
||||
|
/** 主键ID */ |
||||
|
id?: string | number; |
||||
|
|
||||
|
/** 案例名称 */ |
||||
|
caseName?: string; |
||||
|
|
||||
|
/** 案例描述 */ |
||||
|
caseDescription?: string; |
||||
|
|
||||
|
/** 案例类型 */ |
||||
|
caseType?: string; |
||||
|
|
||||
|
/** 发布日期 */ |
||||
|
publishDate?: string; |
||||
|
|
||||
|
/** 是否有效(Y:有效 N:无效) */ |
||||
|
isEffective?: string; |
||||
|
|
||||
|
/** 处理状态 */ |
||||
|
progressStatus?: string; |
||||
|
|
||||
|
/** OSS文件ID */ |
||||
|
ossId?: string | number; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualCaseFilesQuery extends PageQuery { |
||||
|
/** 案例名称 */ |
||||
|
caseName?: string; |
||||
|
|
||||
|
/** 案例类型 */ |
||||
|
caseType?: string; |
||||
|
|
||||
|
/** 发布日期 */ |
||||
|
publishDate?: string; |
||||
|
|
||||
|
/** 日期范围查询-开始日期 */ |
||||
|
publishDateStart?: string; |
||||
|
|
||||
|
/** 日期范围查询-结束日期 */ |
||||
|
publishDateEnd?: string; |
||||
|
|
||||
|
/** 是否有效(Y:有效 N:无效) */ |
||||
|
isEffective?: string; |
||||
|
|
||||
|
/** 处理状态 */ |
||||
|
progressStatus?: string; |
||||
|
} |
@ -0,0 +1,98 @@ |
|||||
|
import { defHttp } from '@/utils/http/axios'; |
||||
|
import { ID, IDS, commonExport } from '@/api/base'; |
||||
|
import { ContractualRegulationNamesVO, ContractualRegulationNamesForm, ContractualRegulationNamesQuery } from './model'; |
||||
|
|
||||
|
/** |
||||
|
* 查询合同法规名称列表 |
||||
|
* @param params |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesList(params?: ContractualRegulationNamesQuery) { |
||||
|
return defHttp.get<ContractualRegulationNamesVO[]>({ url: '/productManagement/ContractualRegulationNames/list', params }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 导出合同法规名称列表 |
||||
|
* @param params |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesExport(params?: ContractualRegulationNamesQuery) { |
||||
|
return commonExport('/productManagement/ContractualRegulationNames/export', params ?? {}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 查询合同法规名称详细 |
||||
|
* @param id id |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesInfo(id: ID) { |
||||
|
return defHttp.get<ContractualRegulationNamesVO>({ url: '/productManagement/ContractualRegulationNames/' + id }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 新增合同法规名称 |
||||
|
* @param data |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesAdd(data: ContractualRegulationNamesForm) { |
||||
|
return defHttp.postWithMsg<void>({ url: '/productManagement/ContractualRegulationNames', data }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同法规名称 |
||||
|
* @param data |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesUpdate(data: ContractualRegulationNamesForm) { |
||||
|
return defHttp.putWithMsg<void>({ url: '/productManagement/ContractualRegulationNames', data }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同法规名称状态 |
||||
|
* @param id 主键 |
||||
|
* @param status 状态 |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesUpdateStatus(id: ID, status: string) { |
||||
|
return defHttp.putWithMsg<void>({ |
||||
|
url: '/productManagement/ContractualRegulationNames/updateStatus', |
||||
|
data: { id, isEffective: status } |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除合同法规名称 |
||||
|
* @param id id |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesRemove(id: ID | IDS) { |
||||
|
return defHttp.deleteWithMsg<void>({ url: '/productManagement/ContractualRegulationNames/' + id },); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取法规条款列表 |
||||
|
* @param id 法规ID |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesArticles(id: ID) { |
||||
|
return defHttp.get<any[]>({ url: '/productManagement/ContractualRegulationNames/articles/' + id }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成法规PDF文档(查看详情) |
||||
|
* @param id 法规ID |
||||
|
* @returns PDF响应数据 |
||||
|
*/ |
||||
|
export function ContractualRegulationNamesViewPdf(id: ID) { |
||||
|
return defHttp.get( |
||||
|
{ |
||||
|
url: '/productManagement/ContractualRegulationNames/view/' + id, |
||||
|
responseType: 'blob', |
||||
|
timeout: 1000 * 60 * 10, |
||||
|
headers: { |
||||
|
Accept: 'application/pdf', |
||||
|
} |
||||
|
}, |
||||
|
{ isReturnNativeResponse: true } |
||||
|
); |
||||
|
} |
@ -0,0 +1,98 @@ |
|||||
|
import { BaseEntity, PageQuery } from '@/api/base'; |
||||
|
|
||||
|
export interface ContractualRegulationNamesVO { |
||||
|
/** |
||||
|
* 主键ID |
||||
|
*/ |
||||
|
id: string | number; |
||||
|
|
||||
|
/** |
||||
|
* 法规名称 |
||||
|
*/ |
||||
|
regulationName: string; |
||||
|
|
||||
|
/** |
||||
|
* 法规描述 |
||||
|
*/ |
||||
|
regulationDescription: string; |
||||
|
|
||||
|
/** |
||||
|
* 发布日期 |
||||
|
*/ |
||||
|
publishDate: string; |
||||
|
|
||||
|
/** |
||||
|
* 是否有效(Y:有效 N:无效) |
||||
|
*/ |
||||
|
isEffective: string; |
||||
|
|
||||
|
/** |
||||
|
* 当前状态 |
||||
|
*/ |
||||
|
progressStatus?: string; |
||||
|
|
||||
|
/** |
||||
|
* OSS文件ID |
||||
|
*/ |
||||
|
ossId?: string | number; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualRegulationNamesForm extends BaseEntity { |
||||
|
/** |
||||
|
* 主键ID |
||||
|
*/ |
||||
|
id?: string | number; |
||||
|
|
||||
|
/** |
||||
|
* 法规名称 |
||||
|
*/ |
||||
|
regulationName?: string; |
||||
|
|
||||
|
/** |
||||
|
* 法规描述 |
||||
|
*/ |
||||
|
regulationDescription?: string; |
||||
|
|
||||
|
/** |
||||
|
* 发布日期 |
||||
|
*/ |
||||
|
publishDate?: string; |
||||
|
|
||||
|
/** |
||||
|
* 是否有效(Y:有效 N:无效) |
||||
|
*/ |
||||
|
isEffective?: string; |
||||
|
|
||||
|
/** |
||||
|
* 当前状态 |
||||
|
*/ |
||||
|
progressStatus?: string; |
||||
|
|
||||
|
/** |
||||
|
* OSS文件ID |
||||
|
*/ |
||||
|
ossId?: string | number; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualRegulationNamesQuery extends PageQuery { |
||||
|
|
||||
|
/** |
||||
|
* 法规名称 |
||||
|
*/ |
||||
|
regulationName?: string; |
||||
|
|
||||
|
/** |
||||
|
* 发布日期 |
||||
|
*/ |
||||
|
publishDate?: string; |
||||
|
|
||||
|
/** |
||||
|
* 是否有效(Y:有效 N:无效) |
||||
|
*/ |
||||
|
isEffective?: string; |
||||
|
|
||||
|
/** |
||||
|
* 日期范围参数 |
||||
|
*/ |
||||
|
params?: any; |
||||
|
} |
@ -0,0 +1,133 @@ |
|||||
|
import { defHttp } from '@/utils/http/axios'; |
||||
|
import { ID, IDS } from '@/api/base'; |
||||
|
import { ContractualTaskResultDetailVO } from './model'; |
||||
|
import { message } from 'ant-design-vue'; |
||||
|
|
||||
|
/** |
||||
|
* 通用文件下载函数 |
||||
|
* @param url 下载地址 |
||||
|
* @param onError 错误处理回调 |
||||
|
* @returns Promise<boolean> 下载是否成功 |
||||
|
*/ |
||||
|
export async function useDownload( |
||||
|
url: string, |
||||
|
onError?: (error: any) => void |
||||
|
): Promise<boolean> { |
||||
|
try { |
||||
|
const response = await defHttp.get( |
||||
|
{ |
||||
|
url, |
||||
|
responseType: 'blob', |
||||
|
timeout: 60000, // 设置较长的超时时间
|
||||
|
}, |
||||
|
{ |
||||
|
isReturnNativeResponse: true, |
||||
|
// 自定义错误处理
|
||||
|
errorMessageMode: 'none', |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
// 检查响应类型
|
||||
|
const contentType = response.headers['content-type']; |
||||
|
if (contentType && contentType.includes('application/json')) { |
||||
|
// 如果返回的是JSON(通常是错误信息),转换并抛出
|
||||
|
const reader = new FileReader(); |
||||
|
reader.onload = () => { |
||||
|
const error = JSON.parse(reader.result as string); |
||||
|
message.error(error.message || '下载失败'); |
||||
|
onError?.(error); |
||||
|
}; |
||||
|
reader.readAsText(response.data); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// 获取文件名
|
||||
|
const contentDisposition = response.headers['content-disposition']; |
||||
|
let fileName = ''; |
||||
|
|
||||
|
if (contentDisposition) { |
||||
|
const matches = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/); |
||||
|
if (matches && matches[1]) { |
||||
|
fileName = decodeURIComponent(matches[1].replace(/['"]/g, '')); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 创建Blob对象
|
||||
|
const blob = new Blob([response.data], { |
||||
|
type: contentType || 'application/octet-stream' |
||||
|
}); |
||||
|
|
||||
|
if ((window.navigator as any).msSaveOrOpenBlob) { |
||||
|
// 针对IE的处理
|
||||
|
(window.navigator as any).msSaveOrOpenBlob(blob, fileName); |
||||
|
} else { |
||||
|
// 现代浏览器的处理
|
||||
|
const url = window.URL.createObjectURL(blob); |
||||
|
const link = document.createElement('a'); |
||||
|
link.href = url; |
||||
|
link.download = fileName; |
||||
|
link.style.display = 'none'; |
||||
|
document.body.appendChild(link); |
||||
|
link.click(); |
||||
|
document.body.removeChild(link); |
||||
|
window.URL.revokeObjectURL(url); |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} catch (error: any) { |
||||
|
console.error('下载失败:', error); |
||||
|
message.error(error.message || '下载失败,请稍后重试'); |
||||
|
onError?.(error); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据任务id查询详细分类的合同任务结果 |
||||
|
* @param taskId 任务ID |
||||
|
* @returns 详细的合同任务结果列表 |
||||
|
*/ |
||||
|
export function getDetailResultsByTaskId(taskId: ID) { |
||||
|
return defHttp.get<ContractualTaskResultDetailVO[]>({ url: '/productManagement/ContractualTaskResults/taskDetail/' + taskId }); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载合同任务结果 |
||||
|
* @param id id |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function ContractualTaskResultDownload(id: ID | IDS) { |
||||
|
return useDownload(`/productManagement/ContractualTaskResults/downloadResult/${id}`); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取合同任务PDF文件流 |
||||
|
* @param taskId 任务ID |
||||
|
* @returns Promise<Blob> |
||||
|
*/ |
||||
|
export function getPdfStream(taskId: ID): Promise<Blob> { |
||||
|
return defHttp.get( |
||||
|
{ |
||||
|
url: `/productManagement/ContractualTaskResults/getPdfStream/${taskId}`, |
||||
|
responseType: 'blob', |
||||
|
timeout:600000 |
||||
|
}, |
||||
|
{ |
||||
|
isReturnNativeResponse: true, |
||||
|
errorMessageMode: 'none', |
||||
|
} |
||||
|
).then(response => response.data); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新合同任务结果项的状态(已读/采纳) |
||||
|
* @param id 结果项ID |
||||
|
* @param field 字段名(isRead/isAdopted) |
||||
|
* @param value 值(0/1) |
||||
|
* @returns |
||||
|
*/ |
||||
|
export function updateResultItemStatus(id: ID, field: 'isRead' | 'isAdopted', value: '0' | '1') { |
||||
|
return defHttp.putWithMsg<void>({ |
||||
|
url: `/productManagement/ContractualTaskResults/updateResultItemStatus/${id}/${field}/${value}` |
||||
|
}); |
||||
|
} |
@ -0,0 +1,99 @@ |
|||||
|
import { BaseEntity, PageQuery } from '@/api/base'; |
||||
|
|
||||
|
export interface ContractualTaskResultsVO { |
||||
|
/** |
||||
|
* 任务结果 |
||||
|
*/ |
||||
|
result?: string; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualTaskResultDetailVO { |
||||
|
/** |
||||
|
* 分类名称 |
||||
|
*/ |
||||
|
name: string; |
||||
|
/** |
||||
|
* 结果列表 |
||||
|
*/ |
||||
|
results: { |
||||
|
/** |
||||
|
* 唯一标识ID |
||||
|
*/ |
||||
|
id?: string; |
||||
|
/** |
||||
|
* 序号 |
||||
|
*/ |
||||
|
serialNumber?: number; |
||||
|
/** |
||||
|
* 问题点名称 |
||||
|
*/ |
||||
|
issueName?: string; |
||||
|
/** |
||||
|
* 原文 |
||||
|
*/ |
||||
|
originalText?: string; |
||||
|
/** |
||||
|
* 比对原文 |
||||
|
*/ |
||||
|
comparedText?: string; |
||||
|
/** |
||||
|
* 修改后的内容 |
||||
|
*/ |
||||
|
modifiedContent?: string; |
||||
|
/** |
||||
|
* 展示修改情况 |
||||
|
*/ |
||||
|
modificationDisplay?: string; |
||||
|
/** |
||||
|
* 存在的问题 |
||||
|
*/ |
||||
|
existingIssues?: string; |
||||
|
/** |
||||
|
* 审查依据 |
||||
|
*/ |
||||
|
reviewBasis?: { |
||||
|
/** |
||||
|
* 审查内容 |
||||
|
*/ |
||||
|
reviewContent?: string; |
||||
|
/** |
||||
|
* 审查点 |
||||
|
*/ |
||||
|
reviewPoints?: string[]; |
||||
|
}; |
||||
|
/** |
||||
|
* 是否已读 |
||||
|
*/ |
||||
|
isRead?: string; |
||||
|
/** |
||||
|
* 是否采纳 |
||||
|
*/ |
||||
|
isAdopted?: string; |
||||
|
}[]; |
||||
|
} |
||||
|
|
||||
|
export interface ContractualTaskResultsForm extends BaseEntity { |
||||
|
} |
||||
|
|
||||
|
export interface ContractualTaskResultsQuery extends PageQuery { |
||||
|
|
||||
|
/** |
||||
|
* id |
||||
|
*/ |
||||
|
id?: string | number; |
||||
|
|
||||
|
/** |
||||
|
* 任务id |
||||
|
*/ |
||||
|
contractualTaskId?: string | number; |
||||
|
|
||||
|
/** |
||||
|
* 任务结果 |
||||
|
*/ |
||||
|
result?: string; |
||||
|
|
||||
|
/** |
||||
|
* 日期范围参数 |
||||
|
*/ |
||||
|
params?: any; |
||||
|
} |
Before Width: | Height: | Size: 368 B |
@ -0,0 +1,497 @@ |
|||||
|
<template> |
||||
|
<Modal |
||||
|
:open="visible" |
||||
|
title="新增合同案例文件" |
||||
|
@ok="handleSubmit" |
||||
|
@cancel="handleCancel" |
||||
|
:confirmLoading="loading" |
||||
|
width="600px" |
||||
|
> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
:model="formData" |
||||
|
:rules="rules" |
||||
|
layout="vertical" |
||||
|
> |
||||
|
<!-- 文件上传 --> |
||||
|
<FormItem label="案例文件" name="caseFile" :required="!hasUploadedFile"> |
||||
|
<div class="upload-box" :class="{'file-preview': fileList && fileList.length > 0}"> |
||||
|
<template v-if="fileList && fileList.length > 0"> |
||||
|
<!-- 文件预览 --> |
||||
|
<div class="file-info"> |
||||
|
<FileTextOutlined class="file-icon" /> |
||||
|
<div class="file-details"> |
||||
|
<p class="file-name">{{ fileList[0].name }}</p> |
||||
|
<div class="file-progress" v-if="uploading"> |
||||
|
<Progress :percent="uploadPercent" size="small" status="active" /> |
||||
|
</div> |
||||
|
<p class="file-status" v-else> |
||||
|
<CheckCircleFilled class="status-icon success" /> 上传成功 |
||||
|
<Button type="link" class="remove-btn" @click="removeFile">删除</Button> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<!-- 上传区域 --> |
||||
|
<Upload |
||||
|
:fileList="fileList" |
||||
|
:customRequest="customUploadRequest" |
||||
|
:beforeUpload="beforeUpload" |
||||
|
:showUploadList="false" |
||||
|
:maxCount="1" |
||||
|
:multiple="false" |
||||
|
name="file" |
||||
|
accept=".doc,.docx" |
||||
|
:disabled="uploading" |
||||
|
> |
||||
|
<div class="upload-content"> |
||||
|
<div class="upload-icon"> |
||||
|
<UploadOutlined class="upload-arrow-icon" /> |
||||
|
</div> |
||||
|
<div class="upload-text-container"> |
||||
|
<p class="upload-text"> |
||||
|
<a class="upload-link">选择案例文件</a> |
||||
|
</p> |
||||
|
<p class="upload-hint">支持DOC、DOCX格式文件,最大500MB</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Upload> |
||||
|
</template> |
||||
|
</div> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 案例名称 --> |
||||
|
<FormItem label="案例名称" name="caseName" required> |
||||
|
<Input |
||||
|
v-model:value="formData.caseName" |
||||
|
:disabled="caseNameDisabled" |
||||
|
:placeholder="caseNamePlaceholder" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 案例描述 --> |
||||
|
<FormItem label="案例描述" name="caseDescription" required> |
||||
|
<TextArea |
||||
|
v-model:value="formData.caseDescription" |
||||
|
placeholder="请输入案例描述" |
||||
|
:rows="4" |
||||
|
show-count |
||||
|
:maxlength="500" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 案例类型 --> |
||||
|
<FormItem label="案例类型" name="caseType"> |
||||
|
<Input v-model:value="formData.caseType" placeholder="请输入案例类型" /> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 发布日期 --> |
||||
|
<FormItem label="发布日期" name="publishDate" required> |
||||
|
<DatePicker |
||||
|
v-model:value="formData.publishDate" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 100%" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 是否有效 --> |
||||
|
<FormItem label="是否有效" name="isEffective"> |
||||
|
<Select |
||||
|
v-model:value="formData.isEffective" |
||||
|
:options="effectiveOptions" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, computed, watch } from 'vue'; |
||||
|
import { |
||||
|
Modal, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Upload, |
||||
|
Button, |
||||
|
Progress, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
UploadOutlined, |
||||
|
FileTextOutlined, |
||||
|
CheckCircleFilled |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { ContractualCaseFilesAdd } from '@/api/contractReview/ContractualCaseFiles'; |
||||
|
import { uploadApi } from '@/api/upload'; |
||||
|
import { ossRemove } from '@/api/system/oss'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import { UploadFileParams } from '#/axios'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
const { TextArea } = Input; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>(); |
||||
|
const emit = defineEmits(['update:visible', 'success']); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const fileList = ref<any[]>([]); |
||||
|
const uploading = ref(false); |
||||
|
const uploadPercent = ref(0); |
||||
|
const currentOssId = ref<string | null>(null); |
||||
|
const formRef = ref(); |
||||
|
|
||||
|
// 表单数据 |
||||
|
const formData = reactive({ |
||||
|
caseName: '', |
||||
|
caseDescription: '', |
||||
|
caseType: '', |
||||
|
publishDate: dayjs(), |
||||
|
isEffective: 'Y', |
||||
|
progressStatus: 'PENDING' |
||||
|
}); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const hasUploadedFile = computed(() => { |
||||
|
return !!currentOssId.value; |
||||
|
}); |
||||
|
|
||||
|
const caseNameDisabled = computed(() => { |
||||
|
return !currentOssId.value; |
||||
|
}); |
||||
|
|
||||
|
const caseNamePlaceholder = computed(() => { |
||||
|
return currentOssId.value ? '可编辑案例名称' : '请先上传案例文件'; |
||||
|
}); |
||||
|
|
||||
|
// 表单验证规则 - 动态规则 |
||||
|
const rules = computed(() => ({ |
||||
|
caseFile: hasUploadedFile.value ? [] : [{ required: true, message: '请上传案例文件' }], |
||||
|
caseName: [{ required: true, message: '请输入案例名称' }], |
||||
|
caseDescription: [{ required: true, message: '请输入案例描述' }], |
||||
|
publishDate: [{ required: true, message: '请选择发布日期' }], |
||||
|
})); |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 监听弹窗关闭,重置表单 |
||||
|
watch(() => props.visible, (newVal) => { |
||||
|
if (!newVal) { |
||||
|
resetForm(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 文件上传相关处理 |
||||
|
function customUploadRequest(options: any) { |
||||
|
const { file, onSuccess, onError } = options; |
||||
|
|
||||
|
uploading.value = true; |
||||
|
uploadPercent.value = 0; |
||||
|
|
||||
|
const uploadParams: UploadFileParams = { |
||||
|
name: 'file', |
||||
|
file: file, |
||||
|
data: {}, |
||||
|
}; |
||||
|
|
||||
|
fileList.value = [ |
||||
|
{ |
||||
|
uid: '1', |
||||
|
name: file.name, |
||||
|
status: 'uploading', |
||||
|
url: URL.createObjectURL(file), |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
uploadApi( |
||||
|
uploadParams, |
||||
|
(progressEvent) => { |
||||
|
if (progressEvent.total) { |
||||
|
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100); |
||||
|
uploadPercent.value = percent; |
||||
|
} |
||||
|
} |
||||
|
).then((res) => { |
||||
|
if (fileList.value && fileList.value.length > 0) { |
||||
|
fileList.value[0].status = 'done'; |
||||
|
fileList.value[0].response = res; |
||||
|
if (res && res.ossId) { |
||||
|
currentOssId.value = res.ossId; |
||||
|
const fileName = file.name; |
||||
|
const nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.')) || fileName; |
||||
|
formData.caseName = nameWithoutExt; |
||||
|
|
||||
|
// 文件上传成功后,清除文件字段的验证错误 |
||||
|
formRef.value?.clearValidate('caseFile'); |
||||
|
} |
||||
|
} |
||||
|
uploading.value = false; |
||||
|
uploadPercent.value = 100; |
||||
|
message.success(`${file.name} 文件上传成功`); |
||||
|
onSuccess(res); |
||||
|
}).catch((err) => { |
||||
|
uploading.value = false; |
||||
|
fileList.value = []; |
||||
|
currentOssId.value = null; |
||||
|
message.error(`文件上传失败: ${err.message || '未知错误'}`); |
||||
|
onError(err); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function beforeUpload(file: File) { |
||||
|
const isDoc = file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || |
||||
|
file.type === 'application/msword'; |
||||
|
|
||||
|
if (!isDoc) { |
||||
|
message.error('只能上传 DOC/DOCX 格式的文件!'); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const isLt500M = file.size / 1024 / 1024 < 500; |
||||
|
if (!isLt500M) { |
||||
|
message.error('文件大小不能超过 500MB!'); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
function removeFile() { |
||||
|
if (uploading.value) { |
||||
|
message.warning('文件正在上传中,请稍后再试'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (currentOssId.value) { |
||||
|
ossRemove([currentOssId.value]) |
||||
|
.then(() => { |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
currentOssId.value = null; |
||||
|
formData.caseName = ''; |
||||
|
message.success('文件已删除'); |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
message.error(`文件删除失败: ${err.message || '未知错误'}`); |
||||
|
}); |
||||
|
} else { |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
formData.caseName = ''; |
||||
|
message.success('文件已删除'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 过滤空值参数 |
||||
|
function filterEmptyParams(params: any) { |
||||
|
const filteredParams: any = {}; |
||||
|
Object.keys(params).forEach(key => { |
||||
|
const value = params[key]; |
||||
|
if (value !== null && value !== undefined && value !== '') { |
||||
|
filteredParams[key] = value; |
||||
|
} |
||||
|
}); |
||||
|
return filteredParams; |
||||
|
} |
||||
|
|
||||
|
// 提交表单 |
||||
|
async function handleSubmit() { |
||||
|
try { |
||||
|
await formRef.value?.validate(); |
||||
|
} catch (error) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!currentOssId.value) { |
||||
|
message.warning('请上传案例文件'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const baseData = { |
||||
|
...formData, |
||||
|
publishDate: formData.publishDate ? formData.publishDate.format('YYYY-MM-DD') : '', |
||||
|
ossId: currentOssId.value |
||||
|
}; |
||||
|
|
||||
|
const submitData = filterEmptyParams(baseData); |
||||
|
|
||||
|
await ContractualCaseFilesAdd(submitData); |
||||
|
message.success('新增成功'); |
||||
|
|
||||
|
// TODO: 预留代码空位 - 发起解析API请求 |
||||
|
// await parseCaseFile(currentOssId.value); |
||||
|
|
||||
|
emit('update:visible', false); |
||||
|
emit('success'); |
||||
|
resetForm(); |
||||
|
} catch (error) { |
||||
|
message.error('新增失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 取消 |
||||
|
function handleCancel() { |
||||
|
emit('update:visible', false); |
||||
|
resetForm(); |
||||
|
} |
||||
|
|
||||
|
// 重置表单 |
||||
|
function resetForm() { |
||||
|
Object.assign(formData, { |
||||
|
caseName: '', |
||||
|
caseDescription: '', |
||||
|
caseType: '', |
||||
|
publishDate: dayjs(), |
||||
|
isEffective: 'Y', |
||||
|
progressStatus: 'PENDING' |
||||
|
}); |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
currentOssId.value = null; |
||||
|
formRef.value?.clearValidate(); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
/* 文件上传样式 */ |
||||
|
.upload-box { |
||||
|
border: 1px dashed #ddd; |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
transition: border-color 0.3s; |
||||
|
width: 100%; |
||||
|
height: 200px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background-color: #f9f9f9; |
||||
|
|
||||
|
&:hover { |
||||
|
border-color: #13c2c2; |
||||
|
background-color: rgba(19, 194, 194, 0.02); |
||||
|
} |
||||
|
|
||||
|
&.file-preview { |
||||
|
border: 2px solid #e6fffb; |
||||
|
background-color: #f0f8ff; |
||||
|
cursor: default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.upload-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.upload-icon { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16px; |
||||
|
background-color: #e6fffb; |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.upload-arrow-icon { |
||||
|
font-size: 24px; |
||||
|
color: #13c2c2; |
||||
|
} |
||||
|
|
||||
|
.upload-text-container { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.upload-text { |
||||
|
font-size: 16px; |
||||
|
color: #333; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.upload-link { |
||||
|
color: #13c2c2; |
||||
|
cursor: pointer; |
||||
|
text-decoration: none; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.upload-link:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.upload-hint { |
||||
|
color: #888; |
||||
|
font-size: 14px; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
|
||||
|
.file-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
max-width: 800px; |
||||
|
} |
||||
|
|
||||
|
.file-icon { |
||||
|
font-size: 32px; |
||||
|
color: #13c2c2; |
||||
|
margin-right: 16px; |
||||
|
} |
||||
|
|
||||
|
.file-details { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.file-name { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
margin: 0 0 8px 0; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.file-progress { |
||||
|
margin: 0 0 8px 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.file-status { |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.status-icon { |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
|
||||
|
.status-icon.success { |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
.remove-btn { |
||||
|
padding: 0; |
||||
|
margin-left: 12px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,460 @@ |
|||||
|
<template> |
||||
|
<Drawer |
||||
|
v-model:open="visible" |
||||
|
title="修改合同案例" |
||||
|
width="80%" |
||||
|
:destroyOnClose="true" |
||||
|
class="edit-case-drawer" |
||||
|
placement="right" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Space> |
||||
|
<Button @click="handleCancel">取消</Button> |
||||
|
<Button type="primary" @click="handleSubmit" :loading="loading">保存</Button> |
||||
|
</Space> |
||||
|
</template> |
||||
|
|
||||
|
<div class="edit-container"> |
||||
|
<!-- 左侧PDF预览 --> |
||||
|
<div class="pdf-preview-section"> |
||||
|
<div class="section-title"> |
||||
|
<FileTextOutlined /> |
||||
|
<span>案例文档预览</span> |
||||
|
<Button type="link" size="small" @click="refreshPdf" :loading="pdfLoading"> |
||||
|
<ReloadOutlined /> |
||||
|
</Button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="pdf-container"> |
||||
|
<div v-if="pdfLoading" class="loading-container"> |
||||
|
<Spin size="large" tip="正在加载文档..." /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="pdfError" class="error-container"> |
||||
|
<Result |
||||
|
status="error" |
||||
|
:title="pdfError" |
||||
|
sub-title="请检查网络连接或联系管理员" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Button type="primary" @click="refreshPdf">重新加载</Button> |
||||
|
</template> |
||||
|
</Result> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="pdfUrl" class="pdf-embed-container"> |
||||
|
<embed |
||||
|
:src="pdfUrl" |
||||
|
type="application/pdf" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="no-pdf-container"> |
||||
|
<Empty description="暂无PDF文档" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 右侧修改表单 --> |
||||
|
<div class="edit-form-section"> |
||||
|
<div class="section-title"> |
||||
|
<EditOutlined /> |
||||
|
<span>修改信息</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-container"> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
:model="formData" |
||||
|
:rules="rules" |
||||
|
layout="vertical" |
||||
|
class="edit-form" |
||||
|
> |
||||
|
<!-- 案例名称 --> |
||||
|
<FormItem label="案例名称" name="caseName" required> |
||||
|
<Input |
||||
|
v-model:value="formData.caseName" |
||||
|
placeholder="请输入案例名称" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 案例描述 --> |
||||
|
<FormItem label="案例描述" name="caseDescription"> |
||||
|
<TextArea |
||||
|
v-model:value="formData.caseDescription" |
||||
|
placeholder="请输入案例描述" |
||||
|
:rows="6" |
||||
|
show-count |
||||
|
:maxlength="500" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 案例类型 --> |
||||
|
<FormItem label="案例类型" name="caseType"> |
||||
|
<Input |
||||
|
v-model:value="formData.caseType" |
||||
|
placeholder="请输入案例类型" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 发布日期 --> |
||||
|
<FormItem label="发布日期" name="publishDate"> |
||||
|
<DatePicker |
||||
|
v-model:value="formData.publishDate" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 100%" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 是否有效 --> |
||||
|
<FormItem label="是否有效" name="isEffective"> |
||||
|
<Select |
||||
|
v-model:value="formData.isEffective" |
||||
|
:options="effectiveOptions" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, computed, watch, onUnmounted } from 'vue'; |
||||
|
import { |
||||
|
Drawer, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Button, |
||||
|
Space, |
||||
|
Spin, |
||||
|
Result, |
||||
|
Empty, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
FileTextOutlined, |
||||
|
EditOutlined, |
||||
|
ReloadOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualCaseFilesInfo, |
||||
|
ContractualCaseFilesUpdate, |
||||
|
ContractualCaseFilesViewPdf |
||||
|
} from '@/api/contractReview/ContractualCaseFiles'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
|
||||
|
const { TextArea } = Input; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
caseFileId?: number | string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
visible: false, |
||||
|
caseFileId: undefined |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
'update:visible': [value: boolean]; |
||||
|
'success': []; |
||||
|
}>(); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const pdfLoading = ref(false); |
||||
|
const pdfError = ref(''); |
||||
|
const pdfUrl = ref(''); |
||||
|
const formRef = ref(); |
||||
|
|
||||
|
// 表单数据 |
||||
|
const formData = reactive({ |
||||
|
id: undefined as string | number | undefined, |
||||
|
caseName: '', |
||||
|
caseDescription: '', |
||||
|
caseType: '', |
||||
|
publishDate: '', |
||||
|
isEffective: 'Y' |
||||
|
}); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const visible = computed({ |
||||
|
get: () => props.visible, |
||||
|
set: (value) => emit('update:visible', value) |
||||
|
}); |
||||
|
|
||||
|
// 表单验证规则 |
||||
|
const rules = computed(() => ({ |
||||
|
caseName: [{ required: true, message: '请输入案例名称', trigger: 'blur' }], |
||||
|
})); |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 监听弹窗显示 |
||||
|
watch(() => props.visible, (newVisible) => { |
||||
|
if (newVisible && props.caseFileId) { |
||||
|
loadCaseData(); |
||||
|
loadPdf(); |
||||
|
} else if (!newVisible) { |
||||
|
resetForm(); |
||||
|
cleanupPdfUrl(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 加载案例数据 |
||||
|
async function loadCaseData() { |
||||
|
if (!props.caseFileId) return; |
||||
|
|
||||
|
try { |
||||
|
const result = await ContractualCaseFilesInfo(props.caseFileId); |
||||
|
if (result) { |
||||
|
formData.id = result.id; |
||||
|
formData.caseName = result.caseName || ''; |
||||
|
formData.caseDescription = result.caseDescription || ''; |
||||
|
formData.caseType = result.caseType || ''; |
||||
|
formData.publishDate = result.publishDate || ''; |
||||
|
formData.isEffective = result.isEffective || 'Y'; |
||||
|
} |
||||
|
} catch (error: any) { |
||||
|
message.error('加载案例信息失败: ' + (error.message || '未知错误')); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 加载PDF |
||||
|
async function loadPdf() { |
||||
|
if (!props.caseFileId) return; |
||||
|
|
||||
|
pdfLoading.value = true; |
||||
|
pdfError.value = ''; |
||||
|
|
||||
|
try { |
||||
|
const pdfResponse = await ContractualCaseFilesViewPdf(props.caseFileId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
// 清理之前的URL |
||||
|
cleanupPdfUrl(); |
||||
|
|
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
} else { |
||||
|
pdfError.value = 'PDF数据格式错误'; |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
pdfError.value = err.message || '加载PDF失败'; |
||||
|
console.error('加载PDF失败:', err); |
||||
|
} finally { |
||||
|
pdfLoading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 刷新PDF |
||||
|
function refreshPdf() { |
||||
|
loadPdf(); |
||||
|
} |
||||
|
|
||||
|
// 提交表单 |
||||
|
async function handleSubmit() { |
||||
|
try { |
||||
|
await formRef.value?.validate(); |
||||
|
} catch (error) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const submitData = { |
||||
|
...formData, |
||||
|
publishDate: formData.publishDate ? dayjs(formData.publishDate).format('YYYY-MM-DD') : '' |
||||
|
}; |
||||
|
|
||||
|
await ContractualCaseFilesUpdate(submitData); |
||||
|
message.success('修改成功'); |
||||
|
|
||||
|
emit('update:visible', false); |
||||
|
emit('success'); |
||||
|
} catch (error: any) { |
||||
|
message.error('修改失败: ' + (error.message || '未知错误')); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 取消 |
||||
|
function handleCancel() { |
||||
|
emit('update:visible', false); |
||||
|
} |
||||
|
|
||||
|
// 重置表单 |
||||
|
function resetForm() { |
||||
|
Object.assign(formData, { |
||||
|
id: undefined, |
||||
|
caseName: '', |
||||
|
caseDescription: '', |
||||
|
caseType: '', |
||||
|
publishDate: '', |
||||
|
isEffective: 'Y' |
||||
|
}); |
||||
|
formRef.value?.clearValidate(); |
||||
|
pdfError.value = ''; |
||||
|
} |
||||
|
|
||||
|
// 清理Blob URL |
||||
|
function cleanupPdfUrl() { |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
pdfUrl.value = ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件销毁时清理 |
||||
|
onUnmounted(() => { |
||||
|
cleanupPdfUrl(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.edit-case-drawer { |
||||
|
:deep(.ant-drawer-content) { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-drawer-body) { |
||||
|
padding: 0; |
||||
|
height: calc(100vh - 55px); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.edit-container { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
gap: 1px; |
||||
|
background: #f0f2f5; |
||||
|
} |
||||
|
|
||||
|
.pdf-preview-section { |
||||
|
flex: 1; |
||||
|
background: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.edit-form-section { |
||||
|
width: 400px; |
||||
|
background: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
border-left: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
padding: 16px 20px; |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
color: #262626; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
background: #fafafa; |
||||
|
|
||||
|
span { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
background: #f5f5f5; |
||||
|
|
||||
|
.loading-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.error-container { |
||||
|
padding: 40px; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.pdf-embed-container { |
||||
|
height: 100%; |
||||
|
padding: 16px; |
||||
|
|
||||
|
embed { |
||||
|
border-radius: 6px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.no-pdf-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.form-container { |
||||
|
flex: 1; |
||||
|
padding: 20px; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.edit-form { |
||||
|
.ant-form-item { |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.ant-form-item-label > label { |
||||
|
font-weight: 500; |
||||
|
color: #262626; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 1400px) { |
||||
|
.edit-form-section { |
||||
|
width: 380px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 1200px) { |
||||
|
.edit-container { |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.pdf-preview-section { |
||||
|
height: 50%; |
||||
|
} |
||||
|
|
||||
|
.edit-form-section { |
||||
|
width: 100%; |
||||
|
height: 50%; |
||||
|
border-left: none; |
||||
|
border-top: 1px solid #e8e8e8; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,382 @@ |
|||||
|
<template> |
||||
|
<Drawer |
||||
|
v-model:open="visible" |
||||
|
:title="modalTitle" |
||||
|
width="70%" |
||||
|
:destroyOnClose="true" |
||||
|
class="case-view-drawer" |
||||
|
placement="right" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Space> |
||||
|
<Button type="primary" @click="downloadPdf" :loading="downloading"> |
||||
|
<template #icon><DownloadOutlined /></template> |
||||
|
下载PDF |
||||
|
</Button> |
||||
|
<Button @click="refreshPdf"> |
||||
|
<template #icon><ReloadOutlined /></template> |
||||
|
刷新 |
||||
|
</Button> |
||||
|
</Space> |
||||
|
</template> |
||||
|
|
||||
|
<div class="case-view-container"> |
||||
|
<!-- PDF预览区域 --> |
||||
|
<div class="pdf-container"> |
||||
|
<div v-if="loading" class="loading-container"> |
||||
|
<Spin size="large" tip="正在加载案例文档..." /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="error" class="error-container"> |
||||
|
<Result |
||||
|
status="error" |
||||
|
:title="error" |
||||
|
sub-title="请检查网络连接或联系管理员" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Button type="primary" @click="refreshPdf">重新加载</Button> |
||||
|
</template> |
||||
|
</Result> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="pdf-embed-container"> |
||||
|
<embed |
||||
|
:src="pdfUrl" |
||||
|
type="application/pdf" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
/> |
||||
|
<!-- 备选方案:如果embed不支持,显示iframe --> |
||||
|
<iframe |
||||
|
v-if="showIframe" |
||||
|
:src="pdfUrl" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
></iframe> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 条款列表(备选展示方式) --> |
||||
|
<div v-if="showArticlesList" class="articles-list"> |
||||
|
<Divider>案例条款详情</Divider> |
||||
|
<div v-for="(article, index) in articles" :key="article.id" class="article-item"> |
||||
|
<div class="article-title">第{{ index + 1 }}条</div> |
||||
|
<div class="article-content">{{ article.articleContent }}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, computed, watch, onUnmounted } from 'vue'; |
||||
|
import { |
||||
|
Drawer, |
||||
|
Button, |
||||
|
Space, |
||||
|
Spin, |
||||
|
Result, |
||||
|
Divider, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
DownloadOutlined, |
||||
|
ReloadOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualCaseFilesViewPdf, |
||||
|
ContractualCaseFilesArticles |
||||
|
} from '@/api/contractReview/ContractualCaseFiles'; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
caseFileId?: number | string; |
||||
|
caseName?: string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
visible: false, |
||||
|
caseFileId: undefined, |
||||
|
caseName: '' |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
'update:visible': [value: boolean]; |
||||
|
}>(); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const downloading = ref(false); |
||||
|
const error = ref(''); |
||||
|
const articles = ref<any[]>([]); |
||||
|
const pdfUrl = ref(''); |
||||
|
const showIframe = ref(false); |
||||
|
const showArticlesList = ref(false); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const visible = computed({ |
||||
|
get: () => props.visible, |
||||
|
set: (value) => emit('update:visible', value) |
||||
|
}); |
||||
|
|
||||
|
const modalTitle = computed(() => { |
||||
|
return props.caseName ? `案例详情 - ${props.caseName}` : '案例详情'; |
||||
|
}); |
||||
|
|
||||
|
// 监听弹窗显示 |
||||
|
watch(() => props.visible, (newVisible) => { |
||||
|
if (newVisible && props.caseFileId) { |
||||
|
loadCaseDetail(); |
||||
|
} else if (!newVisible) { |
||||
|
// 弹窗关闭时清理资源 |
||||
|
cleanupPdfUrl(); |
||||
|
error.value = ''; |
||||
|
showIframe.value = false; |
||||
|
showArticlesList.value = false; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 加载案例详情 |
||||
|
async function loadCaseDetail() { |
||||
|
if (!props.caseFileId) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
error.value = ''; |
||||
|
|
||||
|
try { |
||||
|
// 并行加载PDF和条款列表 |
||||
|
const [pdfResponse, articlesResult] = await Promise.all([ |
||||
|
ContractualCaseFilesViewPdf(props.caseFileId), |
||||
|
ContractualCaseFilesArticles(props.caseFileId) |
||||
|
]); |
||||
|
|
||||
|
console.log('PDF响应:', pdfResponse); |
||||
|
|
||||
|
// 处理PDF Blob |
||||
|
if (pdfResponse?.data) { |
||||
|
console.log('PDF加载成功, 创建Blob'); |
||||
|
|
||||
|
// 清理之前的URL |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
} |
||||
|
|
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
console.log('PDF URL创建成功:', pdfUrl.value); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
|
||||
|
// 处理条款列表 |
||||
|
articles.value = articlesResult || []; |
||||
|
|
||||
|
} catch (err: any) { |
||||
|
error.value = err.message || '加载案例详情失败'; |
||||
|
console.error('加载案例详情失败:', err); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 刷新PDF |
||||
|
async function refreshPdf() { |
||||
|
if (!props.caseFileId) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
error.value = ''; |
||||
|
|
||||
|
try { |
||||
|
const pdfResponse = await ContractualCaseFilesViewPdf(props.caseFileId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
// 清理之前的URL |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
} |
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
error.value = err.message || '刷新PDF失败'; |
||||
|
console.error('刷新PDF失败:', err); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 下载PDF |
||||
|
async function downloadPdf() { |
||||
|
if (!props.caseFileId) return; |
||||
|
|
||||
|
downloading.value = true; |
||||
|
try { |
||||
|
const pdfResponse = await ContractualCaseFilesViewPdf(props.caseFileId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
const url = URL.createObjectURL(blob); |
||||
|
const link = document.createElement('a'); |
||||
|
link.href = url; |
||||
|
link.download = `${props.caseName || '案例文档'}.pdf`; |
||||
|
document.body.appendChild(link); |
||||
|
link.click(); |
||||
|
document.body.removeChild(link); |
||||
|
|
||||
|
// 清理临时URL |
||||
|
URL.revokeObjectURL(url); |
||||
|
message.success('PDF下载已开始'); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
message.error('下载失败: ' + (err.message || '未知错误')); |
||||
|
console.error('下载PDF失败:', err); |
||||
|
} finally { |
||||
|
downloading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 切换显示模式 |
||||
|
function toggleDisplayMode() { |
||||
|
showArticlesList.value = !showArticlesList.value; |
||||
|
} |
||||
|
|
||||
|
// PDF加载错误处理 |
||||
|
function handlePdfError() { |
||||
|
showIframe.value = true; |
||||
|
} |
||||
|
|
||||
|
// 清理Blob URL |
||||
|
function cleanupPdfUrl() { |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
pdfUrl.value = ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件销毁时清理 |
||||
|
onUnmounted(() => { |
||||
|
cleanupPdfUrl(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.case-view-drawer { |
||||
|
:deep(.ant-drawer-content) { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-drawer-body) { |
||||
|
padding: 0; |
||||
|
height: calc(100vh - 55px); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.case-view-container { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
padding: 16px; |
||||
|
} |
||||
|
|
||||
|
.toolbar { |
||||
|
margin-bottom: 16px; |
||||
|
padding: 12px; |
||||
|
background: #fafafa; |
||||
|
border-radius: 6px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
min-height: 0; |
||||
|
|
||||
|
.loading-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.error-container { |
||||
|
padding: 40px; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.pdf-embed-container { |
||||
|
border: 1px solid #d9d9d9; |
||||
|
border-radius: 6px; |
||||
|
overflow: hidden; |
||||
|
height: 100%; |
||||
|
|
||||
|
embed, iframe { |
||||
|
display: block; |
||||
|
height: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.articles-list { |
||||
|
margin-top: 24px; |
||||
|
flex-shrink: 0; |
||||
|
max-height: 300px; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.article-item { |
||||
|
margin-bottom: 24px; |
||||
|
padding: 16px; |
||||
|
border: 1px solid #e8e8e8; |
||||
|
border-radius: 6px; |
||||
|
background: #fafafa; |
||||
|
|
||||
|
.article-title { |
||||
|
font-weight: bold; |
||||
|
font-size: 16px; |
||||
|
color: #262626; |
||||
|
margin-bottom: 12px; |
||||
|
padding-bottom: 8px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.article-content { |
||||
|
line-height: 1.8; |
||||
|
color: #595959; |
||||
|
text-align: justify; |
||||
|
white-space: pre-wrap; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 1200px) { |
||||
|
.case-view-drawer { |
||||
|
:deep(.ant-drawer) { |
||||
|
width: 80% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 768px) { |
||||
|
.case-view-drawer { |
||||
|
:deep(.ant-drawer) { |
||||
|
width: 90% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,458 @@ |
|||||
|
<template> |
||||
|
<PageWrapper dense> |
||||
|
<!-- 搜索表单 --> |
||||
|
<Card class="search-card"> |
||||
|
<Form |
||||
|
:model="searchForm" |
||||
|
layout="inline" |
||||
|
class="search-form" |
||||
|
> |
||||
|
<FormItem label="案例名称"> |
||||
|
<Input |
||||
|
v-model:value="searchForm.caseName" |
||||
|
placeholder="请输入案例名称" |
||||
|
style="width: 200px" |
||||
|
allowClear |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem label="案例类型"> |
||||
|
<Input |
||||
|
v-model:value="searchForm.caseType" |
||||
|
placeholder="请输入案例类型" |
||||
|
style="width: 150px" |
||||
|
allowClear |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem label="发布日期"> |
||||
|
<RangePicker |
||||
|
v-model:value="searchForm.publishDateRange" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 240px" |
||||
|
allowClear |
||||
|
:placeholder="['开始日期', '结束日期']" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem label="是否有效"> |
||||
|
<Select |
||||
|
v-model:value="searchForm.isEffective" |
||||
|
placeholder="请选择状态" |
||||
|
style="width: 120px" |
||||
|
allowClear |
||||
|
:options="effectiveOptions" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem> |
||||
|
<Space> |
||||
|
<Button type="primary" @click="handleSearch">查询</Button> |
||||
|
<Button @click="resetSearch">重置</Button> |
||||
|
</Space> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</Card> |
||||
|
|
||||
|
<!-- 表格区域 --> |
||||
|
<Card class="table-card"> |
||||
|
<!-- 表格标题和操作按钮 --> |
||||
|
<div class="table-header"> |
||||
|
<div class="table-title">合同案例文件列表</div> |
||||
|
<div class="table-actions"> |
||||
|
<Space> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
danger |
||||
|
@click="multipleRemove" |
||||
|
:disabled="!selectedRowKeys.length" |
||||
|
v-auth="'productManagement:ContractualCaseFiles:remove'" |
||||
|
> |
||||
|
删除 |
||||
|
</Button> |
||||
|
<Button type="primary" @click="handleAdd" v-auth="'productManagement:ContractualCaseFiles:add'"> |
||||
|
新增 |
||||
|
</Button> |
||||
|
<Button @click="handleRefresh"> |
||||
|
<template #icon><ReloadOutlined /></template> |
||||
|
</Button> |
||||
|
</Space> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 数据表格 --> |
||||
|
<Table |
||||
|
:dataSource="dataSource" |
||||
|
:columns="tableColumns" |
||||
|
:loading="loading" |
||||
|
:pagination="pagination" |
||||
|
:rowSelection="rowSelection" |
||||
|
rowKey="id" |
||||
|
@change="handleTableChange" |
||||
|
size="middle" |
||||
|
/> |
||||
|
</Card> |
||||
|
|
||||
|
<!-- 新增弹窗 --> |
||||
|
<AddModal |
||||
|
v-model:visible="modalVisible" |
||||
|
@success="loadData" |
||||
|
/> |
||||
|
|
||||
|
<!-- 查看弹窗 --> |
||||
|
<ViewModal |
||||
|
v-model:visible="viewModalVisible" |
||||
|
:caseFileId="selectedRecord?.id" |
||||
|
:caseName="selectedRecord?.caseName" |
||||
|
/> |
||||
|
|
||||
|
<!-- 编辑弹窗 --> |
||||
|
<EditModal |
||||
|
v-model:visible="editModalVisible" |
||||
|
:caseFileId="selectedRecord?.id" |
||||
|
:caseName="selectedRecord?.caseName" |
||||
|
@success="loadData" |
||||
|
/> |
||||
|
</PageWrapper> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, onMounted, h } from 'vue'; |
||||
|
import { PageWrapper } from '@/components/Page'; |
||||
|
import { |
||||
|
Table, |
||||
|
Button, |
||||
|
Space, |
||||
|
Card, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Switch, |
||||
|
Modal, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
ReloadOutlined, |
||||
|
EyeOutlined, |
||||
|
DeleteOutlined, |
||||
|
EditOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualCaseFilesList, |
||||
|
ContractualCaseFilesRemove, |
||||
|
ContractualCaseFilesUpdateStatus |
||||
|
} from '@/api/contractReview/ContractualCaseFiles'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import AddModal from './AddModal.vue'; |
||||
|
import ViewModal from './ViewModal.vue'; |
||||
|
import EditModal from './EditModal.vue'; |
||||
|
const { RangePicker } = DatePicker; |
||||
|
import { useRender } from '@/hooks/component/useRender'; |
||||
|
|
||||
|
const { renderDict } = useRender(); |
||||
|
defineOptions({ name: 'ContractualCaseFiles' }); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const dataSource = ref<any[]>([]); |
||||
|
const selectedRowKeys = ref<any[]>([]); |
||||
|
const modalVisible = ref(false); |
||||
|
const viewModalVisible = ref(false); |
||||
|
const editModalVisible = ref(false); |
||||
|
const selectedRecord = ref<any>(null); |
||||
|
const searchForm = reactive({ |
||||
|
caseName: '', |
||||
|
caseType: '', |
||||
|
publishDateRange: null as any, |
||||
|
isEffective: undefined as string | undefined |
||||
|
}); |
||||
|
|
||||
|
// 分页配置 |
||||
|
const pagination = reactive({ |
||||
|
current: 1, |
||||
|
pageSize: 10, |
||||
|
total: 0, |
||||
|
showSizeChanger: true, |
||||
|
showQuickJumper: true, |
||||
|
showTotal: (total: number) => `共 ${total} 条数据`, |
||||
|
}); |
||||
|
|
||||
|
// 行选择配置 |
||||
|
const rowSelection = { |
||||
|
selectedRowKeys: selectedRowKeys.value, |
||||
|
onChange: (keys: any[]) => { |
||||
|
selectedRowKeys.value = keys; |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 开关状态切换 |
||||
|
async function handleSwitchChange(record: any) { |
||||
|
if (record._switching) return; |
||||
|
|
||||
|
record._switching = true; |
||||
|
try { |
||||
|
const newStatus = record.isEffective === 'Y' ? 'N' : 'Y'; |
||||
|
await ContractualCaseFilesUpdateStatus(record.id, newStatus); |
||||
|
record.isEffective = newStatus; |
||||
|
message.success('状态更新成功'); |
||||
|
} catch (error) { |
||||
|
message.error('状态更新失败'); |
||||
|
} finally { |
||||
|
record._switching = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表格列配置 |
||||
|
const tableColumns = [ |
||||
|
{ |
||||
|
title: '案例名称', |
||||
|
dataIndex: 'caseName', |
||||
|
key: 'caseName', |
||||
|
width: 180, |
||||
|
}, |
||||
|
{ |
||||
|
title: '案例描述', |
||||
|
dataIndex: 'caseDescription', |
||||
|
key: 'caseDescription', |
||||
|
width: 200, |
||||
|
ellipsis: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '案例类型', |
||||
|
dataIndex: 'caseType', |
||||
|
key: 'caseType', |
||||
|
width: 120, |
||||
|
}, |
||||
|
{ |
||||
|
title: '发布日期', |
||||
|
dataIndex: 'publishDate', |
||||
|
key: 'publishDate', |
||||
|
width: 120, |
||||
|
}, |
||||
|
{ |
||||
|
title: '是否有效', |
||||
|
dataIndex: 'isEffective', |
||||
|
key: 'isEffective', |
||||
|
width: 120, |
||||
|
customRender: ({ record }: any) => { |
||||
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [ |
||||
|
h(Switch, { |
||||
|
checked: record.isEffective === 'Y', |
||||
|
loading: record._switching, |
||||
|
onChange: () => handleSwitchChange(record) |
||||
|
}), |
||||
|
h('span', {}, record.isEffective === 'Y' ? '有效' : '无效') |
||||
|
]); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '当前状态', |
||||
|
dataIndex: 'progressStatus', |
||||
|
key: 'progressStatus', |
||||
|
width: 120, |
||||
|
customRender: ({ value }) => renderDict(value, 'file_progress_status'), |
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
key: 'action', |
||||
|
fixed: 'right' as const, |
||||
|
width: 180, |
||||
|
customRender: ({ record }: any) => { |
||||
|
return h(Space, {}, [ |
||||
|
h(Button, { |
||||
|
type: 'primary', |
||||
|
size: 'small', |
||||
|
icon: h(EyeOutlined), |
||||
|
onClick: () => handleView(record) |
||||
|
}, '查看'), |
||||
|
h(Button, { |
||||
|
type: 'default', |
||||
|
size: 'small', |
||||
|
icon: h(EditOutlined), |
||||
|
onClick: () => handleEdit(record) |
||||
|
}, '修改'), |
||||
|
h(Button, { |
||||
|
type: 'primary', |
||||
|
danger: true, |
||||
|
size: 'small', |
||||
|
icon: h(DeleteOutlined), |
||||
|
onClick: () => handleDelete(record) |
||||
|
}, '删除') |
||||
|
]); |
||||
|
}, |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
// 过滤空值参数 |
||||
|
function filterEmptyParams(params: any) { |
||||
|
const filteredParams: any = {}; |
||||
|
Object.keys(params).forEach(key => { |
||||
|
const value = params[key]; |
||||
|
if (value !== null && value !== undefined && value !== '') { |
||||
|
filteredParams[key] = value; |
||||
|
} |
||||
|
}); |
||||
|
return filteredParams; |
||||
|
} |
||||
|
|
||||
|
// 加载数据 |
||||
|
async function loadData() { |
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const baseParams: any = { |
||||
|
caseName: searchForm.caseName, |
||||
|
caseType: searchForm.caseType, |
||||
|
pageNum: pagination.current, |
||||
|
pageSize: pagination.pageSize, |
||||
|
}; |
||||
|
|
||||
|
// 处理日期范围参数 |
||||
|
if (searchForm.publishDateRange && searchForm.publishDateRange.length === 2) { |
||||
|
baseParams.publishDateStart = searchForm.publishDateRange[0]; |
||||
|
baseParams.publishDateEnd = searchForm.publishDateRange[1]; |
||||
|
} |
||||
|
|
||||
|
// 过滤空值参数 |
||||
|
const params = filterEmptyParams(baseParams); |
||||
|
|
||||
|
const result: any = await ContractualCaseFilesList(params); |
||||
|
dataSource.value = result.rows || []; |
||||
|
pagination.total = result.total || 0; |
||||
|
} catch (error) { |
||||
|
message.error('加载数据失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表格变化处理 |
||||
|
function handleTableChange(pag: any) { |
||||
|
pagination.current = pag.current; |
||||
|
pagination.pageSize = pag.pageSize; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 搜索处理 |
||||
|
function handleSearch() { |
||||
|
pagination.current = 1; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 重置搜索 |
||||
|
function resetSearch() { |
||||
|
Object.assign(searchForm, { |
||||
|
caseName: '', |
||||
|
caseType: '', |
||||
|
publishDateRange: null, |
||||
|
isEffective: undefined |
||||
|
}); |
||||
|
pagination.current = 1; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 刷新 |
||||
|
function handleRefresh() { |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 新增 |
||||
|
function handleAdd() { |
||||
|
modalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 查看详情 |
||||
|
function handleView(record: any) { |
||||
|
selectedRecord.value = record; |
||||
|
viewModalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 编辑 |
||||
|
function handleEdit(record: any) { |
||||
|
selectedRecord.value = record; |
||||
|
editModalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 单个删除 |
||||
|
async function handleDelete(record: any) { |
||||
|
Modal.confirm({ |
||||
|
title: '删除确认', |
||||
|
content: `是否删除案例《${record.caseName}》?`, |
||||
|
onOk: async () => { |
||||
|
try { |
||||
|
await ContractualCaseFilesRemove([record.id]); |
||||
|
message.success('删除成功'); |
||||
|
loadData(); |
||||
|
} catch (error) { |
||||
|
message.error('删除失败'); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 批量删除 |
||||
|
async function multipleRemove() { |
||||
|
if (!selectedRowKeys.value.length) { |
||||
|
message.warning('请选择要删除的记录'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const selectedRecords = dataSource.value.filter((item: any) => |
||||
|
selectedRowKeys.value.includes(item.id) |
||||
|
); |
||||
|
const caseNames = selectedRecords.map((item: any) => item.caseName).join('、'); |
||||
|
|
||||
|
Modal.confirm({ |
||||
|
title: '删除确认', |
||||
|
content: `是否删除案例《${caseNames}》?`, |
||||
|
onOk: async () => { |
||||
|
try { |
||||
|
await ContractualCaseFilesRemove(selectedRowKeys.value); |
||||
|
message.success('删除成功'); |
||||
|
selectedRowKeys.value = []; |
||||
|
loadData(); |
||||
|
} catch (error) { |
||||
|
message.error('删除失败'); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 页面加载时获取数据 |
||||
|
onMounted(() => { |
||||
|
loadData(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.search-card { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.search-form { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.table-card { |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.table-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.table-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-table) { |
||||
|
.ant-table-tbody > tr > td { |
||||
|
padding: 12px 8px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,476 @@ |
|||||
|
<template> |
||||
|
<Modal |
||||
|
:open="visible" |
||||
|
title="新增合同法规名称" |
||||
|
@ok="handleSubmit" |
||||
|
@cancel="handleCancel" |
||||
|
:confirmLoading="loading" |
||||
|
width="600px" |
||||
|
> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
:model="formData" |
||||
|
:rules="rules" |
||||
|
layout="vertical" |
||||
|
> |
||||
|
<!-- 文件上传 --> |
||||
|
<FormItem label="法规文件" name="regulationFile" :required="!hasUploadedFile"> |
||||
|
<div class="upload-box" :class="{'file-preview': fileList && fileList.length > 0}"> |
||||
|
<template v-if="fileList && fileList.length > 0"> |
||||
|
<!-- 文件预览 --> |
||||
|
<div class="file-info"> |
||||
|
<FileTextOutlined class="file-icon" /> |
||||
|
<div class="file-details"> |
||||
|
<p class="file-name">{{ fileList[0].name }}</p> |
||||
|
<div class="file-progress" v-if="uploading"> |
||||
|
<Progress :percent="uploadPercent" size="small" status="active" /> |
||||
|
</div> |
||||
|
<p class="file-status" v-else> |
||||
|
<CheckCircleFilled class="status-icon success" /> 上传成功 |
||||
|
<Button type="link" class="remove-btn" @click="removeFile">删除</Button> |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
<template v-else> |
||||
|
<!-- 上传区域 --> |
||||
|
<Upload |
||||
|
:fileList="fileList" |
||||
|
:customRequest="customUploadRequest" |
||||
|
:beforeUpload="beforeUpload" |
||||
|
:showUploadList="false" |
||||
|
:maxCount="1" |
||||
|
:multiple="false" |
||||
|
name="file" |
||||
|
accept=".doc,.docx" |
||||
|
:disabled="uploading" |
||||
|
> |
||||
|
<div class="upload-content"> |
||||
|
<div class="upload-icon"> |
||||
|
<UploadOutlined class="upload-arrow-icon" /> |
||||
|
</div> |
||||
|
<div class="upload-text-container"> |
||||
|
<p class="upload-text"> |
||||
|
<a class="upload-link">选择法规文件</a> |
||||
|
</p> |
||||
|
<p class="upload-hint">支持DOC、DOCX格式文件,最大500MB</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Upload> |
||||
|
</template> |
||||
|
</div> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 法规名称 --> |
||||
|
<FormItem label="法规名称" name="regulationName" required> |
||||
|
<Input |
||||
|
v-model:value="formData.regulationName" |
||||
|
:disabled="regulationNameDisabled" |
||||
|
:placeholder="regulationNamePlaceholder" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 法规描述 --> |
||||
|
<FormItem label="法规描述" name="regulationDescription" required> |
||||
|
<TextArea |
||||
|
v-model:value="formData.regulationDescription" |
||||
|
placeholder="请输入法规描述" |
||||
|
:rows="4" |
||||
|
show-count |
||||
|
:maxlength="500" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 发布日期 --> |
||||
|
<FormItem label="发布日期" name="publishDate" required> |
||||
|
<DatePicker |
||||
|
v-model:value="formData.publishDate" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 100%" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 是否有效 --> |
||||
|
<FormItem label="是否有效" name="isEffective"> |
||||
|
<Select |
||||
|
v-model:value="formData.isEffective" |
||||
|
:options="effectiveOptions" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</Modal> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, computed, watch } from 'vue'; |
||||
|
import { |
||||
|
Modal, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Upload, |
||||
|
Button, |
||||
|
Progress, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
UploadOutlined, |
||||
|
FileTextOutlined, |
||||
|
CheckCircleFilled |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { ContractualRegulationNamesAdd } from '@/api/contractReview/ContractualRegulationNames'; |
||||
|
import { uploadApi } from '@/api/upload'; |
||||
|
import { ossRemove } from '@/api/system/oss'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import { UploadFileParams } from '#/axios'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
const { TextArea } = Input; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
} |
||||
|
|
||||
|
const props = defineProps<Props>(); |
||||
|
const emit = defineEmits(['update:visible', 'success']); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const fileList = ref<any[]>([]); |
||||
|
const uploading = ref(false); |
||||
|
const uploadPercent = ref(0); |
||||
|
const currentOssId = ref<string | null>(null); |
||||
|
const formRef = ref(); |
||||
|
|
||||
|
// 表单数据 |
||||
|
const formData = reactive({ |
||||
|
regulationName: '', |
||||
|
regulationDescription: '', |
||||
|
publishDate: dayjs(), |
||||
|
isEffective: 'Y', |
||||
|
progressStatus: 'PENDING' |
||||
|
}); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const hasUploadedFile = computed(() => { |
||||
|
return !!currentOssId.value; |
||||
|
}); |
||||
|
|
||||
|
const regulationNameDisabled = computed(() => { |
||||
|
return !currentOssId.value; |
||||
|
}); |
||||
|
|
||||
|
const regulationNamePlaceholder = computed(() => { |
||||
|
return currentOssId.value ? '可编辑法规名称' : '请先上传法规文件'; |
||||
|
}); |
||||
|
|
||||
|
// 表单验证规则 - 动态规则 |
||||
|
const rules = computed(() => ({ |
||||
|
regulationFile: hasUploadedFile.value ? [] : [{ required: true, message: '请上传法规文件' }], |
||||
|
regulationName: [{ required: true, message: '请输入法规名称' }], |
||||
|
regulationDescription: [{ required: true, message: '请输入法规描述' }], |
||||
|
publishDate: [{ required: true, message: '请选择发布日期' }], |
||||
|
})); |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 监听弹窗关闭,重置表单 |
||||
|
watch(() => props.visible, (newVal) => { |
||||
|
if (!newVal) { |
||||
|
resetForm(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 文件上传相关处理 |
||||
|
function customUploadRequest(options: any) { |
||||
|
const { file, onSuccess, onError } = options; |
||||
|
|
||||
|
uploading.value = true; |
||||
|
uploadPercent.value = 0; |
||||
|
|
||||
|
const uploadParams: UploadFileParams = { |
||||
|
name: 'file', |
||||
|
file: file, |
||||
|
data: {}, |
||||
|
}; |
||||
|
|
||||
|
fileList.value = [ |
||||
|
{ |
||||
|
uid: '1', |
||||
|
name: file.name, |
||||
|
status: 'uploading', |
||||
|
url: URL.createObjectURL(file), |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
uploadApi( |
||||
|
uploadParams, |
||||
|
(progressEvent) => { |
||||
|
if (progressEvent.total) { |
||||
|
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100); |
||||
|
uploadPercent.value = percent; |
||||
|
} |
||||
|
} |
||||
|
).then((res) => { |
||||
|
if (fileList.value && fileList.value.length > 0) { |
||||
|
fileList.value[0].status = 'done'; |
||||
|
fileList.value[0].response = res; |
||||
|
if (res && res.ossId) { |
||||
|
currentOssId.value = res.ossId; |
||||
|
const fileName = file.name; |
||||
|
const nameWithoutExt = fileName.substring(0, fileName.lastIndexOf('.')) || fileName; |
||||
|
formData.regulationName = nameWithoutExt; |
||||
|
|
||||
|
// 文件上传成功后,清除文件字段的验证错误 |
||||
|
formRef.value?.clearValidate('regulationFile'); |
||||
|
} |
||||
|
} |
||||
|
uploading.value = false; |
||||
|
uploadPercent.value = 100; |
||||
|
message.success(`${file.name} 文件上传成功`); |
||||
|
onSuccess(res); |
||||
|
}).catch((err) => { |
||||
|
uploading.value = false; |
||||
|
fileList.value = []; |
||||
|
currentOssId.value = null; |
||||
|
message.error(`文件上传失败: ${err.message || '未知错误'}`); |
||||
|
onError(err); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function beforeUpload(file: File) { |
||||
|
const isDoc = file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' || |
||||
|
file.type === 'application/msword'; |
||||
|
|
||||
|
if (!isDoc) { |
||||
|
message.error('只能上传 DOC/DOCX 格式的文件!'); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const isLt500M = file.size / 1024 / 1024 < 500; |
||||
|
if (!isLt500M) { |
||||
|
message.error('文件大小不能超过 500MB!'); |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
function removeFile() { |
||||
|
if (uploading.value) { |
||||
|
message.warning('文件正在上传中,请稍后再试'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (currentOssId.value) { |
||||
|
ossRemove([currentOssId.value]) |
||||
|
.then(() => { |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
currentOssId.value = null; |
||||
|
formData.regulationName = ''; |
||||
|
message.success('文件已删除'); |
||||
|
}) |
||||
|
.catch((err) => { |
||||
|
message.error(`文件删除失败: ${err.message || '未知错误'}`); |
||||
|
}); |
||||
|
} else { |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
formData.regulationName = ''; |
||||
|
message.success('文件已删除'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 提交表单 |
||||
|
async function handleSubmit() { |
||||
|
try { |
||||
|
await formRef.value?.validate(); |
||||
|
} catch (error) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (!currentOssId.value) { |
||||
|
message.warning('请上传法规文件'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const submitData = { |
||||
|
...formData, |
||||
|
publishDate: formData.publishDate ? formData.publishDate.format('YYYY-MM-DD') : '', |
||||
|
ossId: currentOssId.value |
||||
|
}; |
||||
|
|
||||
|
await ContractualRegulationNamesAdd(submitData); |
||||
|
message.success('新增成功'); |
||||
|
|
||||
|
// TODO: 预留代码空位 - 发起解析API请求 |
||||
|
// await parseRegulationFile(currentOssId.value); |
||||
|
|
||||
|
emit('update:visible', false); |
||||
|
emit('success'); |
||||
|
resetForm(); |
||||
|
} catch (error) { |
||||
|
message.error('新增失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 取消 |
||||
|
function handleCancel() { |
||||
|
emit('update:visible', false); |
||||
|
resetForm(); |
||||
|
} |
||||
|
|
||||
|
// 重置表单 |
||||
|
function resetForm() { |
||||
|
Object.assign(formData, { |
||||
|
regulationName: '', |
||||
|
regulationDescription: '', |
||||
|
publishDate: dayjs(), |
||||
|
isEffective: 'Y', |
||||
|
progressStatus: 'PENDING' |
||||
|
}); |
||||
|
fileList.value = []; |
||||
|
uploadPercent.value = 0; |
||||
|
currentOssId.value = null; |
||||
|
formRef.value?.clearValidate(); |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="less" scoped> |
||||
|
/* 文件上传样式 */ |
||||
|
.upload-box { |
||||
|
border: 1px dashed #ddd; |
||||
|
border-radius: 8px; |
||||
|
padding: 20px; |
||||
|
text-align: center; |
||||
|
cursor: pointer; |
||||
|
transition: border-color 0.3s; |
||||
|
width: 100%; |
||||
|
height: 200px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
background-color: #f9f9f9; |
||||
|
|
||||
|
&:hover { |
||||
|
border-color: #13c2c2; |
||||
|
background-color: rgba(19, 194, 194, 0.02); |
||||
|
} |
||||
|
|
||||
|
&.file-preview { |
||||
|
border: 2px solid #e6fffb; |
||||
|
background-color: #f0f8ff; |
||||
|
cursor: default; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.upload-content { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.upload-icon { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16px; |
||||
|
background-color: #e6fffb; |
||||
|
width: 50px; |
||||
|
height: 50px; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.upload-arrow-icon { |
||||
|
font-size: 24px; |
||||
|
color: #13c2c2; |
||||
|
} |
||||
|
|
||||
|
.upload-text-container { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.upload-text { |
||||
|
font-size: 16px; |
||||
|
color: #333; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.upload-link { |
||||
|
color: #13c2c2; |
||||
|
cursor: pointer; |
||||
|
text-decoration: none; |
||||
|
font-weight: 600; |
||||
|
} |
||||
|
|
||||
|
.upload-link:hover { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.upload-hint { |
||||
|
color: #888; |
||||
|
font-size: 14px; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
|
||||
|
.file-info { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
max-width: 800px; |
||||
|
} |
||||
|
|
||||
|
.file-icon { |
||||
|
font-size: 32px; |
||||
|
color: #13c2c2; |
||||
|
margin-right: 16px; |
||||
|
} |
||||
|
|
||||
|
.file-details { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.file-name { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
margin: 0 0 8px 0; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.file-progress { |
||||
|
margin: 0 0 8px 0; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.file-status { |
||||
|
font-size: 14px; |
||||
|
color: #666; |
||||
|
margin: 0; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.status-icon { |
||||
|
margin-right: 6px; |
||||
|
} |
||||
|
|
||||
|
.status-icon.success { |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
.remove-btn { |
||||
|
padding: 0; |
||||
|
margin-left: 12px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,108 @@ |
|||||
|
import { BasicColumn } from '@/components/Table'; |
||||
|
import { FormSchema } from '@/components/Form'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import { h } from 'vue'; |
||||
|
import { useRender } from '@/hooks/component/useRender'; |
||||
|
|
||||
|
const { renderDict } = useRender(); |
||||
|
|
||||
|
export const formSchemas: FormSchema[] = [ |
||||
|
{ |
||||
|
label: '法规名称', |
||||
|
field: 'regulationName', |
||||
|
component: 'Input', |
||||
|
}, |
||||
|
{ |
||||
|
label: '发布日期', |
||||
|
field: 'publishDate', |
||||
|
component: 'DatePicker', |
||||
|
componentProps: { |
||||
|
showTime: true, |
||||
|
format: 'YYYY-MM-DD', |
||||
|
valueFormat: 'YYYY-MM-DD', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '是否有效', |
||||
|
field: 'isEffective', |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: getDictOptions('sys_yes_no'), |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
|
||||
|
export const columns: BasicColumn[] = [ |
||||
|
{ |
||||
|
title: '主键ID', |
||||
|
dataIndex: 'id', |
||||
|
ifShow: false, |
||||
|
}, |
||||
|
{ |
||||
|
title: '法规名称', |
||||
|
dataIndex: 'regulationName', |
||||
|
}, |
||||
|
{ |
||||
|
title: '发布日期', |
||||
|
dataIndex: 'publishDate', |
||||
|
}, |
||||
|
{ |
||||
|
title: '是否有效', |
||||
|
dataIndex: 'isEffective', |
||||
|
width: 120, |
||||
|
customRender: ({ record }) => { |
||||
|
return h('ASwitch', { |
||||
|
checked: record.isEffective === 'Y', |
||||
|
loading: record._switching, |
||||
|
onClick: () => handleSwitchChange(record) |
||||
|
}); |
||||
|
}, |
||||
|
},{ |
||||
|
title: '当前状态', |
||||
|
dataIndex: 'progressStatus', |
||||
|
customRender: ({ value }) => renderDict(value, 'file_progress_status'), |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
export const modalSchemas: FormSchema[] = [ |
||||
|
{ |
||||
|
label: '主键ID', |
||||
|
field: 'id', |
||||
|
required: false, |
||||
|
component: 'Input', |
||||
|
show: false, |
||||
|
}, |
||||
|
{ |
||||
|
label: '法规名称', |
||||
|
field: 'regulationName', |
||||
|
required: true, |
||||
|
component: 'Input', |
||||
|
componentProps: { |
||||
|
disabled: true, |
||||
|
placeholder: '请先上传法规文件', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '发布日期', |
||||
|
field: 'publishDate', |
||||
|
required: true, |
||||
|
component: 'DatePicker', |
||||
|
componentProps: { |
||||
|
showTime: true, |
||||
|
format: 'YYYY-MM-DD', |
||||
|
valueFormat: 'YYYY-MM-DD', |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
label: '是否有效', |
||||
|
field: 'isEffective', |
||||
|
required: false, |
||||
|
component: 'Select', |
||||
|
componentProps: { |
||||
|
options: getDictOptions('sys_yes_no'), |
||||
|
}, |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
// 声明处理开关变化的函数(需要在使用的组件中实现)
|
||||
|
declare function handleSwitchChange(record: any): void; |
@ -0,0 +1,450 @@ |
|||||
|
<template> |
||||
|
<Drawer |
||||
|
v-model:open="visible" |
||||
|
title="修改合同法规" |
||||
|
width="80%" |
||||
|
:destroyOnClose="true" |
||||
|
class="edit-regulation-drawer" |
||||
|
placement="right" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Space> |
||||
|
<Button @click="handleCancel">取消</Button> |
||||
|
<Button type="primary" @click="handleSubmit" :loading="loading">保存</Button> |
||||
|
</Space> |
||||
|
</template> |
||||
|
|
||||
|
<div class="edit-container"> |
||||
|
<!-- 左侧PDF预览 --> |
||||
|
<div class="pdf-preview-section"> |
||||
|
<div class="section-title"> |
||||
|
<FileTextOutlined /> |
||||
|
<span>法规文档预览</span> |
||||
|
<Button type="link" size="small" @click="refreshPdf" :loading="pdfLoading"> |
||||
|
<ReloadOutlined /> |
||||
|
</Button> |
||||
|
</div> |
||||
|
|
||||
|
<div class="pdf-container"> |
||||
|
<div v-if="pdfLoading" class="loading-container"> |
||||
|
<Spin size="large" tip="正在加载文档..." /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="pdfError" class="error-container"> |
||||
|
<Result |
||||
|
status="error" |
||||
|
:title="pdfError" |
||||
|
sub-title="请检查网络连接或联系管理员" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Button type="primary" @click="refreshPdf">重新加载</Button> |
||||
|
</template> |
||||
|
</Result> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="pdfUrl" class="pdf-embed-container"> |
||||
|
<embed |
||||
|
:src="pdfUrl" |
||||
|
type="application/pdf" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
/> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="no-pdf-container"> |
||||
|
<Empty description="暂无PDF文档" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 右侧修改表单 --> |
||||
|
<div class="edit-form-section"> |
||||
|
<div class="section-title"> |
||||
|
<EditOutlined /> |
||||
|
<span>修改信息</span> |
||||
|
</div> |
||||
|
|
||||
|
<div class="form-container"> |
||||
|
<Form |
||||
|
ref="formRef" |
||||
|
:model="formData" |
||||
|
:rules="rules" |
||||
|
layout="vertical" |
||||
|
class="edit-form" |
||||
|
> |
||||
|
<!-- 法规名称 --> |
||||
|
<FormItem label="法规名称" name="regulationName" required> |
||||
|
<Input |
||||
|
v-model:value="formData.regulationName" |
||||
|
placeholder="请输入法规名称" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 法规描述 --> |
||||
|
<FormItem label="法规描述" name="regulationDescription" required> |
||||
|
<TextArea |
||||
|
v-model:value="formData.regulationDescription" |
||||
|
placeholder="请输入法规描述" |
||||
|
:rows="6" |
||||
|
show-count |
||||
|
:maxlength="500" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 发布日期 --> |
||||
|
<FormItem label="发布日期" name="publishDate" required> |
||||
|
<DatePicker |
||||
|
v-model:value="formData.publishDate" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 100%" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
|
||||
|
<!-- 是否有效 --> |
||||
|
<FormItem label="是否有效" name="isEffective"> |
||||
|
<Select |
||||
|
v-model:value="formData.isEffective" |
||||
|
:options="effectiveOptions" |
||||
|
size="large" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, computed, watch, onUnmounted } from 'vue'; |
||||
|
import { |
||||
|
Drawer, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Button, |
||||
|
Space, |
||||
|
Spin, |
||||
|
Result, |
||||
|
Empty, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
FileTextOutlined, |
||||
|
EditOutlined, |
||||
|
ReloadOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualRegulationNamesInfo, |
||||
|
ContractualRegulationNamesUpdate, |
||||
|
ContractualRegulationNamesViewPdf |
||||
|
} from '@/api/contractReview/ContractualRegulationNames'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import dayjs from 'dayjs'; |
||||
|
|
||||
|
const { TextArea } = Input; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
regulationId?: number | string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
visible: false, |
||||
|
regulationId: undefined |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
'update:visible': [value: boolean]; |
||||
|
'success': []; |
||||
|
}>(); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const pdfLoading = ref(false); |
||||
|
const pdfError = ref(''); |
||||
|
const pdfUrl = ref(''); |
||||
|
const formRef = ref(); |
||||
|
|
||||
|
// 表单数据 |
||||
|
const formData = reactive({ |
||||
|
id: undefined as string | number | undefined, |
||||
|
regulationName: '', |
||||
|
regulationDescription: '', |
||||
|
publishDate: '', |
||||
|
isEffective: 'Y' |
||||
|
}); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const visible = computed({ |
||||
|
get: () => props.visible, |
||||
|
set: (value) => emit('update:visible', value) |
||||
|
}); |
||||
|
|
||||
|
// 表单验证规则 |
||||
|
const rules = computed(() => ({ |
||||
|
regulationName: [{ required: true, message: '请输入法规名称', trigger: 'blur' }], |
||||
|
regulationDescription: [{ required: true, message: '请输入法规描述', trigger: 'blur' }], |
||||
|
publishDate: [{ required: true, message: '请选择发布日期', trigger: 'change' }], |
||||
|
})); |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 监听弹窗显示 |
||||
|
watch(() => props.visible, (newVisible) => { |
||||
|
if (newVisible && props.regulationId) { |
||||
|
loadRegulationData(); |
||||
|
loadPdf(); |
||||
|
} else if (!newVisible) { |
||||
|
resetForm(); |
||||
|
cleanupPdfUrl(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 加载法规数据 |
||||
|
async function loadRegulationData() { |
||||
|
if (!props.regulationId) return; |
||||
|
|
||||
|
try { |
||||
|
const result = await ContractualRegulationNamesInfo(props.regulationId); |
||||
|
if (result) { |
||||
|
formData.id = result.id; |
||||
|
formData.regulationName = result.regulationName || ''; |
||||
|
formData.regulationDescription = result.regulationDescription || ''; |
||||
|
formData.publishDate = result.publishDate || ''; |
||||
|
formData.isEffective = result.isEffective || 'Y'; |
||||
|
} |
||||
|
} catch (error: any) { |
||||
|
message.error('加载法规信息失败: ' + (error.message || '未知错误')); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 加载PDF |
||||
|
async function loadPdf() { |
||||
|
if (!props.regulationId) return; |
||||
|
|
||||
|
pdfLoading.value = true; |
||||
|
pdfError.value = ''; |
||||
|
|
||||
|
try { |
||||
|
const pdfResponse = await ContractualRegulationNamesViewPdf(props.regulationId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
// 清理之前的URL |
||||
|
cleanupPdfUrl(); |
||||
|
|
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
} else { |
||||
|
pdfError.value = 'PDF数据格式错误'; |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
pdfError.value = err.message || '加载PDF失败'; |
||||
|
console.error('加载PDF失败:', err); |
||||
|
} finally { |
||||
|
pdfLoading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 刷新PDF |
||||
|
function refreshPdf() { |
||||
|
loadPdf(); |
||||
|
} |
||||
|
|
||||
|
// 提交表单 |
||||
|
async function handleSubmit() { |
||||
|
try { |
||||
|
await formRef.value?.validate(); |
||||
|
} catch (error) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const submitData = { |
||||
|
...formData, |
||||
|
publishDate: formData.publishDate ? dayjs(formData.publishDate).format('YYYY-MM-DD') : '' |
||||
|
}; |
||||
|
|
||||
|
await ContractualRegulationNamesUpdate(submitData); |
||||
|
message.success('修改成功'); |
||||
|
|
||||
|
emit('update:visible', false); |
||||
|
emit('success'); |
||||
|
} catch (error: any) { |
||||
|
message.error('修改失败: ' + (error.message || '未知错误')); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 取消 |
||||
|
function handleCancel() { |
||||
|
emit('update:visible', false); |
||||
|
} |
||||
|
|
||||
|
// 重置表单 |
||||
|
function resetForm() { |
||||
|
Object.assign(formData, { |
||||
|
id: undefined, |
||||
|
regulationName: '', |
||||
|
regulationDescription: '', |
||||
|
publishDate: '', |
||||
|
isEffective: 'Y' |
||||
|
}); |
||||
|
formRef.value?.clearValidate(); |
||||
|
pdfError.value = ''; |
||||
|
} |
||||
|
|
||||
|
// 清理Blob URL |
||||
|
function cleanupPdfUrl() { |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
pdfUrl.value = ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件销毁时清理 |
||||
|
onUnmounted(() => { |
||||
|
cleanupPdfUrl(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.edit-regulation-drawer { |
||||
|
:deep(.ant-drawer-content) { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-drawer-body) { |
||||
|
padding: 0; |
||||
|
height: calc(100vh - 55px); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.edit-container { |
||||
|
display: flex; |
||||
|
height: 100%; |
||||
|
gap: 1px; |
||||
|
background: #f0f2f5; |
||||
|
} |
||||
|
|
||||
|
.pdf-preview-section { |
||||
|
flex: 1; |
||||
|
background: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.edit-form-section { |
||||
|
width: 400px; |
||||
|
background: #fff; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
border-left: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
padding: 16px 20px; |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
color: #262626; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
gap: 8px; |
||||
|
background: #fafafa; |
||||
|
|
||||
|
span { |
||||
|
flex: 1; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
background: #f5f5f5; |
||||
|
|
||||
|
.loading-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.error-container { |
||||
|
padding: 40px; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.pdf-embed-container { |
||||
|
height: 100%; |
||||
|
padding: 16px; |
||||
|
|
||||
|
embed { |
||||
|
border-radius: 6px; |
||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.no-pdf-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.form-container { |
||||
|
flex: 1; |
||||
|
padding: 20px; |
||||
|
overflow-y: auto; |
||||
|
} |
||||
|
|
||||
|
.edit-form { |
||||
|
.ant-form-item { |
||||
|
margin-bottom: 24px; |
||||
|
} |
||||
|
|
||||
|
.ant-form-item-label > label { |
||||
|
font-weight: 500; |
||||
|
color: #262626; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 1400px) { |
||||
|
.edit-form-section { |
||||
|
width: 380px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 1200px) { |
||||
|
.edit-container { |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.pdf-preview-section { |
||||
|
height: 50%; |
||||
|
} |
||||
|
|
||||
|
.edit-form-section { |
||||
|
width: 100%; |
||||
|
height: 50%; |
||||
|
border-left: none; |
||||
|
border-top: 1px solid #e8e8e8; |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,382 @@ |
|||||
|
<template> |
||||
|
<Drawer |
||||
|
v-model:open="visible" |
||||
|
:title="modalTitle" |
||||
|
width="70%" |
||||
|
:destroyOnClose="true" |
||||
|
class="regulation-view-drawer" |
||||
|
placement="right" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Space> |
||||
|
<Button type="primary" @click="downloadPdf" :loading="downloading"> |
||||
|
<template #icon><DownloadOutlined /></template> |
||||
|
下载PDF |
||||
|
</Button> |
||||
|
<Button @click="refreshPdf"> |
||||
|
<template #icon><ReloadOutlined /></template> |
||||
|
刷新 |
||||
|
</Button> |
||||
|
</Space> |
||||
|
</template> |
||||
|
|
||||
|
<div class="regulation-view-container"> |
||||
|
<!-- PDF预览区域 --> |
||||
|
<div class="pdf-container"> |
||||
|
<div v-if="loading" class="loading-container"> |
||||
|
<Spin size="large" tip="正在加载法规文档..." /> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else-if="error" class="error-container"> |
||||
|
<Result |
||||
|
status="error" |
||||
|
:title="error" |
||||
|
sub-title="请检查网络连接或联系管理员" |
||||
|
> |
||||
|
<template #extra> |
||||
|
<Button type="primary" @click="refreshPdf">重新加载</Button> |
||||
|
</template> |
||||
|
</Result> |
||||
|
</div> |
||||
|
|
||||
|
<div v-else class="pdf-embed-container"> |
||||
|
<embed |
||||
|
:src="pdfUrl" |
||||
|
type="application/pdf" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
/> |
||||
|
<!-- 备选方案:如果embed不支持,显示iframe --> |
||||
|
<iframe |
||||
|
v-if="showIframe" |
||||
|
:src="pdfUrl" |
||||
|
width="100%" |
||||
|
height="100%" |
||||
|
style="border: none; display: block;" |
||||
|
></iframe> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 条款列表(备选展示方式) --> |
||||
|
<div v-if="showArticlesList" class="articles-list"> |
||||
|
<Divider>法规条款详情</Divider> |
||||
|
<div v-for="(article, index) in articles" :key="article.id" class="article-item"> |
||||
|
<div class="article-title">第{{ index + 1 }}条</div> |
||||
|
<div class="article-content">{{ article.articleContent }}</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Drawer> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, computed, watch, onUnmounted } from 'vue'; |
||||
|
import { |
||||
|
Drawer, |
||||
|
Button, |
||||
|
Space, |
||||
|
Spin, |
||||
|
Result, |
||||
|
Divider, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
DownloadOutlined, |
||||
|
ReloadOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualRegulationNamesViewPdf, |
||||
|
ContractualRegulationNamesArticles |
||||
|
} from '@/api/contractReview/ContractualRegulationNames'; |
||||
|
|
||||
|
interface Props { |
||||
|
visible: boolean; |
||||
|
regulationId?: number | string; |
||||
|
regulationName?: string; |
||||
|
} |
||||
|
|
||||
|
const props = withDefaults(defineProps<Props>(), { |
||||
|
visible: false, |
||||
|
regulationId: undefined, |
||||
|
regulationName: '' |
||||
|
}); |
||||
|
|
||||
|
const emit = defineEmits<{ |
||||
|
'update:visible': [value: boolean]; |
||||
|
}>(); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const downloading = ref(false); |
||||
|
const error = ref(''); |
||||
|
const articles = ref<any[]>([]); |
||||
|
const pdfUrl = ref(''); |
||||
|
const showIframe = ref(false); |
||||
|
const showArticlesList = ref(false); |
||||
|
|
||||
|
// 计算属性 |
||||
|
const visible = computed({ |
||||
|
get: () => props.visible, |
||||
|
set: (value) => emit('update:visible', value) |
||||
|
}); |
||||
|
|
||||
|
const modalTitle = computed(() => { |
||||
|
return props.regulationName ? `法规详情 - ${props.regulationName}` : '法规详情'; |
||||
|
}); |
||||
|
|
||||
|
// 监听弹窗显示 |
||||
|
watch(() => props.visible, (newVisible) => { |
||||
|
if (newVisible && props.regulationId) { |
||||
|
loadRegulationDetail(); |
||||
|
} else if (!newVisible) { |
||||
|
// 弹窗关闭时清理资源 |
||||
|
cleanupPdfUrl(); |
||||
|
error.value = ''; |
||||
|
showIframe.value = false; |
||||
|
showArticlesList.value = false; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 加载法规详情 |
||||
|
async function loadRegulationDetail() { |
||||
|
if (!props.regulationId) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
error.value = ''; |
||||
|
|
||||
|
try { |
||||
|
// 并行加载PDF和条款列表 |
||||
|
const [pdfResponse, articlesResult] = await Promise.all([ |
||||
|
ContractualRegulationNamesViewPdf(props.regulationId), |
||||
|
ContractualRegulationNamesArticles(props.regulationId) |
||||
|
]); |
||||
|
|
||||
|
console.log('PDF响应:', pdfResponse); |
||||
|
|
||||
|
// 处理PDF Blob |
||||
|
if (pdfResponse?.data) { |
||||
|
console.log('PDF加载成功, 创建Blob'); |
||||
|
|
||||
|
// 清理之前的URL |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
} |
||||
|
|
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
console.log('PDF URL创建成功:', pdfUrl.value); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
|
||||
|
// 处理条款列表 |
||||
|
articles.value = articlesResult || []; |
||||
|
|
||||
|
} catch (err: any) { |
||||
|
error.value = err.message || '加载法规详情失败'; |
||||
|
console.error('加载法规详情失败:', err); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 刷新PDF |
||||
|
async function refreshPdf() { |
||||
|
if (!props.regulationId) return; |
||||
|
|
||||
|
loading.value = true; |
||||
|
error.value = ''; |
||||
|
|
||||
|
try { |
||||
|
const pdfResponse = await ContractualRegulationNamesViewPdf(props.regulationId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
// 清理之前的URL |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
} |
||||
|
// 创建新的Blob URL |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
pdfUrl.value = URL.createObjectURL(blob); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
error.value = err.message || '刷新PDF失败'; |
||||
|
console.error('刷新PDF失败:', err); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 下载PDF |
||||
|
async function downloadPdf() { |
||||
|
if (!props.regulationId) return; |
||||
|
|
||||
|
downloading.value = true; |
||||
|
try { |
||||
|
const pdfResponse = await ContractualRegulationNamesViewPdf(props.regulationId); |
||||
|
|
||||
|
if (pdfResponse?.data) { |
||||
|
const blob = new Blob([pdfResponse.data], { type: 'application/pdf' }); |
||||
|
const url = URL.createObjectURL(blob); |
||||
|
const link = document.createElement('a'); |
||||
|
link.href = url; |
||||
|
link.download = `${props.regulationName || '法规文档'}.pdf`; |
||||
|
document.body.appendChild(link); |
||||
|
link.click(); |
||||
|
document.body.removeChild(link); |
||||
|
|
||||
|
// 清理临时URL |
||||
|
URL.revokeObjectURL(url); |
||||
|
message.success('PDF下载已开始'); |
||||
|
} else { |
||||
|
throw new Error('PDF数据格式错误'); |
||||
|
} |
||||
|
} catch (err: any) { |
||||
|
message.error('下载失败: ' + (err.message || '未知错误')); |
||||
|
console.error('下载PDF失败:', err); |
||||
|
} finally { |
||||
|
downloading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 切换显示模式 |
||||
|
function toggleDisplayMode() { |
||||
|
showArticlesList.value = !showArticlesList.value; |
||||
|
} |
||||
|
|
||||
|
// PDF加载错误处理 |
||||
|
function handlePdfError() { |
||||
|
showIframe.value = true; |
||||
|
} |
||||
|
|
||||
|
// 清理Blob URL |
||||
|
function cleanupPdfUrl() { |
||||
|
if (pdfUrl.value && pdfUrl.value.startsWith('blob:')) { |
||||
|
URL.revokeObjectURL(pdfUrl.value); |
||||
|
pdfUrl.value = ''; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 组件销毁时清理 |
||||
|
onUnmounted(() => { |
||||
|
cleanupPdfUrl(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.regulation-view-drawer { |
||||
|
:deep(.ant-drawer-content) { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-drawer-body) { |
||||
|
padding: 0; |
||||
|
height: calc(100vh - 55px); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.regulation-view-container { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
padding: 16px; |
||||
|
} |
||||
|
|
||||
|
.toolbar { |
||||
|
margin-bottom: 16px; |
||||
|
padding: 12px; |
||||
|
background: #fafafa; |
||||
|
border-radius: 6px; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
flex-shrink: 0; |
||||
|
} |
||||
|
|
||||
|
.pdf-container { |
||||
|
flex: 1; |
||||
|
position: relative; |
||||
|
min-height: 0; |
||||
|
|
||||
|
.loading-container { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.error-container { |
||||
|
padding: 40px; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
|
||||
|
.pdf-embed-container { |
||||
|
border: 1px solid #d9d9d9; |
||||
|
border-radius: 6px; |
||||
|
overflow: hidden; |
||||
|
height: 100%; |
||||
|
|
||||
|
embed, iframe { |
||||
|
display: block; |
||||
|
height: 100% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.articles-list { |
||||
|
margin-top: 24px; |
||||
|
flex-shrink: 0; |
||||
|
max-height: 300px; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.article-item { |
||||
|
margin-bottom: 24px; |
||||
|
padding: 16px; |
||||
|
border: 1px solid #e8e8e8; |
||||
|
border-radius: 6px; |
||||
|
background: #fafafa; |
||||
|
|
||||
|
.article-title { |
||||
|
font-weight: bold; |
||||
|
font-size: 16px; |
||||
|
color: #262626; |
||||
|
margin-bottom: 12px; |
||||
|
padding-bottom: 8px; |
||||
|
border-bottom: 1px solid #e8e8e8; |
||||
|
} |
||||
|
|
||||
|
.article-content { |
||||
|
line-height: 1.8; |
||||
|
color: #595959; |
||||
|
text-align: justify; |
||||
|
white-space: pre-wrap; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* 响应式设计 */ |
||||
|
@media (max-width: 1200px) { |
||||
|
.regulation-view-drawer { |
||||
|
:deep(.ant-drawer) { |
||||
|
width: 80% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media (max-width: 768px) { |
||||
|
.regulation-view-drawer { |
||||
|
:deep(.ant-drawer) { |
||||
|
width: 90% !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</style> |
@ -0,0 +1,442 @@ |
|||||
|
<template> |
||||
|
<PageWrapper dense> |
||||
|
<!-- 搜索表单 --> |
||||
|
<Card class="search-card"> |
||||
|
<Form |
||||
|
:model="searchForm" |
||||
|
layout="inline" |
||||
|
class="search-form" |
||||
|
> |
||||
|
<FormItem label="法规名称"> |
||||
|
<Input |
||||
|
v-model:value="searchForm.regulationName" |
||||
|
placeholder="请输入法规名称" |
||||
|
style="width: 200px" |
||||
|
allowClear |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem label="发布日期"> |
||||
|
<RangePicker |
||||
|
v-model:value="searchForm.publishDateRange" |
||||
|
format="YYYY-MM-DD" |
||||
|
valueFormat="YYYY-MM-DD" |
||||
|
style="width: 240px" |
||||
|
allowClear |
||||
|
:placeholder="['开始日期', '结束日期']" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem label="是否有效"> |
||||
|
<Select |
||||
|
v-model:value="searchForm.isEffective" |
||||
|
placeholder="请选择状态" |
||||
|
style="width: 120px" |
||||
|
allowClear |
||||
|
:options="effectiveOptions" |
||||
|
/> |
||||
|
</FormItem> |
||||
|
<FormItem> |
||||
|
<Space> |
||||
|
<Button type="primary" @click="handleSearch">查询</Button> |
||||
|
<Button @click="resetSearch">重置</Button> |
||||
|
</Space> |
||||
|
</FormItem> |
||||
|
</Form> |
||||
|
</Card> |
||||
|
|
||||
|
<!-- 表格区域 --> |
||||
|
<Card class="table-card"> |
||||
|
<!-- 表格标题和操作按钮 --> |
||||
|
<div class="table-header"> |
||||
|
<div class="table-title">合同法规文件列表</div> |
||||
|
<div class="table-actions"> |
||||
|
<Space> |
||||
|
<Button |
||||
|
type="primary" |
||||
|
danger |
||||
|
@click="multipleRemove" |
||||
|
:disabled="!selectedRowKeys.length" |
||||
|
v-auth="'productManagement:ContractualRegulationNames:remove'" |
||||
|
> |
||||
|
删除 |
||||
|
</Button> |
||||
|
<Button type="primary" @click="handleAdd" v-auth="'productManagement:ContractualRegulationNames:add'"> |
||||
|
新增 |
||||
|
</Button> |
||||
|
<Button @click="handleRefresh"> |
||||
|
<template #icon><ReloadOutlined /></template> |
||||
|
</Button> |
||||
|
</Space> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 数据表格 --> |
||||
|
<Table |
||||
|
:dataSource="dataSource" |
||||
|
:columns="tableColumns" |
||||
|
:loading="loading" |
||||
|
:pagination="pagination" |
||||
|
:rowSelection="rowSelection" |
||||
|
rowKey="id" |
||||
|
@change="handleTableChange" |
||||
|
size="middle" |
||||
|
/> |
||||
|
</Card> |
||||
|
|
||||
|
<!-- 新增弹窗 --> |
||||
|
<AddModal |
||||
|
v-model:visible="modalVisible" |
||||
|
@success="loadData" |
||||
|
/> |
||||
|
|
||||
|
<!-- 查看弹窗 --> |
||||
|
<ViewModal |
||||
|
v-model:visible="viewModalVisible" |
||||
|
:regulationId="selectedRecord?.id" |
||||
|
:regulationName="selectedRecord?.regulationName" |
||||
|
/> |
||||
|
|
||||
|
<!-- 修改弹窗 --> |
||||
|
<EditModal |
||||
|
v-model:visible="editModalVisible" |
||||
|
:regulationId="selectedRecord?.id" |
||||
|
@success="loadData" |
||||
|
/> |
||||
|
</PageWrapper> |
||||
|
</template> |
||||
|
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, reactive, onMounted, h } from 'vue'; |
||||
|
import { PageWrapper } from '@/components/Page'; |
||||
|
import { |
||||
|
Table, |
||||
|
Button, |
||||
|
Space, |
||||
|
Card, |
||||
|
Form, |
||||
|
FormItem, |
||||
|
Input, |
||||
|
DatePicker, |
||||
|
Select, |
||||
|
Switch, |
||||
|
Modal, |
||||
|
message |
||||
|
} from 'ant-design-vue'; |
||||
|
import { |
||||
|
ReloadOutlined, |
||||
|
EyeOutlined, |
||||
|
DeleteOutlined, |
||||
|
EditOutlined |
||||
|
} from '@ant-design/icons-vue'; |
||||
|
import { |
||||
|
ContractualRegulationNamesList, |
||||
|
ContractualRegulationNamesRemove, |
||||
|
ContractualRegulationNamesUpdateStatus |
||||
|
} from '@/api/contractReview/ContractualRegulationNames'; |
||||
|
import { getDictOptions } from '@/utils/dict'; |
||||
|
import AddModal from './AddModal.vue'; |
||||
|
import ViewModal from './ViewModal.vue'; |
||||
|
import EditModal from './EditModal.vue'; |
||||
|
const { RangePicker } = DatePicker; |
||||
|
import { useRender } from '@/hooks/component/useRender'; |
||||
|
|
||||
|
const { renderDict } = useRender(); |
||||
|
defineOptions({ name: 'ContractualRegulationNames' }); |
||||
|
|
||||
|
// 状态变量 |
||||
|
const loading = ref(false); |
||||
|
const dataSource = ref<any[]>([]); |
||||
|
const selectedRowKeys = ref<any[]>([]); |
||||
|
const modalVisible = ref(false); |
||||
|
const viewModalVisible = ref(false); |
||||
|
const editModalVisible = ref(false); |
||||
|
const selectedRecord = ref<any>(null); |
||||
|
const searchForm = reactive({ |
||||
|
regulationName: '', |
||||
|
publishDateRange: null as any, |
||||
|
isEffective: undefined as string | undefined |
||||
|
}); |
||||
|
|
||||
|
// 分页配置 |
||||
|
const pagination = reactive({ |
||||
|
current: 1, |
||||
|
pageSize: 10, |
||||
|
total: 0, |
||||
|
showSizeChanger: true, |
||||
|
showQuickJumper: true, |
||||
|
showTotal: (total: number) => `共 ${total} 条数据`, |
||||
|
}); |
||||
|
|
||||
|
// 行选择配置 |
||||
|
const rowSelection = { |
||||
|
selectedRowKeys: selectedRowKeys.value, |
||||
|
onChange: (keys: any[]) => { |
||||
|
selectedRowKeys.value = keys; |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
// 字典选项 |
||||
|
const effectiveOptions = getDictOptions('sys_yes_no'); |
||||
|
|
||||
|
// 开关状态切换 |
||||
|
async function handleSwitchChange(record: any) { |
||||
|
if (record._switching) return; |
||||
|
|
||||
|
record._switching = true; |
||||
|
try { |
||||
|
const newStatus = record.isEffective === 'Y' ? 'N' : 'Y'; |
||||
|
await ContractualRegulationNamesUpdateStatus(record.id, newStatus); |
||||
|
record.isEffective = newStatus; |
||||
|
message.success('状态更新成功'); |
||||
|
} catch (error) { |
||||
|
message.error('状态更新失败'); |
||||
|
} finally { |
||||
|
record._switching = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表格列配置 - 保持原本字段 |
||||
|
const tableColumns = [ |
||||
|
{ |
||||
|
title: '法规名称', |
||||
|
dataIndex: 'regulationName', |
||||
|
key: 'regulationName', |
||||
|
width: 180, |
||||
|
}, |
||||
|
{ |
||||
|
title: '法规描述', |
||||
|
dataIndex: 'regulationDescription', |
||||
|
key: 'regulationDescription', |
||||
|
width: 200, |
||||
|
ellipsis: true, |
||||
|
}, |
||||
|
{ |
||||
|
title: '发布日期', |
||||
|
dataIndex: 'publishDate', |
||||
|
key: 'publishDate', |
||||
|
width: 150, |
||||
|
}, |
||||
|
{ |
||||
|
title: '是否有效', |
||||
|
dataIndex: 'isEffective', |
||||
|
key: 'isEffective', |
||||
|
width: 120, |
||||
|
customRender: ({ record }: any) => { |
||||
|
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px' } }, [ |
||||
|
h(Switch, { |
||||
|
checked: record.isEffective === 'Y', |
||||
|
loading: record._switching, |
||||
|
onChange: () => handleSwitchChange(record) |
||||
|
}), |
||||
|
h('span', {}, record.isEffective === 'Y' ? '有效' : '无效') |
||||
|
]); |
||||
|
}, |
||||
|
}, |
||||
|
{ |
||||
|
title: '当前状态', |
||||
|
dataIndex: 'progressStatus', |
||||
|
key: 'progressStatus', |
||||
|
width: 120, |
||||
|
customRender: ({ value }) => renderDict(value, 'file_progress_status'), |
||||
|
|
||||
|
}, |
||||
|
{ |
||||
|
title: '操作', |
||||
|
key: 'action', |
||||
|
fixed: 'right' as const, |
||||
|
width: 180, |
||||
|
customRender: ({ record }: any) => { |
||||
|
return h(Space, {}, [ |
||||
|
h(Button, { |
||||
|
type: 'primary', |
||||
|
size: 'small', |
||||
|
icon: h(EyeOutlined), |
||||
|
onClick: () => handleView(record) |
||||
|
}, '查看'), |
||||
|
h(Button, { |
||||
|
type: 'default', |
||||
|
size: 'small', |
||||
|
icon: h(EditOutlined), |
||||
|
onClick: () => handleEdit(record) |
||||
|
}, '修改'), |
||||
|
h(Button, { |
||||
|
type: 'primary', |
||||
|
danger: true, |
||||
|
size: 'small', |
||||
|
icon: h(DeleteOutlined), |
||||
|
onClick: () => handleDelete(record) |
||||
|
}, '删除') |
||||
|
]); |
||||
|
}, |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
// 过滤空值参数 |
||||
|
function filterEmptyParams(params: any) { |
||||
|
const filteredParams: any = {}; |
||||
|
Object.keys(params).forEach(key => { |
||||
|
const value = params[key]; |
||||
|
if (value !== null && value !== undefined && value !== '') { |
||||
|
filteredParams[key] = value; |
||||
|
} |
||||
|
}); |
||||
|
return filteredParams; |
||||
|
} |
||||
|
|
||||
|
// 加载数据 |
||||
|
async function loadData() { |
||||
|
loading.value = true; |
||||
|
try { |
||||
|
const baseParams: any = { |
||||
|
regulationName: searchForm.regulationName, |
||||
|
isEffective: searchForm.isEffective, |
||||
|
pageNum: pagination.current, |
||||
|
pageSize: pagination.pageSize, |
||||
|
}; |
||||
|
|
||||
|
// 处理日期范围参数 |
||||
|
if (searchForm.publishDateRange && searchForm.publishDateRange.length === 2) { |
||||
|
baseParams.publishDateStart = searchForm.publishDateRange[0]; |
||||
|
baseParams.publishDateEnd = searchForm.publishDateRange[1]; |
||||
|
} |
||||
|
|
||||
|
// 过滤空值参数 |
||||
|
const params = filterEmptyParams(baseParams); |
||||
|
|
||||
|
const result: any = await ContractualRegulationNamesList(params); |
||||
|
dataSource.value = result.rows || []; |
||||
|
pagination.total = result.total || 0; |
||||
|
} catch (error) { |
||||
|
message.error('加载数据失败'); |
||||
|
} finally { |
||||
|
loading.value = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 表格变化处理 |
||||
|
function handleTableChange(pag: any) { |
||||
|
pagination.current = pag.current; |
||||
|
pagination.pageSize = pag.pageSize; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 搜索处理 |
||||
|
function handleSearch() { |
||||
|
pagination.current = 1; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 重置搜索 |
||||
|
function resetSearch() { |
||||
|
Object.assign(searchForm, { |
||||
|
regulationName: '', |
||||
|
publishDateRange: null, |
||||
|
isEffective: undefined |
||||
|
}); |
||||
|
pagination.current = 1; |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 刷新 |
||||
|
function handleRefresh() { |
||||
|
loadData(); |
||||
|
} |
||||
|
|
||||
|
// 新增 |
||||
|
function handleAdd() { |
||||
|
modalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 查看详情 |
||||
|
function handleView(record: any) { |
||||
|
selectedRecord.value = record; |
||||
|
viewModalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 修改法规 |
||||
|
function handleEdit(record: any) { |
||||
|
selectedRecord.value = record; |
||||
|
editModalVisible.value = true; |
||||
|
} |
||||
|
|
||||
|
// 单个删除 |
||||
|
async function handleDelete(record: any) { |
||||
|
Modal.confirm({ |
||||
|
title: '删除确认', |
||||
|
content: `是否删除《${record.regulationName}》?`, |
||||
|
onOk: async () => { |
||||
|
try { |
||||
|
await ContractualRegulationNamesRemove([record.id]); |
||||
|
message.success('删除成功'); |
||||
|
loadData(); |
||||
|
} catch (error) { |
||||
|
message.error('删除失败'); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 批量删除 |
||||
|
async function multipleRemove() { |
||||
|
if (!selectedRowKeys.value.length) { |
||||
|
message.warning('请选择要删除的记录'); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const selectedRecords = dataSource.value.filter((item: any) => |
||||
|
selectedRowKeys.value.includes(item.id) |
||||
|
); |
||||
|
const regulationNames = selectedRecords.map((item: any) => item.regulationName).join('、'); |
||||
|
|
||||
|
Modal.confirm({ |
||||
|
title: '删除确认', |
||||
|
content: `是否删除《${regulationNames}》?`, |
||||
|
onOk: async () => { |
||||
|
try { |
||||
|
await ContractualRegulationNamesRemove(selectedRowKeys.value); |
||||
|
message.success('删除成功'); |
||||
|
selectedRowKeys.value = []; |
||||
|
loadData(); |
||||
|
} catch (error) { |
||||
|
message.error('删除失败'); |
||||
|
} |
||||
|
}, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// 页面加载时获取数据 |
||||
|
onMounted(() => { |
||||
|
loadData(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
.search-card { |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.search-form { |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
.table-card { |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.table-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.table-title { |
||||
|
font-size: 16px; |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
:deep(.ant-table) { |
||||
|
.ant-table-tbody > tr > td { |
||||
|
padding: 12px 8px; |
||||
|
} |
||||
|
} |
||||
|
</style> |
Loading…
Reference in new issue