Browse Source

新增jyj合同审批

jyj_dev
zhouhaibin 2 months ago
parent
commit
af29a66f05
  1. 4
      package.json
  2. 57
      src/api/contractReview/ContractualInfo/index.ts
  3. 90
      src/api/contractReview/ContractualInfo/model.ts
  4. 103
      src/api/contractReview/JyjcontractualTaskBatch/index.ts
  5. 160
      src/api/contractReview/JyjcontractualTaskBatch/model.ts
  6. 22
      src/components/Upload/src/components/UploadModal.vue
  7. 2
      src/enums/pageEnum.ts
  8. 3
      src/views/CanvasEditor/style.css
  9. 85
      src/views/contractReview/ContractualInfo/ContractualInfo.data.ts
  10. 68
      src/views/contractReview/ContractualInfo/ContractualInfoModal.vue
  11. 115
      src/views/contractReview/ContractualInfo/index.vue
  12. 264
      src/views/contractReview/ContractualTasks/ContractualShowModal.vue
  13. 123
      src/views/contractReview/ContractualTasks/ContractualTasks.data.ts
  14. 208
      src/views/contractReview/ContractualTasks/ContractualTasksTable.vue
  15. 127
      src/views/contractReview/ContractualTasks/PdfViewer.vue
  16. 115
      src/views/contractReview/ContractualTasks/index.vue
  17. 149
      src/views/contractReview/ContractualTasks/showResultCard.vue
  18. 71
      src/views/contractReview/JyjcontractualTaskBatch/ContractualTasksTableModal.vue
  19. 109
      src/views/contractReview/JyjcontractualTaskBatch/JyjcontractualTaskBatch.data.ts
  20. 74
      src/views/contractReview/JyjcontractualTaskBatch/JyjcontractualTaskBatchModal.vue
  21. 200
      src/views/contractReview/JyjcontractualTaskBatch/index.vue
  22. 251
      src/views/workbench/index copy.vue
  23. 115
      src/views/workbench/index.vue

4
package.json

@ -186,8 +186,8 @@
"md-editor-v3": "^4.9.0",
"markdown-it": "^13.0.2",
"markdown-it-anchor": "^8.6.7",
"markdown-it-toc-done-right": "^4.2.0"
"markdown-it-toc-done-right": "^4.2.0",
"@vue-office/pdf": "^2.0.10"
},
"engines": {
"node": ">=18.12.0",

57
src/api/contractReview/ContractualInfo/index.ts

@ -0,0 +1,57 @@
import { defHttp } from '@/utils/http/axios';
import { ID, IDS, commonExport } from '@/api/base';
import { ContractualInfoVO, ContractualInfoForm, ContractualInfoQuery } from './model';
/**
*
* @param params
* @returns
*/
export function ContractualInfoList(params?: ContractualInfoQuery) {
return defHttp.get<ContractualInfoVO[]>({ url: '/productManagement/ContractualInfo/list', params });
}
/**
*
* @param params
* @returns
*/
export function ContractualInfoExport(params?: ContractualInfoQuery) {
return commonExport('/productManagement/ContractualInfo/export', params ?? {});
}
/**
*
* @param id id
* @returns
*/
export function ContractualInfoInfo(id: ID) {
return defHttp.get<ContractualInfoVO>({ url: '/productManagement/ContractualInfo/' + id });
}
/**
*
* @param data
* @returns
*/
export function ContractualInfoAdd(data: ContractualInfoForm) {
return defHttp.postWithMsg<void>({ url: '/productManagement/ContractualInfo', data });
}
/**
*
* @param data
* @returns
*/
export function ContractualInfoUpdate(data: ContractualInfoForm) {
return defHttp.putWithMsg<void>({ url: '/productManagement/ContractualInfo', data });
}
/**
*
* @param id id
* @returns
*/
export function ContractualInfoRemove(id: ID | IDS) {
return defHttp.deleteWithMsg<void>({ url: '/productManagement/ContractualInfo/' + id },);
}

90
src/api/contractReview/ContractualInfo/model.ts

@ -0,0 +1,90 @@
import { BaseEntity, PageQuery } from '@/api/base';
export interface ContractualInfoVO {
/**
*
*/
fileName: string;
/**
*
*/
purchaserName: string;
/**
*
*/
supplierName: string;
/**
*
*/
signDate: string;
/**
*
*/
contractAmount: string;
}
export interface ContractualInfoForm extends BaseEntity {
/**
*
*/
fileName?: string;
/**
*
*/
purchaserName?: string;
/**
*
*/
supplierName?: string;
/**
*
*/
signDate?: string;
/**
*
*/
contractAmount?: string;
}
export interface ContractualInfoQuery extends PageQuery {
/**
*
*/
fileName?: string;
/**
*
*/
purchaserName?: string;
/**
*
*/
supplierName?: string;
/**
*
*/
signDate?: string;
/**
*
*/
contractAmount?: string;
/**
*
*/
params?: any;
}

103
src/api/contractReview/JyjcontractualTaskBatch/index.ts

@ -0,0 +1,103 @@
import { defHttp } from '@/utils/http/axios';
import { ID, IDS, commonExport } from '@/api/base';
import { JyjcontractualTaskBatchVO, JyjcontractualTaskBatchForm, JyjcontractualTaskBatchQuery } from './model';
/**
*
* @param params
* @returns
*/
export function JyjcontractualTaskBatchList(params?: JyjcontractualTaskBatchQuery) {
return defHttp.get<JyjcontractualTaskBatchVO[]>({ url: '/productManagement/JyjcontractualTaskBatch/list', params });
}
/**
*
* @param params
* @returns
*/
export function JyjcontractualTaskBatchExport(params?: JyjcontractualTaskBatchQuery) {
return commonExport('/productManagement/JyjcontractualTaskBatch/export', params ?? {});
}
/**
*
* @param id id
* @returns
*/
export function JyjcontractualTaskBatchInfo(id: ID) {
return defHttp.get<JyjcontractualTaskBatchVO>({ url: '/productManagement/JyjcontractualTaskBatch/' + id });
}
/**
*
* @param data
* @returns
*/
export function JyjcontractualTaskBatchAdd(data: JyjcontractualTaskBatchForm) {
return defHttp.postWithMsg<void>({ url: '/productManagement/JyjcontractualTaskBatch', data ,timeout:1000 * 60 * 10});
}
/**
*
* @param data
* @returns
*/
export function JyjcontractualTaskBatchUpdate(data: JyjcontractualTaskBatchForm) {
return defHttp.putWithMsg<void>({ url: '/productManagement/JyjcontractualTaskBatch', data });
}
/**
*
* @param id id
* @returns
*/
export function JyjcontractualTaskBatchRemove(id: ID | IDS) {
return defHttp.deleteWithMsg<void>({ url: '/productManagement/JyjcontractualTaskBatch/' + id },);
}
import { UploadFileParams } from '#/axios';
import { AxiosProgressEvent } from 'axios';
/**
* @description: Upload interface
*/
export function uploadContractual(
params: UploadFileParams,
onUploadProgress?: (progressEvent: AxiosProgressEvent) => void,
) {
return defHttp.uploadFile<any>(
{
// 固定url地址
url: '/productManagement/JyjcontractualTaskBatch/back/upload',
onUploadProgress,
timeout: 1000 * 60 * 10,
},
params,
);
}
/**
* id获取合同的审查结果
* @param id id
* @returns
*/
export function getContractualResultById(id: ID) {
return defHttp.get({ url: '/productManagement/JyjcontractualTaskBatch/getContractulResultById/' + id });
}
/**
* PDF文件流
* @param fileName - PDF文件名
* @returns Blob
*/
export function getPdfFile(id:Number) {
return defHttp.get(
{
url: `/productManagement/JyjcontractualTaskBatch/getContractulPdf/${id}`,
responseType: 'blob',
headers: {
Accept: 'application/pdf',
}
},
{ isReturnNativeResponse: true }
);
}

160
src/api/contractReview/JyjcontractualTaskBatch/model.ts

@ -0,0 +1,160 @@
import { BaseEntity, PageQuery } from '@/api/base';
export interface JyjcontractualTaskBatchVO {
/**
*
*/
taskName: string;
/**
*
*/
taskType: string;
/**
*
*/
documentName: string;
/**
* OSS文件ID
*/
ossId: string | number;
/**
*
*/
progressStatus: string;
/**
* id
*/
groupId: string | number;
/**
*
*/
totalContracts: number;
/**
*
*/
approvedCount: number;
/**
*
*/
passCount: number;
/**
*
*/
rejectCount: number;
/**
*
*/
irrelevantCount: number;
/**
* ()
*/
progress: number;
}
export interface JyjcontractualTaskBatchForm extends BaseEntity {
/**
*
*/
taskName?: string;
/**
*
*/
taskType?: string;
/**
*
*/
documentName?: string;
/**
* OSS文件ID
*/
ossId?: string | number;
/**
*
*/
progressStatus?: string;
}
export interface JyjcontractualTaskBatchQuery extends PageQuery {
/**
*
*/
taskName?: string;
/**
*
*/
taskType?: string;
/**
*
*/
documentName?: string;
/**
* OSS文件ID
*/
ossId?: string | number;
/**
*
*/
progressStatus?: string;
/**
* id
*/
groupId?: string | number;
/**
*
*/
totalContracts?: number;
/**
*
*/
approvedCount?: number;
/**
*
*/
passCount?: number;
/**
*
*/
rejectCount?: number;
/**
*
*/
irrelevantCount?: number;
/**
* ()
*/
progress?: number;
/**
*
*/
params?: any;
}

22
src/components/Upload/src/components/UploadModal.vue

@ -139,8 +139,11 @@
await new Promise((resolve, reject) => {
Modal.confirm({
title: '提醒',
content: (
createVNode('div', { style: 'color:red;font-weight: bold;font-size: 22px' }, beforeUploadPrompt) ),
content: createVNode(
'div',
{ style: 'color:red;font-weight: bold;font-size: 22px' },
beforeUploadPrompt,
),
onOk() {
resolve(true);
},
@ -232,6 +235,21 @@
url: get(ret, props.resultField),
};
}
console.log(ret.validFileCount);
console.log(ret.invalidFileCount);
console.log(ret.invalidFileNames);
await new Promise((resolve, reject) => {
Modal.confirm({
title: '提醒',
content: `符合文件格式要求的文件数量:${ret.validFileCount}\n不符合文件格式要求的文件数量:${ret.invalidFileCount}\n不符合要求的文件名称:${ret.invalidFileNames}`,
onOk() {
resolve(true);
},
onCancel() {
reject(false);
},
});
});
return {
success: true,
error: null,

2
src/enums/pageEnum.ts

@ -2,7 +2,7 @@ export enum PageEnum {
// basic login path
BASE_LOGIN = '/login',
// 默认/dashboard 在登录时会带上redirect 然后死循环 原因未知 暂时写死地址
BASE_HOME = '/workbench',
BASE_HOME = '/contractReview/JyjcontractualTaskBatch',
// error page path
ERROR_PAGE = '/exception',
// error log page path

3
src/views/CanvasEditor/style.css

@ -876,6 +876,7 @@ ul {
line-height: 22px;
}
/* 修改后的 footer 样式 */
.footer {
width: 100%;
height: 30px;
@ -884,7 +885,7 @@ ul {
justify-content: space-between;
background: #f2f4f7;
z-index: 9;
position: fixed;
position: absolute; /* 改为 absolute 而不是 fixed */
bottom: 0;
left: 0;
font-size: 12px;

85
src/views/contractReview/ContractualInfo/ContractualInfo.data.ts

@ -0,0 +1,85 @@
import { BasicColumn } from '@/components/Table';
import { FormSchema } from '@/components/Form';
export const formSchemas: FormSchema[] = [
{
label: '合同名称',
field: 'fileName',
component: 'Input',
},
{
label: '采购人名称',
field: 'purchaserName',
component: 'Input',
},
{
label: '供应商名称或姓名',
field: 'supplierName',
component: 'Input',
},
{
label: '合同签订时间',
field: 'signDate',
component: 'Input',
},
{
label: '合同金额',
field: 'contractAmount',
component: 'Input',
},
];
export const columns: BasicColumn[] = [
{
title: '合同名称',
dataIndex: 'fileName',
},
{
title: '采购人名称',
dataIndex: 'purchaserName',
},
{
title: '供应商名称或姓名',
dataIndex: 'supplierName',
},
{
title: '合同签订时间',
dataIndex: 'signDate',
},
{
title: '合同金额',
dataIndex: 'contractAmount',
},
];
export const modalSchemas: FormSchema[] = [
{
label: '合同名称',
field: 'fileName',
required: true,
component: 'Input',
},
{
label: '采购人名称',
field: 'purchaserName',
required: true,
component: 'Input',
},
{
label: '供应商名称或姓名',
field: 'supplierName',
required: true,
component: 'Input',
},
{
label: '合同签订时间',
field: 'signDate',
required: true,
component: 'Input',
},
{
label: '合同金额',
field: 'contractAmount',
required: true,
component: 'Input',
},
];

68
src/views/contractReview/ContractualInfo/ContractualInfoModal.vue

@ -0,0 +1,68 @@
<template>
<BasicModal
v-bind="$attrs"
:title="title"
@register="registerInnerModal"
@ok="handleSubmit"
@cancel="resetForm"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { computed, ref, unref } from 'vue';
import { ContractualInfoInfo, ContractualInfoAdd, ContractualInfoUpdate } from '@/api/contractReview/ContractualInfo';
import { modalSchemas } from './ContractualInfo.data';
defineOptions({ name: 'ContractualInfoModal' });
const emit = defineEmits(['register', 'reload']);
const isUpdate = ref<boolean>(false);
const title = computed<string>(() => {
return isUpdate.value ? '编辑合同信息保存' : '新增合同信息保存';
});
const [registerInnerModal, { modalLoading, closeModal }] = useModalInner(
async (data: { record?: Recordable; update: boolean }) => {
modalLoading(true);
const { record, update } = data;
isUpdate.value = update;
if (update && record) {
const ret = await ContractualInfoInfo(record.id);
await setFieldsValue(ret);
}
modalLoading(false);
},
);
const [registerForm, { setFieldsValue, resetForm, validate }] = useForm({
labelWidth: 100,
showActionButtonGroup: false,
baseColProps: { span: 24 },
schemas: modalSchemas,
});
async function handleSubmit() {
try {
modalLoading(true);
const data = await validate();
if (unref(isUpdate)) {
await ContractualInfoUpdate(data);
} else {
await ContractualInfoAdd(data);
}
emit('reload');
closeModal();
await resetForm();
} catch (e) {
} finally {
modalLoading(false);
}
}
</script>
<style scoped></style>

115
src/views/contractReview/ContractualInfo/index.vue

@ -0,0 +1,115 @@
<template>
<PageWrapper dense>
<BasicTable @register="registerTable">
<template #toolbar>
<a-button
@click="downloadExcel(ContractualInfoExport, '合同信息保存数据', getForm().getFieldsValue())"
v-auth="'productManagement:ContractualInfo:export'"
>导出</a-button
>
<a-button
type="primary"
danger
@click="multipleRemove(ContractualInfoRemove)"
:disabled="!selected"
v-auth="'productManagement:ContractualInfo:remove'"
>删除</a-button
>
<a-button
type="primary"
@click="handleAdd"
v-auth="'productManagement:ContractualInfo:add'"
>新增</a-button
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
stopButtonPropagation
:actions="[
{
label: '修改',
icon: IconEnum.EDIT,
type: 'primary',
ghost: true,
auth: 'productManagement:ContractualInfo:edit',
onClick: handleEdit.bind(null, record),
},
{
label: '删除',
icon: IconEnum.DELETE,
type: 'primary',
danger: true,
ghost: true,
auth: 'productManagement:ContractualInfo:remove',
popConfirm: {
placement: 'left',
title: '是否删除合同信息保存[' + record.id + ']?',
confirm: handleDelete.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<ContractualInfoModal @register="registerModal" @reload="reload" />
</PageWrapper>
</template>
<script setup lang="ts">
import { PageWrapper } from '@/components/Page';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { ContractualInfoList, ContractualInfoExport, ContractualInfoRemove } from '@/api/contractReview/ContractualInfo';
import { downloadExcel } from '@/utils/file/download';
import { useModal } from '@/components/Modal';
import ContractualInfoModal from './ContractualInfoModal.vue';
import { formSchemas, columns } from './ContractualInfo.data';
import { IconEnum } from '@/enums/appEnum';
defineOptions({ name: 'ContractualInfo' });
const [registerTable, { reload, multipleRemove, selected, getForm }] = useTable({
rowSelection: {
type: 'checkbox',
},
title: '合同信息保存列表',
api: ContractualInfoList,
showIndexColumn: false,
rowKey: 'id',
useSearchForm: true,
formConfig: {
schemas: formSchemas,
baseColProps: {
xs: 24,
sm: 24,
md: 24,
lg: 6,
},
},
columns: columns,
actionColumn: {
width: 200,
title: '操作',
key: 'action',
fixed: 'right',
},
});
const [registerModal, { openModal }] = useModal();
function handleEdit(record: Recordable) {
openModal(true, { record, update: true });
}
function handleAdd() {
openModal(true, { update: false });
}
async function handleDelete(record: Recordable) {
await ContractualInfoRemove([record.id]);
await reload();
}
</script>
<style scoped></style>

264
src/views/contractReview/ContractualTasks/ContractualShowModal.vue

@ -0,0 +1,264 @@
<template>
<BasicModal
v-bind="$attrs"
title="title"
:canFullscreen="true"
:defaultFullscreen="true"
:showOkBtn="false"
@register="registerInnerModal"
>
<div class="grid grid-cols-5 gap-4">
<div class="col-span-3">
<!-- <CanvasEditor
ref="canvasEditor"
:parentContent="parentContent"
:view="view"
@save-content="handleSaveCanvasEditorContent"
/> -->
<PdfViewer :id="currentId" :key="pdfViewerkey" v-if="currentId!=0"/>
</div>
<div class="col-span-2">
<!-- 添加 col-span-2 和高度样式 -->
<div class="tabs-container">
<Tabs v-model:activeKey="activeKey" type="card">
<TabPane key="1" tab="审查结果">
<div class="scroll-container">
<div class="card-container">
<Card
v-for="(item, index) in cardList"
:key="index"
:class="['custom-card', { 'card-selected': selectedCard === item.title }]"
@click="handleCardClick(item.title)"
>
<template #title>{{ item.title }}</template>
{{ item.text }}
{{ item.content }}
</Card>
</div>
</div>
</TabPane>
<!-- <TabPane key="2" tab="Tab 2">Content of Tab Pane 2</TabPane>
<TabPane key="3" tab="Tab 3">Content of Tab Pane 3</TabPane> -->
</Tabs>
</div>
</div>
</div>
</BasicModal>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import { BasicModal, useModalInner } from '@/components/Modal';
import { Tabs, TabPane, Card } from 'ant-design-vue';
import { useRouter } from 'vue-router';
import CanvasEditor from '@/views/CanvasEditor/index.vue';
import { getContractualResultById } from '@/api/contractReview/JyjcontractualTaskBatch';
import PdfViewer from "./PdfViewer.vue"
const activeKey = ref('1');
const currentId = ref<any>(0);
const pdfViewerkey = ref(0);
//
const cardList = ref<any>([]);
const selectedCard = ref(''); //
const handleCardClick = (title) => {
selectedCard.value = title;
console.log('点击的卡片标题:', title);
};
const [registerInnerModal, { modalLoading, closeModal }] = useModalInner(
async (data: { record?: Recordable }) => {
modalLoading(true);
cardList.value=[]
const { record } = data;
const res = await getContractualResultById(record.id);
pdfViewerkey.value+=1
currentId.value=record.id
for (const item of res.results) {
for (const content of item.contentList) {
cardList.value.push({
title: content.problemTitle,
text: content.text,
content: content.problemDesc,
isPosition: content.isPosition,
accord: item.accord,
});
}
}
modalLoading(false);
},
);
//
let parentContent = reactive<any>(undefined);
//
const content = ref<any>(undefined);
//
const view = ref<string | undefined>(undefined);
//
onMounted(() => {
console.log('模拟父组件向后端请求数据, 传递给子组件');
getEditorContent();
view.value = 'parent';
});
//
const getEditorContent = () => {
parentContent = {
header: [
{
value: '',
size: 12,
bold: false,
color: 'rgb(33, 53, 71)',
italic: false,
},
],
main: [
{
value:
'父类传递的数据 通过后端获取!\n甲方:企查查科技股份有限公司\n法定代表人:陈德强\n住所:苏州工业园区科创东区东石泾港路2号润港产业园6号楼10层1001室11',
size: 10,
bold: false,
},
],
};
};
//
const canvasEditor = ref<InstanceType<typeof CanvasEditor> | null>(null);
//
const handleSaveContent = () => {
// 访
(canvasEditor.value as any).saveContent();
};
//
const handleSaveCanvasEditorContent = (data: any) => {
console.log('从子组件接收到的数据:', data);
// data json , 便
content.value = JSON.stringify(data);
console.log('转换后的数据 content 为: ', content.value);
};
const router = useRouter();
//
const loading = ref(false);
const handleMenuClick = ({ key }) => {
console.log(key);
router.push({ path: key });
};
//
const fetchTaskList = async () => {};
//
onMounted(() => {
fetchTaskList();
});
setTimeout(() => {
loading.value = false;
}, 1500);
</script>
<style scoped>
/* Tabs 相关样式 */
/* 设置未选中标签的基本样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab) {
color: rgba(0, 0, 0, 0.85) !important; /* 文字颜色为黑色,带透明度 */
background: #fff; /* 背景色为白色 */
border: 1px solid #f0f0f0; /* 浅灰色边框 */
}
/* 设置选中标签的样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active) {
color: #ffffff !important; /* 文字颜色为白色 */
background: #1890ff !important; /* 背景色为蓝色 */
border-color: #1890ff !important; /* 边框颜色也改为蓝色 */
}
/* 确保选中标签的文字颜色为白色 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active .ant-tabs-tab-btn) {
color: #ffffff !important; /* 文字颜色强制设为白色 */
}
/* 鼠标悬停时的文字颜色效果 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab:hover .ant-tabs-tab-btn) {
color: #1890ff !important; /* 悬停时文字变为蓝色 */
}
/* Card 相关样式 */
/* 卡片容器布局设置 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
width: 100%;
}
/* 单个卡片的基本样式 */
.custom-card {
width: 100%;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.3s;
background-color: #fff; /* 添加默认背景色 */
border: 1px solid #e8e8e8; /* 添加默认边框 */
}
/* 卡片选中状态 */
.custom-card.card-selected {
background-color: #e6f4ff !important; /* 选中时的淡蓝色背景 */
border: 1px solid #1890ff !important; /* 选中时的蓝色边框 */
}
/* 卡片悬停效果 */
.custom-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* 选中状态下的悬停效果 */
.custom-card.card-selected:hover {
background-color: #e6f4ff !important;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
}
/* 卡片点击效果 */
.custom-card:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* 修改卡片内容区域的内边距 */
:deep(.ant-card-body) {
padding: 16px;
}
/* 滚动容器样式 */
.scroll-container {
height: calc(100vh - 25vh); /* 可以根据实际需要调整高度 */
overflow-y: auto; /* 添加垂直滚动条 */
padding: 16px;
}
/* 美化滚动条样式(可选) */
.scroll-container::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
.scroll-container::-webkit-scrollbar-thumb {
background-color: #ccc; /* 滚动条颜色 */
border-radius: 3px; /* 滚动条圆角 */
}
.scroll-container::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 滚动条轨道颜色 */
}
/* 保持原有的卡片容器样式 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
</style>

123
src/views/contractReview/ContractualTasks/ContractualTasks.data.ts

@ -4,35 +4,7 @@ import { getDictOptions } from '@/utils/dict';
import { useRender } from '@/hooks/component/useRender';
import { uploadDocument } from '@/api/documentReview/DocumentTasks';
import { useUserStore } from '@/store/modules/user';
import { RoleEnum } from '@/enums/roleEnum';
const { roleList } = useUserStore();
export const formSchemas: FormSchema[] = [
// {
// label: '模型所属行业',
// field: 'taskIndustry',
// component: 'Select',
// componentProps: {
// options: getDictOptions('model_industry')
// },
// },
// {
// label: '模型所属区域',
// field: 'taskRegion',
// component: 'Select',
// componentProps: {
// options: getDictOptions('model_region')
// },
// },
{
label: '任务名称',
field: 'taskNameList',
component: 'Select',
componentProps: {
options: getDictOptions('contract_review'),
mode: 'multiple',
},
},
{
label: '文档名称',
field: 'documentName',
@ -46,37 +18,38 @@ export const formSchemas: FormSchema[] = [
options: getDictOptions('document_task_status'),
},
},
{
label: '审核结果',
field: 'resultType',
component: 'Input',
},
{
label: '批次名称',
field: 'batchName',
component: 'Input',
},{
label: '批次ID',
field: 'groupId',
component: 'Input',
show:false
}
];
const { renderDict } = useRender();
export const columns: BasicColumn[] = [
{
title: '任务名称',
dataIndex: 'taskName',
customRender: ({ value }) => renderDict(value, 'contract_review'),
},
{
title: '文档名称',
dataIndex: 'documentName',
},
{
title: '模型所属区域',
dataIndex: 'taskRegion',
customRender: ({ value }) => renderDict(value, 'model_region'),
},
{
title: '模型所属行业',
dataIndex: 'taskIndustry',
customRender: ({ value }) => renderDict(value, 'model_industry'),
},
{
title: '审查立场',
dataIndex: 'contractPartyRole',
{
title: '批次名称',
dataIndex: 'batchName',
},
{
title: '上传时间',
dataIndex: 'createTime',
title: '任务名称',
dataIndex: 'taskName',
customRender: ({ value }) => renderDict(value, 'contract_review'),
},
{
title: '提交人',
@ -84,8 +57,8 @@ export const columns: BasicColumn[] = [
auth: 'documentReview:DocumentTasks:tableShow',
},
{
title: '任务耗时',
dataIndex: 'taskDuration',
title: '审核结果',
dataIndex: 'resultType',
},
{
title: '状态',
@ -95,57 +68,17 @@ export const columns: BasicColumn[] = [
];
export const modalSchemas: FormSchema[] = [
{
label: '模型所属区域',
field: 'taskRegion',
required: true,
component: 'Select',
// componentProps: {
// options: taskRegionPermission(),
// defaultValue:"normal",
// },
componentProps: () => {
const isSuperAdmin = roleList.includes(RoleEnum.SUPER_ADMIN);
let options = getDictOptions('model_region');
if (!isSuperAdmin) {
// 如果不是超级管理员,移除 label 带有 '#' 的项
options = options.filter((option) => !option.label.includes('#'));
}
return {
options: options,
defaultValue: 'normal',
};
},
},
{
label: '模型所属行业',
field: 'taskIndustry',
required: true,
component: 'Select',
componentProps: {
options: getDictOptions('model_industry'),
defaultValue: 'normal',
},
},
{
label: '审查立场',
field: 'contractPartyRole',
required: true,
component: 'Select',
componentProps: {
options: getDictOptions('contract_party_role'),
defaultValue: '甲方',
},
},
{
label: '任务名称',
field: 'taskNameList',
required: true,
required: false,
dynamicDisabled:true,
component: 'Select',
componentProps: {
options:getDictOptions('contract_review'),
mode: 'multiple',
},
defaultValue:"contractualReview"
},
{
label: '文档名称',

208
src/views/contractReview/ContractualTasks/ContractualTasksTable.vue

@ -0,0 +1,208 @@
<template>
<PageWrapper dense>
<BasicTable @register="registerTable">
<template #toolbar v-if="props.showAdd">
<a-button
type="primary"
@click="handleAdd"
v-auth="'productManagement:ContractualTasks:add'"
>新增</a-button
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
stopButtonPropagation
:actions="[
{
label: '详情',
icon: IconEnum.EDIT,
type: 'primary',
ghost: true,
ifShow: () => {
if (
record.progressStatus != 'PENDING' &&
record.progressStatus != 'STARTED' &&
record.progressStatus != 'REVOKED'
) {
return true;
} else {
return false;
}
},
onClick: handleDetail.bind(null, record),
},
// {
// label: '',
// icon: IconEnum.DOWNLOAD,
// type: 'primary',
// color: 'success',
// ghost: true,
// ifShow: () => {
// if (
// record.progressStatus != 'PENDING' &&
// record.progressStatus != 'STARTED' &&
// record.progressStatus != 'REVOKED'
// ) {
// return true;
// } else {
// return false;
// }
// },
// onClick: handleDownload.bind(null, record),
// },
// {
// label: '',
// icon: IconEnum.DOWNLOAD,
// type: 'primary',
// color: 'success',
// ghost: true,
// ifShow: () => {
// if (
// record.progressStatus != 'PENDING' &&
// record.progressStatus != 'STARTED' &&
// record.progressStatus != 'REVOKED'
// ) {
// return true;
// } else {
// return false;
// }
// },
// onClick: handleDownload.bind(null, record),
// },
{
label: '终止任务',
icon: IconEnum.DELETE,
type: 'primary',
danger: true,
ghost: true,
ifShow: () => {
if (record.progressStatus == 'PENDING') {
return true;
} else {
return false;
}
},
popConfirm: {
placement: 'left',
title: '是否终止当前任务?',
confirm: handleStop.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<ContractualTasksModal @register="registerModal" @reload="reload" />
<ContractualShowModal @register="registerShowModal" />
<DocsDrawer @register="registerDrawer" />
</PageWrapper>
</template>
<script setup lang="ts">
import { PageWrapper } from '@/components/Page';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { useModal } from '@/components/Modal';
import ContractualTasksModal from './ContractualTasksModal.vue';
import DocsDrawer from '@/views/documentReview/DocumentTasks/DocsDrawer.vue';
import { formSchemas, columns } from './ContractualTasks.data';
import { IconEnum } from '@/enums/appEnum';
import ContractualShowModal from '@/views/contractReview/ContractualTasks/ContractualShowModal.vue';
import { DocumentTasksStop } from '@/api/documentReview/DocumentTasks';
import { ContractualTasksList } from '@/api/contractReview/ContractualTasks';
import {
DocumentTaskResultsInfoByTaskId,
DocumentTaskResultDownload,
} from '@/api/documentReview/DocumentTaskResults';
import { onMounted } from 'vue';
import { useDrawer } from '@/components/Drawer';
// batchName
onMounted(() => {
console.log('onMountedonMountedonMounted');
const { setFieldsValue, updateSchema } = getForm();
if (props.batchName) {
setFieldsValue({ batchName: props.batchName });
updateSchema({ field: 'batchName', dynamicDisabled: true });
}
if (props.resultType) {
if (props.resultType == '审查失败文件') {
setFieldsValue({ progressStatus: 'FAILURE', groupId: props.groupId });
updateSchema([
{ field: 'progressStatus', dynamicDisabled: true },
{ field: 'resultType', dynamicDisabled: true },
]);
} else {
setFieldsValue({ resultType: props.resultType, groupId: props.groupId });
updateSchema({ field: 'resultType', dynamicDisabled: true });
}
}
});
const [registerShowModal, { openModal: openShowModal }] = useModal();
const [registerDrawer, { openDrawer }] = useDrawer();
defineOptions({ name: 'ContractualTasks' });
let props = defineProps(['showAdd', 'groupId', 'batchName', 'resultType']);
const [registerTable, { reload, multipleRemove, selected, getForm }] = useTable({
rowSelection: {
type: 'checkbox',
},
title: '合同任务列表',
api: ContractualTasksList,
showIndexColumn: false,
rowKey: 'id',
useSearchForm: true,
formConfig: {
schemas: formSchemas,
baseColProps: {
xs: 24,
sm: 24,
md: 24,
lg: 6,
},
},
columns: columns,
actionColumn: {
width: 200,
title: '操作',
key: 'action',
fixed: 'right',
},
});
const [registerModal, { openModal }] = useModal();
async function handleDetail(record: Recordable) {
if(record.progressStatus=="FAILURE"){
let res = await DocumentTaskResultsInfoByTaskId(record.id);
openDrawer(true, { value: res.result, type: 'markdown' });
}else{
openShowModal(true, {record });
}
//record.id
}
async function handleStop(record: Recordable) {
await DocumentTasksStop(record.id);
await reload();
}
function handleAdd() {
openModal(true, { update: false });
}
async function handleDownload(record: Recordable) {
await DocumentTaskResultDownload([record.id]);
await reload();
}
</script>
<style scoped></style>

127
src/views/contractReview/ContractualTasks/PdfViewer.vue

@ -0,0 +1,127 @@
<template>
<div class="pdf-container">
<vue-office-docx
:src="pdfUrl"
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';
//VueOfficeDocx
import VueOfficeDocx from '@vue-office/pdf'
//
import { message } from 'ant-design-vue';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons-vue';
import { getPdfFile } from '@/api/contractReview/JyjcontractualTaskBatch';
// Props
interface Props {
id: string;
}
const props = defineProps<Props>();
const pdfUrl = ref<string>('');
const currentPage = ref(1);
const pageCount = ref(0);
const documentLoaded = ref(false);
const scale = ref(1.5); //
//
const fitToWidth = () => {
const pdfContainer = document.querySelector('.pdf-wrapper');
if (pdfContainer) {
const containerWidth = pdfContainer.clientWidth;
const pdfCanvas = document.querySelector('.vue3-pdf canvas') as HTMLCanvasElement;
if (pdfCanvas) {
const originalWidth = pdfCanvas.width / scale.value;
scale.value = +(containerWidth / originalWidth * 0.95).toFixed(1); // 95%
}
}
};
// PDF
const loadPdf = async () => {
try {
if(props.id==0){
pdfUrl.value='';
return;
}
console.log('PDF加载成功1:', pdfUrl.value);
const response = await getPdfFile(props.id);
if (response?.data) {
console.log('PDF加载成功2:', pdfUrl.value);
const blob = new Blob([response.data], { type: 'application/pdf' });
pdfUrl.value = URL.createObjectURL(blob);
console.log('PDF加载成功3:', pdfUrl.value);
// PDF
nextTick(() => {
setTimeout(fitToWidth, 500);
});
}
} catch (error) {
console.error('PDF加载失败:', error);
}
};
const handleError = (error: Error) => {
message.error('PDF显示出错:' + error.message);
};
onMounted(() => {
loadPdf();
});
</script>
<style scoped>
.pdf-container {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background: #f0f2f5;
}
.pdf-wrapper {
flex: 1;
overflow: auto;
padding: 20px;
display: flex;
justify-content: center;
background: #f0f2f5;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 16px;
color: #666;
}
.pdf-controls {
padding: 16px;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
:deep(.vue3-pdf) {
background: white;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 20px;
}
:deep(.vue3-pdf canvas) {
max-width: 100%;
height: auto !important;
}
</style>

115
src/views/contractReview/ContractualTasks/index.vue

@ -1,120 +1,26 @@
<template>
<PageWrapper dense>
<BasicTable @register="registerTable">
<template #toolbar>
<!-- <a-button
@click="downloadExcel(ContractualTasksExport, '合同任务数据', getForm().getFieldsValue())"
v-auth="'productManagement:ContractualTasks:export'"
>导出</a-button
>
<a-button
type="primary"
danger
@click="multipleRemove(ContractualTasksRemove)"
:disabled="!selected"
v-auth="'productManagement:ContractualTasks:remove'"
>删除</a-button
> -->
<a-button
type="primary"
@click="handleAdd"
v-auth="'productManagement:ContractualTasks:add'"
>新增</a-button
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action'">
<TableAction
stopButtonPropagation
:actions="[
{
label: '详情',
icon: IconEnum.EDIT,
type: 'primary',
ghost: true,
ifShow: () => {
if (
record.progressStatus != 'PENDING' &&
record.progressStatus != 'STARTED' &&
record.progressStatus != 'REVOKED'
) {
return true;
} else {
return false;
}
},
onClick: handleDetail.bind(null, record),
},
{
label: '下载',
icon: IconEnum.DOWNLOAD,
type: 'primary',
color: 'success',
ghost: true,
ifShow: () => {
if (
record.progressStatus != 'PENDING' &&
record.progressStatus != 'STARTED' &&
record.progressStatus != 'REVOKED'
) {
return true;
} else {
return false;
}
},
onClick: handleDownload.bind(null, record),
},
{
label: '终止任务',
icon: IconEnum.DELETE,
type: 'primary',
danger: true,
ghost: true,
ifShow: () => {
if (record.progressStatus == 'PENDING') {
return true;
} else {
return false;
}
},
popConfirm: {
placement: 'left',
title: '是否终止当前任务?',
confirm: handleStop.bind(null, record),
},
},
]"
/>
</template>
</template>
</BasicTable>
<ContractualTasksModal @register="registerModal" @reload="reload" />
<DocsDrawer @register="registerDrawer" />
<ContractualTasksTable :showAdd="true" />
</PageWrapper>
</template>
<script setup lang="ts">
import { PageWrapper } from '@/components/Page';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { useTable} from '@/components/Table';
import { downloadExcel } from '@/utils/file/download';
import { useModal } from '@/components/Modal';
import ContractualTasksModal from './ContractualTasksModal.vue';
import { formSchemas, columns } from './ContractualTasks.data';
import { IconEnum } from '@/enums/appEnum';
import DocsDrawer from '@/views/documentReview/DocumentTasks/DocsDrawer.vue';
import { useDrawer } from '@/components/Drawer';
import { DocumentTasksStop } from '@/api/documentReview/DocumentTasks';
import {
ContractualTasksList,
ContractualTasksExport,
ContractualTasksRemove,
} from '@/api/contractReview/ContractualTasks';
import {
DocumentTaskResultsInfoByTaskId,
DocumentTaskResultDownload,
} from '@/api/documentReview/DocumentTaskResults';
import ContractualTasksTable from './ContractualTasksTable.vue';
const [registerDrawer, { openDrawer }] = useDrawer();
defineOptions({ name: 'ContractualTasks' });
@ -149,15 +55,9 @@
const [registerModal, { openModal }] = useModal();
async function handleDetail(record: Recordable) {
try {
let res = await DocumentTaskResultsInfoByTaskId(record.id);
openDrawer(true, { value: res.result, type: 'markdown' });
console.log('res', res);
} catch (ex) {
openDrawer(true, { value: '加载失败,请刷新页面', type: 'markdown' });
}
let res = await DocumentTaskResultsInfoByTaskId(record.id);
openModal(true, { record });
console.log('res', res);
//record.id
}
@ -170,7 +70,6 @@
openModal(true, { update: false });
}
async function handleDownload(record: Recordable) {
await DocumentTaskResultDownload([record.id]);
await reload();

149
src/views/contractReview/ContractualTasks/showResultCard.vue

@ -0,0 +1,149 @@
<template>
<!-- 添加 col-span-2 和高度样式 -->
<div class="tabs-container">
<Tabs v-model:activeKey="activeKey" type="card">
<TabPane key="1" tab="审查结果">
<div class="scroll-container">
<div class="card-container">
<Card
v-for="(item, index) in props.cardList"
:key="index"
:class="['custom-card', { 'card-selected': selectedCard === item.title }]"
@click="handleCardClick(item.title)"
>
<template #title>{{ item.title }}</template>
{{ item.text }}
{{ item.content }}
</Card>
</div>
</div>
</TabPane>
<!-- <TabPane key="2" tab="Tab 2">Content of Tab Pane 2</TabPane>
<TabPane key="3" tab="Tab 3">Content of Tab Pane 3</TabPane> -->
</Tabs>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import { Tabs, TabPane, Card } from 'ant-design-vue';
// Props
interface Props {
cardList:any[];
}
const props = defineProps<Props>();
const activeKey = ref('1');
//
const selectedCard = ref(''); //
const handleCardClick = (title) => {
selectedCard.value = title;
console.log('点击的卡片标题:', title);
};
//
onMounted(() => {
});
</script>
<style scoped>
/* Tabs 相关样式 */
/* 设置未选中标签的基本样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab) {
color: rgba(0, 0, 0, 0.85) !important; /* 文字颜色为黑色,带透明度 */
background: #fff; /* 背景色为白色 */
border: 1px solid #f0f0f0; /* 浅灰色边框 */
}
/* 设置选中标签的样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active) {
color: #ffffff !important; /* 文字颜色为白色 */
background: #1890ff !important; /* 背景色为蓝色 */
border-color: #1890ff !important; /* 边框颜色也改为蓝色 */
}
/* 确保选中标签的文字颜色为白色 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active .ant-tabs-tab-btn) {
color: #ffffff !important; /* 文字颜色强制设为白色 */
}
/* 鼠标悬停时的文字颜色效果 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab:hover .ant-tabs-tab-btn) {
color: #1890ff !important; /* 悬停时文字变为蓝色 */
}
/* Card 相关样式 */
/* 卡片容器布局设置 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
width: 100%;
}
/* 单个卡片的基本样式 */
.custom-card {
width: 100%;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.3s;
background-color: #fff; /* 添加默认背景色 */
border: 1px solid #e8e8e8; /* 添加默认边框 */
}
/* 卡片选中状态 */
.custom-card.card-selected {
background-color: #e6f4ff !important; /* 选中时的淡蓝色背景 */
border: 1px solid #1890ff !important; /* 选中时的蓝色边框 */
}
/* 卡片悬停效果 */
.custom-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* 选中状态下的悬停效果 */
.custom-card.card-selected:hover {
background-color: #e6f4ff !important;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
}
/* 卡片点击效果 */
.custom-card:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* 修改卡片内容区域的内边距 */
:deep(.ant-card-body) {
padding: 16px;
}
/* 滚动容器样式 */
.scroll-container {
height: calc(100vh - 25vh); /* 可以根据实际需要调整高度 */
overflow-y: auto; /* 添加垂直滚动条 */
padding: 16px;
}
/* 美化滚动条样式(可选) */
.scroll-container::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
.scroll-container::-webkit-scrollbar-thumb {
background-color: #ccc; /* 滚动条颜色 */
border-radius: 3px; /* 滚动条圆角 */
}
.scroll-container::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 滚动条轨道颜色 */
}
/* 保持原有的卡片容器样式 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
</style>

71
src/views/contractReview/JyjcontractualTaskBatch/ContractualTasksTableModal.vue

@ -0,0 +1,71 @@
<template>
<BasicModal
v-bind="$attrs"
:title="title"
:canFullscreen="true"
:defaultFullscreen="true"
:showOkBtn="false"
@register="registerInnerModal"
>
<ContractualTasksTable
:showAdd="false"
:groupId="groupId"
:batchName="batchName"
:resultType="resultType"
:key="randomKey"
/>
</BasicModal>
</template>
<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { computed, ref, unref } from 'vue';
import {
JyjcontractualTaskBatchInfo,
JyjcontractualTaskBatchAdd,
JyjcontractualTaskBatchUpdate,
} from '@/api/contractReview/JyjcontractualTaskBatch';
import { modalSchemas } from './JyjcontractualTaskBatch.data';
import ContractualTasksTable from '@/views/contractReview/ContractualTasks/ContractualTasksTable.vue';
defineOptions({ name: 'ContractualTasksTableModal' });
// key
const randomKey = ref('1');
const emit = defineEmits(['register', 'reload']);
const title = ref('');
const resultType = ref('');
const batchName = ref('');
const groupId = ref('');
const isUpdate = ref<boolean>(false);
const [registerInnerModal, { modalLoading, closeModal }] = useModalInner(
async (data: { record?: Recordable; type?: string }) => {
modalLoading(true);
const { record, type } = data;
if (type == 'passCount') {
title.value = '查看审核通过记录';
resultType.value = '审查合格';
} else if (type == 'rejectCount') {
title.value = '查看审核不通过记录';
resultType.value = '审查不合格';
} else if(type=="irrelevantCount"){
title.value = '查看非审查范围记录';
resultType.value = '非审查范围文件';
}else{
title.value = '查看审查失败的范围记录';
resultType.value = '审查失败文件';
}
batchName.value = record.batchName;
groupId.value = record.id;
console.log(batchName.value, groupId.value);
const generateRandomKey = () => Math.random().toString(36).substr(2, 9);
//
randomKey.value = generateRandomKey(); // key
modalLoading(false);
},
);
</script>
<style scoped></style>

109
src/views/contractReview/JyjcontractualTaskBatch/JyjcontractualTaskBatch.data.ts

@ -0,0 +1,109 @@
import { BasicColumn } from '@/components/Table';
import { FormSchema } from '@/components/Form';
import { getDictOptions } from '@/utils/dict';
import { useRender } from '@/hooks/component/useRender';
import { uploadContractual } from '@/api/contractReview/JyjcontractualTaskBatch';
export const formSchemas: FormSchema[] = [
{
label: '文档名称',
field: 'documentName',
component: 'Input',
},
{
label: '批次名称',
field: 'batchName',
component: 'Input',
},
{
label: '进度状态',
field: 'progressStatus',
component: 'Select',
componentProps: {
options: getDictOptions('document_task_status'),
},
},
];
const { renderDict } = useRender();
export const columns: BasicColumn[] = [
{
title: '文件名称',
dataIndex: 'documentName',
},
{
title: '批次名称',
dataIndex: 'batchName',
},
{
title: '合同总数',
dataIndex: 'totalContracts',
},
{
title: '已审批总数',
dataIndex: 'approvedCount',
},
{
title: '审核通过数量',
dataIndex: 'passCount',
width: 120,
},
{
title: '审核不通过数量',
dataIndex: 'rejectCount',
width: 130,
},
{
title: '非审查范围数量',
dataIndex: 'irrelevantCount',
width: 130,
},
{
title: '审核失败数量',
dataIndex: 'failCount',
width: 100,
},
{
title: '进度(百分比)',
dataIndex: 'progress',
},
{
title: '处理时间',
dataIndex: 'processingTime',
},
{
title: '状态',
dataIndex: 'progressStatus',
customRender: ({ value }) => renderDict(value, 'document_task_status'),
},
];
export const modalSchemas: FormSchema[] = [
{
label: '文件名称',
field: 'ossId',
required: true,
component: 'Upload',
componentProps: {
accept: ['.zip'],
maxSize: 500,
multiple: false,
resultField: 'ossId',
api: uploadContractual,
},
},
{
label: '批次名称',
field: 'batchName',
required: false,
component: 'Input',
},
{
label: '备注',
field: 'remark',
required: false,
component: 'InputTextArea',
},
];

74
src/views/contractReview/JyjcontractualTaskBatch/JyjcontractualTaskBatchModal.vue

@ -0,0 +1,74 @@
<template>
<BasicModal
v-bind="$attrs"
:title="title"
@register="registerInnerModal"
@ok="handleSubmit"
@cancel="resetForm"
>
<BasicForm @register="registerForm" />
</BasicModal>
</template>
<script setup lang="ts">
import { BasicModal, useModalInner } from '@/components/Modal';
import { BasicForm, useForm } from '@/components/Form';
import { computed, ref, unref } from 'vue';
import { JyjcontractualTaskBatchInfo, JyjcontractualTaskBatchAdd, JyjcontractualTaskBatchUpdate } from '@/api/contractReview/JyjcontractualTaskBatch';
import { modalSchemas } from './JyjcontractualTaskBatch.data';
defineOptions({ name: 'JyjcontractualTaskBatchModal' });
const emit = defineEmits(['register', 'reload']);
const isUpdate = ref<boolean>(false);
const title = computed<string>(() => {
return isUpdate.value ? '编辑合同批处理记录' : '新增合同批处理记录';
});
const [registerInnerModal, { modalLoading, closeModal }] = useModalInner(
async (data: { record?: Recordable; update: boolean }) => {
modalLoading(true);
const { record, update } = data;
isUpdate.value = update;
if (update && record) {
const ret = await JyjcontractualTaskBatchInfo(record.id);
await setFieldsValue(ret);
}
modalLoading(false);
},
);
const [registerForm, { setFieldsValue, resetForm, validate }] = useForm({
labelWidth: 100,
showActionButtonGroup: false,
baseColProps: { span: 24 },
schemas: modalSchemas,
});
async function handleSubmit() {
try {
modalLoading(true);
const data = await validate();
//
if (unref(isUpdate)) {
data["ossId"]=data["ossId"][0]
await JyjcontractualTaskBatchUpdate(data);
} else {
//
data["ossId"]=data["ossId"][0]
data["taskName"]="batchReview"
data["taskType"]="contract_review"
await JyjcontractualTaskBatchAdd(data);
}
emit('reload');
closeModal();
await resetForm();
} catch (e) {
} finally {
modalLoading(false);
}
}
</script>
<style scoped></style>

200
src/views/contractReview/JyjcontractualTaskBatch/index.vue

@ -0,0 +1,200 @@
<template>
<PageWrapper dense>
<BasicTable @register="registerTable">
<template #toolbar>
<!-- <a-button
@click="
downloadExcel(
JyjcontractualTaskBatchExport,
'合同批处理记录数据',
getForm().getFieldsValue(),
)
"
v-auth="'productManagement:JyjcontractualTaskBatch:export'"
>导出</a-button
>
<a-button
type="primary"
danger
@click="multipleRemove(JyjcontractualTaskBatchRemove)"
:disabled="!selected"
v-auth="'productManagement:JyjcontractualTaskBatch:remove'"
>删除</a-button
> -->
<a-button
type="primary"
@click="handleAdd"
v-auth="'productManagement:JyjcontractualTaskBatch:add'"
>新增</a-button
>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'passCount'">
<div class="grid grid-flow-row auto-rows-max">
<div> {{ record.passCount }}</div>
<div><TableAction
stopButtonPropagation
:actions="[
{
label: '查看',
icon: IconEnum.PREVIEW,
type: 'primary',
color:'success',
ifShow:()=>{
if(record.passCount&&record.passCount>0){
return true
}else{
return false
}
},
ghost: true,
onClick: handlePreview.bind(null, record,'passCount'),
},
]"
/></div>
</div>
</template>
<template v-if="column.key === 'rejectCount'">
<div class="grid grid-flow-row auto-rows-max">
<div> {{ record.rejectCount }}</div>
<div><TableAction
stopButtonPropagation
:actions="[
{
label: '查看',
icon: IconEnum.PREVIEW,
type: 'primary',
color:'error',
ifShow:()=>{
if(record.rejectCount&&record.rejectCount>0){
return true
}else{
return false
}
},
ghost: true,
onClick: handlePreview.bind(null, record,'rejectCount'),
},
]"
/></div>
</div>
</template>
<template v-if="column.key === 'irrelevantCount'">
<div class="grid grid-flow-row auto-rows-max">
<div> {{ record.irrelevantCount }}</div>
<div><TableAction
stopButtonPropagation
:actions="[
{
label: '查看',
icon: IconEnum.PREVIEW,
type: 'primary',
color:'warning',
ifShow:()=>{
if(record.irrelevantCount&&record.irrelevantCount>0){
return true
}else{
return false
}
},
ghost: true,
onClick: handlePreview.bind(null, record,'irrelevantCount'),
},
]"
/></div>
</div>
</template>
<template v-if="column.key === 'failCount'">
<div class="grid grid-flow-row auto-rows-max">
<div> {{ record.failCount }}</div>
<div><TableAction
:actions="[
{
label: '查看',
icon: IconEnum.PREVIEW,
type: 'primary',
ifShow:()=>{
if(record.failCount&&record.failCount>0){
return true
}else{
return false
}
},
ghost: true,
onClick: handlePreview.bind(null, record,'failCount'),
},
]"
/></div>
</div>
</template>
</template>
</BasicTable>
<JyjcontractualTaskBatchModal @register="registerModal" @reload="reload" />
<ContractualTasksTableModal @register="registerTableModal"/>
</PageWrapper>
</template>
<script setup lang="ts">
import { PageWrapper } from '@/components/Page';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import {
JyjcontractualTaskBatchList,
JyjcontractualTaskBatchExport,
JyjcontractualTaskBatchRemove,
} from '@/api/contractReview/JyjcontractualTaskBatch';
import { downloadExcel } from '@/utils/file/download';
import { useModal } from '@/components/Modal';
import JyjcontractualTaskBatchModal from './JyjcontractualTaskBatchModal.vue';
import ContractualTasksTableModal from './ContractualTasksTableModal.vue'
import { formSchemas, columns } from './JyjcontractualTaskBatch.data';
import { IconEnum } from '@/enums/appEnum';
defineOptions({ name: 'JyjcontractualTaskBatch' });
const [registerTable, { reload, multipleRemove, selected, getForm }] = useTable({
rowSelection: {
type: 'checkbox',
},
title: '合同批处理记录列表',
api: JyjcontractualTaskBatchList,
showIndexColumn: false,
rowKey: 'id',
useSearchForm: true,
formConfig: {
schemas: formSchemas,
baseColProps: {
xs: 24,
sm: 24,
md: 24,
lg: 6,
},
},
columns: columns,
actionColumn: {
ifShow:false,
width: 200,
title: '操作',
key: 'action',
fixed: 'right',
},
});
const [registerModal, { openModal }] = useModal();
const [registerTableModal,{openModal:openTableModal}]= useModal();
function handleEdit(record: Recordable) {
openModal(true, { record, update: true });
}
function handleAdd() {
openModal(true, { update: false });
}
function handlePreview(record: Recordable,type:string){
openTableModal(true,{record,type:type})
}
async function handleDelete(record: Recordable) {
await JyjcontractualTaskBatchRemove([record.id]);
await reload();
}
</script>
<style scoped></style>

251
src/views/workbench/index copy.vue

@ -0,0 +1,251 @@
<template>
<PageWrapper dense>
<div class="grid grid-cols-5 gap-4">
<div class="col-span-3">
<CanvasEditor
ref="canvasEditor"
:parentContent="parentContent"
:view="view"
@save-content="handleSaveCanvasEditorContent"
/>
</div>
<div class="col-span-2">
<!-- 添加 col-span-2 和高度样式 -->
<div class="tabs-container">
<Tabs v-model:activeKey="activeKey" type="card">
<TabPane key="1" tab="Tab 1">
<div class="scroll-container">
<div class="card-container">
<Card
v-for="(item, index) in cardList"
:key="index"
:class="['custom-card', { 'card-selected': selectedCard === item.title }]"
@click="handleCardClick(item.title)"
>
<template #title>{{ item.title }}</template>
{{ item.content }}
</Card>
</div>
</div>
</TabPane>
<TabPane key="2" tab="Tab 2">Content of Tab Pane 2</TabPane>
<TabPane key="3" tab="Tab 3">Content of Tab Pane 3</TabPane>
</Tabs>
</div>
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, onMounted, reactive } from 'vue';
import { PageWrapper } from '@/components/Page';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import { Tabs, TabPane, Card, Dropdown, Menu, MenuItem } from 'ant-design-vue';
import ProjectCard from './components/ProjectCard.vue';
import QuickNav from './components/QuickNav.vue';
import DocumentTasksTable from '@/views/documentReview/DocumentTasks/DocumentTasksTable.vue';
import { useRouter } from 'vue-router';
import { DownOutlined, UserOutlined } from '@ant-design/icons-vue';
import { groupItems } from './components/data';
import CanvasEditor from '@/views/CanvasEditor/index.vue';
const activeKey = ref('1');
//
const cardList = ref([
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
{ title: '标题1', content: '内容1' },
{ title: '标题2', content: '内容2' },
{ title: '标题3', content: '内容3' },
]);
const selectedCard = ref(''); //
const handleCardClick = (title) => {
selectedCard.value = title;
console.log('点击的卡片标题:', title);
};
//
let parentContent = reactive<any>(undefined);
//
const content = ref<any>(undefined);
//
const view = ref<string | undefined>(undefined);
//
onMounted(() => {
console.log('模拟父组件向后端请求数据, 传递给子组件');
getEditorContent();
view.value = 'parent';
});
//
const getEditorContent = () => {
parentContent = {
header: [
{
value: '',
size: 12,
bold: false,
color: 'rgb(33, 53, 71)',
italic: false,
},
],
main: [
{
value:
'父类传递的数据 通过后端获取!\n甲方:企查查科技股份有限公司\n法定代表人:陈德强\n住所:苏州工业园区科创东区东石泾港路2号润港产业园6号楼10层1001室11',
size: 10,
bold: false,
},
],
};
};
//
const canvasEditor = ref<InstanceType<typeof CanvasEditor> | null>(null);
//
const handleSaveContent = () => {
// 访
(canvasEditor.value as any).saveContent();
};
//
const handleSaveCanvasEditorContent = (data: any) => {
console.log('从子组件接收到的数据:', data);
// data json , 便
content.value = JSON.stringify(data);
console.log('转换后的数据 content 为: ', content.value);
};
const router = useRouter();
//
const loading = ref(false);
const handleMenuClick = ({ key }) => {
console.log(key);
router.push({ path: key });
};
//
const fetchTaskList = async () => {};
//
onMounted(() => {
fetchTaskList();
});
setTimeout(() => {
loading.value = false;
}, 1500);
</script>
<style scoped>
/* Tabs 相关样式 */
/* 设置未选中标签的基本样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab) {
color: rgba(0, 0, 0, 0.85) !important; /* 文字颜色为黑色,带透明度 */
background: #fff; /* 背景色为白色 */
border: 1px solid #f0f0f0; /* 浅灰色边框 */
}
/* 设置选中标签的样式 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active) {
color: #ffffff !important; /* 文字颜色为白色 */
background: #1890ff !important; /* 背景色为蓝色 */
border-color: #1890ff !important; /* 边框颜色也改为蓝色 */
}
/* 确保选中标签的文字颜色为白色 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab-active .ant-tabs-tab-btn) {
color: #ffffff !important; /* 文字颜色强制设为白色 */
}
/* 鼠标悬停时的文字颜色效果 */
:deep(.ant-tabs-card > .ant-tabs-nav .ant-tabs-tab:hover .ant-tabs-tab-btn) {
color: #1890ff !important; /* 悬停时文字变为蓝色 */
}
/* Card 相关样式 */
/* 卡片容器布局设置 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
width: 100%;
}
/* 单个卡片的基本样式 */
.custom-card {
width: 100%;
margin-bottom: 16px;
cursor: pointer;
transition: all 0.3s;
background-color: #fff; /* 添加默认背景色 */
border: 1px solid #e8e8e8; /* 添加默认边框 */
}
/* 卡片选中状态 */
.custom-card.card-selected {
background-color: #e6f4ff !important; /* 选中时的淡蓝色背景 */
border: 1px solid #1890ff !important; /* 选中时的蓝色边框 */
}
/* 卡片悬停效果 */
.custom-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
/* 选中状态下的悬停效果 */
.custom-card.card-selected:hover {
background-color: #e6f4ff !important;
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.1);
}
/* 卡片点击效果 */
.custom-card:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* 修改卡片内容区域的内边距 */
:deep(.ant-card-body) {
padding: 16px;
}
/* 滚动容器样式 */
.scroll-container {
height: calc(100vh - 25vh); /* 可以根据实际需要调整高度 */
overflow-y: auto; /* 添加垂直滚动条 */
padding: 16px;
}
/* 美化滚动条样式(可选) */
.scroll-container::-webkit-scrollbar {
width: 6px; /* 滚动条宽度 */
}
.scroll-container::-webkit-scrollbar-thumb {
background-color: #ccc; /* 滚动条颜色 */
border-radius: 3px; /* 滚动条圆角 */
}
.scroll-container::-webkit-scrollbar-track {
background-color: #f1f1f1; /* 滚动条轨道颜色 */
}
/* 保持原有的卡片容器样式 */
.card-container {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
</style>

115
src/views/workbench/index.vue

@ -1,24 +1,51 @@
<template>
<PageWrapper dense>
<div class="parent-test">
<CanvasEditor
ref="canvasEditor"
:parentContent="parentContent"
:view="view"
@save-content="handleSaveCanvasEditorContent"
/>
<PageWrapper>
<template #headerContent> <WorkbenchHeader /> </template>
<div class="workbench">
<div class="h-1/4">
<div class="grid grid-cols-5 gap-4">
<div class="col-span-3 h-full">
<ProjectCard :loading="loading" class="enter-y" />
</div>
<div class="col-span-2 h-full">
<QuickNav :loading="loading" class="enter-y" />
</div>
</div>
<div class="h-1/2 mt-4">
<Card title="任务看板" :bordered="false" style="width: 100%; height: 100%">
<template #extra>
<!-- 将按钮改为下拉菜单 -->
<Dropdown>
<a-button>
更多
<DownOutlined />
</a-button>
<template #overlay>
<Menu @click="handleMenuClick" mode="vertical">
<MenuItem v-for="item in groupItems"
:key="item.path"
>
{{ item.title }}
</MenuItem>
</Menu>
</template>
</Dropdown>
</template>
<button
style="width: 80px; height: 40px; border: 2px solid #2b4b6b; margin-right: 20px; float: right"
@click="handleSaveContent"
>
</button>
</div>
</PageWrapper>
<DocumentTasksTable
:show-table-setting="false"
:show-toolbar="false"
:use-search-form="false"
:pagination="false"
/>
</Card>
</div>
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref, onMounted,reactive } from 'vue';
import { ref, onMounted } from 'vue';
import { PageWrapper } from '@/components/Page';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import { Card, Dropdown, Menu, MenuItem } from 'ant-design-vue';
@ -28,60 +55,6 @@
import { useRouter } from 'vue-router';
import { DownOutlined, UserOutlined } from '@ant-design/icons-vue';
import { groupItems } from './components/data';
import CanvasEditor from '@/views/CanvasEditor/index.vue';
//
let parentContent = reactive<any>(undefined);
//
const content = ref<any>(undefined);
//
const view = ref<string | undefined>(undefined);
//
onMounted(() => {
console.log("模拟父组件向后端请求数据, 传递给子组件");
getEditorContent();
view.value = "parent";
});
//
const getEditorContent = () => {
parentContent={
header: [
{
value: "父类传递的数据",
size: 12,
bold: false,
color: "rgb(33, 53, 71)",
italic: false,
},
],
main: [
{
value: "父类传递的数据 通过后端获取!\n甲方:企查查科技股份有限公司\n法定代表人:陈德强\n住所:苏州工业园区科创东区东石泾港路2号润港产业园6号楼10层1001室11",
size: 40,
bold: true,
},
],
};
};
//
const canvasEditor = ref<InstanceType<typeof CanvasEditor> | null>(null);
//
const handleSaveContent = () => {
// 访
(canvasEditor.value as any).saveContent();
};
//
const handleSaveCanvasEditorContent = (data: any) => {
console.log("从子组件接收到的数据:", data);
// data json , 便
content.value = JSON.stringify(data);
console.log("转换后的数据 content 为: ", content.value);
};
const router = useRouter();
//

Loading…
Cancel
Save