10 changed files with 1732 additions and 32 deletions
@ -0,0 +1,515 @@ |
|||
<template> |
|||
<BasicDrawer |
|||
v-bind="$attrs" |
|||
@register="registerDrawer" |
|||
showFooter |
|||
title="文档审核结果" |
|||
width="80%" |
|||
:canFullscreen="true" |
|||
> |
|||
<template #default> |
|||
<div class="document-review-container"> |
|||
<div class="tasks-container"> |
|||
<Collapse v-model:activeKey="activeCollapseKeys" class="tasks-collapse"> |
|||
<CollapsePanel |
|||
v-for="(category, index) in filteredTaskResultDetail" |
|||
:key="index.toString()" |
|||
:header="category.name + ' (' + category.results.length + ')'" |
|||
> |
|||
<div class="category-items"> |
|||
<Collapse v-model:activeKey="activeItemKeys" class="items-collapse"> |
|||
<CollapsePanel |
|||
v-for="(item, idx) in category.results" |
|||
:key="getItemKey(index, idx, item)" |
|||
> |
|||
<template #header> |
|||
<div class="item-header-content"> |
|||
<div class="item-info"> |
|||
<span class="item-serial">{{ item.serialNumber }}</span> |
|||
<span class="item-title">{{ item.issueName }}</span> |
|||
</div> |
|||
<div class="item-actions"> |
|||
<Switch |
|||
:checked="item.isRead === '1'" |
|||
checked-children="已读" |
|||
un-checked-children="未读" |
|||
:loading="loading && currentOpId === item.id && currentOpField === 'isRead'" |
|||
@change="(checked: any) => handleStatusChange(item.id!, 'isRead', checked ? '1' : '0')" |
|||
/> |
|||
<Switch |
|||
class="ml-3" |
|||
:checked="item.isAdopted === '1'" |
|||
checked-children="已采纳" |
|||
un-checked-children="未采纳" |
|||
:loading="loading && currentOpId === item.id && currentOpField === 'isAdopted'" |
|||
@change="(checked: any) => handleStatusChange(item.id!, 'isAdopted', checked ? '1' : '0')" |
|||
/> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<div class="item-content"> |
|||
<!-- 基于配置动态渲染内容区域 --> |
|||
<template v-for="(section, sectionIndex) in getContentSections(category.type, item)" :key="sectionIndex"> |
|||
<div v-if="getItemValue(item, section.field)" class="content-section"> |
|||
<div class="section-title">{{ section.title }}:</div> |
|||
<div class="section-content markdown-content" v-html="renderContent(section, getItemValue(item, section.field))"></div> |
|||
<!-- 特殊处理审查要点列表 --> |
|||
<div v-if="section.type === 'reviewPointsList' && section.field === 'reviewBasis' && item.reviewBasis && item.reviewBasis.reviewPoints && item.reviewBasis.reviewPoints.length"> |
|||
<Divider style="margin: 8px 0" /> |
|||
<ul class="review-points"> |
|||
<li v-for="(point, pointIdx) in item.reviewBasis.reviewPoints" :key="pointIdx"> |
|||
{{ point }} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</CollapsePanel> |
|||
</Collapse> |
|||
</div> |
|||
</CollapsePanel> |
|||
</Collapse> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<template #footer> |
|||
<div class="drawer-footer"> |
|||
<div class="status-switches"> |
|||
<Switch |
|||
:checked="expandReadItems" |
|||
checked-children="展开已读" |
|||
un-checked-children="折叠已读" |
|||
@change="(checked: any) => toggleExpandReadItems(checked)" |
|||
/> |
|||
<Switch |
|||
class="ml-3" |
|||
:checked="expandAdoptedItems" |
|||
checked-children="展开已采纳" |
|||
un-checked-children="折叠已采纳" |
|||
@change="(checked: any) => toggleExpandAdoptedItems(checked)" |
|||
/> |
|||
</div> |
|||
<Button type="primary" @click="handleClose">关闭</Button> |
|||
</div> |
|||
</template> |
|||
</BasicDrawer> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, watch, onMounted, computed } from 'vue'; |
|||
import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; |
|||
import { DocumentTaskResultDetailVO } from '@/api/documentReview/DocumentTaskResults/model'; |
|||
import { updateResultItemStatus } from '@/api/documentReview/DocumentTaskResults'; |
|||
import { message } from 'ant-design-vue'; |
|||
import { Switch, Tabs, TabPane, Divider, Button, Collapse, CollapsePanel } from 'ant-design-vue'; |
|||
import MarkdownIt from 'markdown-it'; |
|||
|
|||
const md = new MarkdownIt({ |
|||
html: true, |
|||
linkify: true, |
|||
typographer: true, |
|||
}); |
|||
|
|||
const activeCollapseKeys = ref<string[]>([]); |
|||
const activeItemKeys = ref<string[]>([]); |
|||
const taskResultDetail = ref<DocumentTaskResultDetailVO[]>([]); |
|||
const taskInfo = ref<Recordable>({}); |
|||
const loading = ref<boolean>(false); |
|||
const currentOpId = ref<string>(''); |
|||
const currentOpField = ref<string>(''); |
|||
const expandReadItems = ref<boolean>(false); |
|||
const expandAdoptedItems = ref<boolean>(false); |
|||
|
|||
// 内容区域配置,定义不同类型任务显示哪些内容 |
|||
interface ContentSectionConfig { |
|||
field: string; |
|||
title: string; |
|||
type?: 'markdown' | 'text' | 'reviewPointsList'; |
|||
nestedFields?: string[]; |
|||
} |
|||
|
|||
// 全局默认配置,所有类型都默认显示这些字段 |
|||
const defaultContentSections: ContentSectionConfig[] = [ |
|||
{ field: 'originalText', title: '原文', type: 'markdown' }, |
|||
{ field: 'comparedText', title: '比对原文', type: 'markdown' }, |
|||
{ field: 'modifiedContent', title: '修改后内容', type: 'markdown' }, |
|||
{ field: 'modificationDisplay', title: '修改情况', type: 'markdown' }, |
|||
{ field: 'existingIssues', title: '存在问题', type: 'markdown' }, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '审查依据', |
|||
type: 'reviewPointsList', |
|||
nestedFields: ['reviewContent', 'reviewPoints'] |
|||
} |
|||
]; |
|||
|
|||
// 不同类型任务的特定配置,可以覆盖或扩展默认配置 |
|||
const taskTypeContentConfig: Record<string, ContentSectionConfig[]> = { |
|||
// 示例:合同审核特定配置 |
|||
'contract': [ |
|||
{ field: 'originalText', title: '合同原文', type: 'markdown' }, |
|||
{ field: 'legalIssues', title: '法律问题', type: 'markdown' }, |
|||
{ field: 'suggestions', title: '修改建议', type: 'markdown' }, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '法律依据', |
|||
type: 'reviewPointsList', |
|||
nestedFields: ['reviewContent', 'reviewPoints'] |
|||
} |
|||
], |
|||
// 可以添加更多任务类型的配置 |
|||
}; |
|||
|
|||
// 根据任务类型获取内容配置 |
|||
const getContentSections = (taskType: string, item: any): ContentSectionConfig[] => { |
|||
// 如果存在特定类型的配置则使用,否则使用默认配置 |
|||
return taskTypeContentConfig[taskType] || defaultContentSections; |
|||
}; |
|||
|
|||
// 从项目中获取特定字段的值 |
|||
const getItemValue = (item: any, field: string): any => { |
|||
if (!item || !field) return null; |
|||
|
|||
// 支持嵌套字段 如 "reviewBasis.reviewContent" |
|||
if (field.includes('.')) { |
|||
const parts = field.split('.'); |
|||
let value = item; |
|||
for (const part of parts) { |
|||
if (value && typeof value === 'object') { |
|||
value = value[part]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
return item[field]; |
|||
}; |
|||
|
|||
// 根据配置渲染内容 |
|||
const renderContent = (section: ContentSectionConfig, value: any): string => { |
|||
if (!value) return ''; |
|||
|
|||
if (section.type === 'markdown') { |
|||
return renderMarkdown(value); |
|||
} |
|||
|
|||
if (section.type === 'reviewPointsList' && section.nestedFields) { |
|||
let content = ''; |
|||
|
|||
// 渲染reviewContent部分 |
|||
if (section.nestedFields.includes('reviewContent') && value.reviewContent) { |
|||
content += renderMarkdown(value.reviewContent); |
|||
} |
|||
|
|||
return content; |
|||
} |
|||
|
|||
return value.toString(); |
|||
}; |
|||
|
|||
// 根据条件过滤展示的结果 |
|||
const filteredTaskResultDetail = computed(() => { |
|||
if (taskResultDetail.value.length <= 0) { |
|||
return []; |
|||
} |
|||
|
|||
// 如果只有两个或更少的类别,或者第一个类别的results数量<=5个,只展示第一个类别 |
|||
if (taskResultDetail.value.length <= 2 || |
|||
(taskResultDetail.value[0] && taskResultDetail.value[0].results && |
|||
taskResultDetail.value[0].results.length <= 5)) { |
|||
return [taskResultDetail.value[0]]; |
|||
} |
|||
|
|||
return taskResultDetail.value; |
|||
}); |
|||
|
|||
// 生成审核项的唯一key |
|||
const getItemKey = (categoryIndex: number, itemIndex: number, item: any) => { |
|||
return item.id ? item.id : `${categoryIndex}-${itemIndex}`; |
|||
}; |
|||
|
|||
// 根据过滤条件更新折叠状态 |
|||
const updateActiveItemKeys = () => { |
|||
const newActiveKeys: string[] = []; |
|||
|
|||
filteredTaskResultDetail.value.forEach((category, categoryIndex) => { |
|||
category.results.forEach((item, itemIndex) => { |
|||
const itemKey = getItemKey(categoryIndex, itemIndex, item); |
|||
const isRead = item.isRead === '1'; |
|||
const isAdopted = item.isAdopted === '1'; |
|||
|
|||
// 如果项目未读且未采纳,或符合展开条件,则展开 |
|||
if ((!isRead && !isAdopted) || |
|||
(isRead && expandReadItems.value) || |
|||
(isAdopted && expandAdoptedItems.value)) { |
|||
newActiveKeys.push(itemKey); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
activeItemKeys.value = newActiveKeys; |
|||
}; |
|||
|
|||
// 切换展开已读项 |
|||
const toggleExpandReadItems = (checked: any) => { |
|||
expandReadItems.value = !!checked; |
|||
updateActiveItemKeys(); |
|||
}; |
|||
|
|||
// 切换展开已采纳项 |
|||
const toggleExpandAdoptedItems = (checked: any) => { |
|||
expandAdoptedItems.value = !!checked; |
|||
updateActiveItemKeys(); |
|||
}; |
|||
|
|||
const renderMarkdown = (text) => { |
|||
if (!text) return ''; |
|||
return md.render(text); |
|||
}; |
|||
|
|||
const [registerDrawer, { closeDrawer, setDrawerProps }] = useDrawerInner(async (data) => { |
|||
setDrawerProps({ loading: true }); |
|||
try { |
|||
if (data?.taskResultDetail) { |
|||
taskResultDetail.value = data.taskResultDetail; |
|||
taskInfo.value = data.taskInfo || {}; |
|||
|
|||
// 默认展开第一个分类 |
|||
if (taskResultDetail.value.length > 0) { |
|||
activeCollapseKeys.value = ['0']; |
|||
} |
|||
|
|||
// 初始化项目折叠状态 |
|||
updateActiveItemKeys(); |
|||
} |
|||
} catch (error) { |
|||
console.error('初始化详情抽屉时出错', error); |
|||
} finally { |
|||
setDrawerProps({ loading: false }); |
|||
} |
|||
}); |
|||
|
|||
async function handleStatusChange(id: string, field: 'isRead' | 'isAdopted', value: '0' | '1') { |
|||
if (!id) { |
|||
message.error('缺少记录ID,无法更新状态'); |
|||
return; |
|||
} |
|||
|
|||
loading.value = true; |
|||
currentOpId.value = id; |
|||
currentOpField.value = field; |
|||
|
|||
try { |
|||
await updateResultItemStatus(id, field, value); |
|||
message.success(`状态更新成功`); |
|||
|
|||
// 更新本地状态 |
|||
taskResultDetail.value.forEach(category => { |
|||
category.results.forEach(item => { |
|||
if (item.id === id) { |
|||
item[field] = value; |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
// 更新折叠状态 |
|||
updateActiveItemKeys(); |
|||
} catch (error) { |
|||
console.error('更新状态失败', error); |
|||
message.error('更新状态失败'); |
|||
} finally { |
|||
loading.value = false; |
|||
currentOpId.value = ''; |
|||
currentOpField.value = ''; |
|||
} |
|||
} |
|||
|
|||
function handleClose() { |
|||
closeDrawer(); |
|||
} |
|||
|
|||
// 监听状态变化,更新折叠状态 |
|||
watch([expandReadItems, expandAdoptedItems], () => { |
|||
updateActiveItemKeys(); |
|||
}); |
|||
|
|||
onMounted(() => { |
|||
// 初始化后执行的代码 |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
:deep(.ant-drawer-body) { |
|||
padding: 16px; |
|||
height: calc(100% - 100px); |
|||
overflow: auto; |
|||
} |
|||
|
|||
.document-review-container { |
|||
height: 100%; |
|||
} |
|||
|
|||
.tasks-container { |
|||
height: 100%; |
|||
overflow: auto; |
|||
} |
|||
|
|||
.tasks-collapse { |
|||
margin-bottom: 16px; |
|||
|
|||
:deep(.ant-collapse-header) { |
|||
font-weight: 500; |
|||
padding: 12px 16px; |
|||
background-color: #f8f8f8; |
|||
} |
|||
|
|||
:deep(.ant-collapse-content-box) { |
|||
padding: 0; |
|||
} |
|||
} |
|||
|
|||
.items-collapse { |
|||
:deep(.ant-collapse-item) { |
|||
margin-bottom: 12px; |
|||
border: 1px solid #e8e8e8; |
|||
border-radius: 4px !important; |
|||
overflow: hidden; |
|||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); |
|||
background-color: #fff; |
|||
} |
|||
|
|||
:deep(.ant-collapse-header) { |
|||
padding: 8px 16px !important; |
|||
background-color: #f8f8f8; |
|||
} |
|||
} |
|||
|
|||
.item-header-content { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 100%; |
|||
} |
|||
|
|||
.item-info { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.item-serial { |
|||
margin-right: 12px; |
|||
font-weight: bold; |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.item-title { |
|||
font-weight: 500; |
|||
font-size: 16px; |
|||
} |
|||
|
|||
.item-actions { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-left: 16px; |
|||
} |
|||
|
|||
.item-content { |
|||
padding: 16px; |
|||
} |
|||
|
|||
.content-section { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.section-title { |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
color: #333; |
|||
} |
|||
|
|||
.section-content { |
|||
padding: 0 0 0 24px; |
|||
background-color: #f9fbfd; |
|||
border-radius: 4px; |
|||
padding: 12px; |
|||
} |
|||
|
|||
.markdown-content { |
|||
:deep(p) { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
:deep(ul), :deep(ol) { |
|||
padding-left: 24px; |
|||
} |
|||
|
|||
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) { |
|||
margin-top: 16px; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
:deep(code) { |
|||
background-color: #f5f5f5; |
|||
padding: 2px 4px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
:deep(pre) { |
|||
background-color: #f5f5f5; |
|||
padding: 12px; |
|||
border-radius: 4px; |
|||
overflow: auto; |
|||
} |
|||
|
|||
:deep(blockquote) { |
|||
border-left: 4px solid #ddd; |
|||
padding-left: 16px; |
|||
color: #666; |
|||
margin: 16px 0; |
|||
} |
|||
|
|||
:deep(table) { |
|||
border-collapse: collapse; |
|||
width: 100%; |
|||
margin: 16px 0; |
|||
|
|||
th, td { |
|||
border: 1px solid #e8e8e8; |
|||
padding: 8px 12px; |
|||
} |
|||
|
|||
th { |
|||
background-color: #f8f8f8; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.review-points { |
|||
list-style-type: disc; |
|||
padding-left: 24px; |
|||
|
|||
li { |
|||
margin-bottom: 4px; |
|||
} |
|||
} |
|||
|
|||
.ml-3 { |
|||
margin-left: 12px; |
|||
} |
|||
|
|||
.drawer-footer { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 100%; |
|||
} |
|||
|
|||
.status-switches { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
</style> |
@ -0,0 +1,674 @@ |
|||
<template> |
|||
<BasicDrawer |
|||
v-bind="$attrs" |
|||
@register="registerDrawer" |
|||
showFooter |
|||
title="文档审核结果" |
|||
width="50%" |
|||
:canFullscreen="true" |
|||
> |
|||
<template #default> |
|||
<div class="document-review-container"> |
|||
<div class="tasks-container"> |
|||
<Tabs v-model:activeKey="activeCollapseKeys[0]"> |
|||
<TabPane |
|||
v-for="(category, index) in filteredTaskResultDetail" |
|||
:key="index.toString()" |
|||
:tab="category.name + ' (' + category.results.length + ')'" |
|||
> |
|||
<div class="category-items"> |
|||
<div class="items-card-list"> |
|||
<Card |
|||
v-for="(item, idx) in category.results" |
|||
:key="getItemKey(index, idx, item)" |
|||
class="item-card" |
|||
:bordered="true" |
|||
:bodyStyle="{ padding: activeItemKeys.includes(getItemKey(index, idx, item)) ? '16px' : '0', height: activeItemKeys.includes(getItemKey(index, idx, item)) ? 'auto' : '0', overflow: 'hidden', transition: 'all 0.3s' }" |
|||
> |
|||
<template #title> |
|||
<div class="item-header-content" @click="toggleItemExpand(index, idx, item)"> |
|||
<div class="item-info"> |
|||
<span class="item-serial">{{ item.serialNumber }}</span> |
|||
<span class="item-title">{{ item.existingIssues }}</span> |
|||
</div> |
|||
<div class="item-actions"> |
|||
<Switch |
|||
:checked="item.isRead === '1'" |
|||
checked-children="已读" |
|||
un-checked-children="未读" |
|||
:loading="loading && currentOpId === item.id && currentOpField === 'isRead'" |
|||
@change="(checked: any) => handleStatusChange(item.id!, 'isRead', checked ? '1' : '0')" |
|||
@click.stop |
|||
/> |
|||
<Switch |
|||
class="ml-3" |
|||
:checked="item.isAdopted === '1'" |
|||
checked-children="已采纳" |
|||
un-checked-children="未采纳" |
|||
:loading="loading && currentOpId === item.id && currentOpField === 'isAdopted'" |
|||
@change="(checked: any) => handleStatusChange(item.id!, 'isAdopted', checked ? '1' : '0')" |
|||
@click.stop |
|||
/> |
|||
<DownOutlined v-if="!activeItemKeys.includes(getItemKey(index, idx, item))" class="expand-icon ml-3" /> |
|||
<UpOutlined v-else class="expand-icon ml-3" /> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<div class="item-content" v-if="activeItemKeys.includes(getItemKey(index, idx, item))"> |
|||
<!-- 基于配置动态渲染内容区域 --> |
|||
<template v-for="(section, sectionIndex) in getContentSections(receivedTaskType, item)" :key="sectionIndex"> |
|||
<div v-if="getItemValue(item, section.field)" class="content-section"> |
|||
<div class="section-title"> |
|||
{{ section.title }}: |
|||
<Button |
|||
type="link" |
|||
size="small" |
|||
class="copy-btn" |
|||
@click.stop="copyContent(getItemValue(item, section.field))" |
|||
v-if="getItemValue(item, section.field)" |
|||
> |
|||
<CopyOutlined /> 复制 |
|||
</Button> |
|||
</div> |
|||
<div class="section-content markdown-content" v-html="renderContent(section, getItemValue(item, section.field))"></div> |
|||
<!-- 特殊处理审查要点列表 --> |
|||
<div v-if="section.type === 'reviewPointsList' && section.field === 'reviewBasis' && item.reviewBasis && item.reviewBasis.reviewPoints && item.reviewBasis.reviewPoints.length"> |
|||
<Divider style="margin: 8px 0" /> |
|||
<ul class="review-points"> |
|||
<li v-for="(point, pointIdx) in item.reviewBasis.reviewPoints" :key="pointIdx"> |
|||
{{ point }} |
|||
</li> |
|||
</ul> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
</div> |
|||
</Card> |
|||
</div> |
|||
</div> |
|||
</TabPane> |
|||
</Tabs> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
<template #footer> |
|||
<div class="drawer-footer"> |
|||
<div class="status-switches"> |
|||
<Switch |
|||
:checked="expandReadItems" |
|||
checked-children="展开已读" |
|||
un-checked-children="折叠已读" |
|||
@change="(checked: any) => toggleExpandReadItems(checked)" |
|||
/> |
|||
<Switch |
|||
class="ml-3" |
|||
:checked="expandAdoptedItems" |
|||
checked-children="展开已采纳" |
|||
un-checked-children="折叠已采纳" |
|||
@change="(checked: any) => toggleExpandAdoptedItems(checked)" |
|||
/> |
|||
</div> |
|||
<Button type="primary" @click="handleClose">关闭</Button> |
|||
</div> |
|||
</template> |
|||
</BasicDrawer> |
|||
</template> |
|||
|
|||
<script lang="ts" setup> |
|||
import { ref, watch, onMounted, computed, nextTick } from 'vue'; |
|||
import { BasicDrawer, useDrawerInner } from '@/components/Drawer'; |
|||
import { DocumentTaskResultDetailVO } from '@/api/documentReview/DocumentTaskResults/model'; |
|||
import { updateResultItemStatus, DocumentTaskResultDownload } from '@/api/documentReview/DocumentTaskResults'; |
|||
import { message, Alert, Button, Card } from 'ant-design-vue'; |
|||
import { Switch, Divider, Tabs, TabPane } from 'ant-design-vue'; |
|||
import { DownOutlined, UpOutlined, CopyOutlined } from '@ant-design/icons-vue'; |
|||
import MarkdownIt from 'markdown-it'; |
|||
|
|||
const md = new MarkdownIt({ |
|||
html: true, |
|||
linkify: true, |
|||
typographer: true, |
|||
breaks: true, |
|||
}); |
|||
|
|||
const activeCollapseKeys = ref<string[]>(['0']); |
|||
const activeItemKeys = ref<string[]>([]); |
|||
const taskResultDetail = ref<DocumentTaskResultDetailVO[]>([]); |
|||
const taskInfo = ref<Recordable>({}); |
|||
const loading = ref<boolean>(false); |
|||
const currentOpId = ref<string>(''); |
|||
const currentOpField = ref<string>(''); |
|||
const expandReadItems = ref<boolean>(false); |
|||
const expandAdoptedItems = ref<boolean>(false); |
|||
const receivedTaskType = ref<string>(''); |
|||
|
|||
// 内容区域配置,定义不同类型任务显示哪些内容 |
|||
interface ContentSectionConfig { |
|||
field: string; |
|||
title: string; |
|||
type?: 'markdown' | 'text' | 'reviewPointsList'; |
|||
nestedFields?: string[]; |
|||
} |
|||
|
|||
// 全局默认配置,所有类型都默认显示这些字段 |
|||
const defaultContentSections: ContentSectionConfig[] = [ |
|||
{ field: 'originalText', title: '原文', type: 'markdown' }, |
|||
{ field: 'comparedText', title: '比对原文', type: 'markdown' }, |
|||
{ field: 'modifiedContent', title: '修改后内容', type: 'markdown' }, |
|||
{ field: 'modificationDisplay', title: '修改情况', type: 'markdown' }, |
|||
{ field: 'existingIssues', title: '存在问题', type: 'markdown' }, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '审查依据', |
|||
type: 'reviewPointsList', |
|||
nestedFields: ['reviewContent', 'reviewPoints'] |
|||
} |
|||
]; |
|||
|
|||
// 不同类型任务的特定配置,可以覆盖或扩展默认配置 |
|||
const taskTypeContentConfig: Record<string, ContentSectionConfig[]> = { |
|||
// 示例:合同审核特定配置 |
|||
'contract': [ |
|||
{ field: 'originalText', title: '合同原文', type: 'markdown' }, |
|||
{ field: 'legalIssues', title: '法律问题', type: 'markdown' }, |
|||
{ field: 'suggestions', title: '修改建议', type: 'markdown' }, |
|||
{ |
|||
field: 'reviewBasis', |
|||
title: '法律依据', |
|||
type: 'reviewPointsList', |
|||
nestedFields: ['reviewContent', 'reviewPoints'] |
|||
} |
|||
], |
|||
"checkCompanyName": [ |
|||
{ field: 'modificationDisplay', title: '相关原文', type: 'markdown' }, |
|||
], |
|||
"checkTitleName": [ |
|||
{ field: 'modificationDisplay', title: '相关原文', type: 'markdown' }, |
|||
], |
|||
"checkPlaceName": [ |
|||
{ field: 'modificationDisplay', title: '相关原文', type: 'markdown' }, |
|||
], |
|||
"checkRepeatText": [ |
|||
{ field: 'originalText', title: '第一段原文', type: 'markdown' }, |
|||
{ field: 'comparedText', title: '第二段原文', type: 'markdown' }, |
|||
{ field: 'modificationDisplay', title: '相似情况', type: 'markdown' }, |
|||
], |
|||
"checkDocumentError": [ |
|||
{ field: 'originalText', title: '原文', type: 'markdown' }, |
|||
{ field: 'modifiedContent', title: '修改建议', type: 'markdown' }, |
|||
{ field: 'modificationDisplay', title: '修改情况', type: 'markdown' }, |
|||
], |
|||
// 可以添加更多任务类型的配置,如checkDocumentError、schemEvaluation等 |
|||
}; |
|||
|
|||
// 根据任务类型获取内容配置 |
|||
const getContentSections = (taskType: string | undefined, item: any): ContentSectionConfig[] => { |
|||
// 如果未提供类型或类型不存在,使用默认配置 |
|||
if (!taskType) return defaultContentSections; |
|||
// 如果存在特定类型的配置则使用,否则使用默认配置 |
|||
return taskTypeContentConfig[taskType] || defaultContentSections; |
|||
}; |
|||
|
|||
// 从项目中获取特定字段的值 |
|||
const getItemValue = (item: any, field: string): any => { |
|||
if (!item || !field) return null; |
|||
|
|||
// 支持嵌套字段 如 "reviewBasis.reviewContent" |
|||
if (field.includes('.')) { |
|||
const parts = field.split('.'); |
|||
let value = item; |
|||
for (const part of parts) { |
|||
if (value && typeof value === 'object') { |
|||
value = value[part]; |
|||
} else { |
|||
return null; |
|||
} |
|||
} |
|||
return value; |
|||
} |
|||
|
|||
return item[field]; |
|||
}; |
|||
|
|||
// 根据配置渲染内容 |
|||
const renderContent = (section: ContentSectionConfig, value: any): string => { |
|||
if (!value) return ''; |
|||
|
|||
if (section.type === 'markdown') { |
|||
return renderMarkdown(value); |
|||
} |
|||
|
|||
if (section.type === 'reviewPointsList' && section.nestedFields) { |
|||
let content = ''; |
|||
|
|||
// 渲染reviewContent部分 |
|||
if (section.nestedFields.includes('reviewContent') && value.reviewContent) { |
|||
content += renderMarkdown(value.reviewContent); |
|||
} |
|||
|
|||
return content; |
|||
} |
|||
|
|||
return value.toString().replace(/\n/g, '<br>'); |
|||
}; |
|||
|
|||
// 根据条件过滤展示的结果 |
|||
const filteredTaskResultDetail = computed(() => { |
|||
if (taskResultDetail.value.length <= 0) { |
|||
return []; |
|||
} |
|||
|
|||
// 如果只有两个或更少的类别,或者第一个类别的results数量<=5个,只展示第一个类别 |
|||
if (taskResultDetail.value.length <= 2 || |
|||
(taskResultDetail.value[0] && taskResultDetail.value[0].results && |
|||
taskResultDetail.value[0].results.length <= 5)) { |
|||
return [taskResultDetail.value[0]]; |
|||
} |
|||
|
|||
return taskResultDetail.value; |
|||
}); |
|||
|
|||
// 生成审核项的唯一key |
|||
const getItemKey = (categoryIndex: number, itemIndex: number, item: any) => { |
|||
return item.id ? item.id : `${categoryIndex}-${itemIndex}`; |
|||
}; |
|||
|
|||
// 根据过滤条件更新折叠状态 |
|||
const updateActiveItemKeys = () => { |
|||
const newActiveKeys: string[] = []; |
|||
|
|||
filteredTaskResultDetail.value.forEach((category, categoryIndex) => { |
|||
category.results.forEach((item, itemIndex) => { |
|||
const itemKey = getItemKey(categoryIndex, itemIndex, item); |
|||
const isRead = item.isRead === '1'; |
|||
const isAdopted = item.isAdopted === '1'; |
|||
|
|||
// 如果项目未读且未采纳,或符合展开条件,则展开 |
|||
if ((!isRead && !isAdopted) || |
|||
(isRead && expandReadItems.value) || |
|||
(isAdopted && expandAdoptedItems.value)) { |
|||
newActiveKeys.push(itemKey); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
activeItemKeys.value = newActiveKeys; |
|||
}; |
|||
|
|||
// 切换展开已读项 |
|||
const toggleExpandReadItems = (checked: any) => { |
|||
expandReadItems.value = !!checked; |
|||
updateActiveItemKeys(); |
|||
}; |
|||
|
|||
// 切换展开已采纳项 |
|||
const toggleExpandAdoptedItems = (checked: any) => { |
|||
expandAdoptedItems.value = !!checked; |
|||
updateActiveItemKeys(); |
|||
}; |
|||
|
|||
// 切换项目展开/折叠 |
|||
const toggleItemExpand = (categoryIndex: number, itemIndex: number, item: any) => { |
|||
const itemKey = getItemKey(categoryIndex, itemIndex, item); |
|||
const index = activeItemKeys.value.indexOf(itemKey); |
|||
|
|||
if (index > -1) { |
|||
activeItemKeys.value.splice(index, 1); |
|||
} else { |
|||
activeItemKeys.value.push(itemKey); |
|||
} |
|||
}; |
|||
|
|||
const renderMarkdown = (text) => { |
|||
if (!text) return ''; |
|||
|
|||
// 确保在Markdown处理前将普通换行符转换为Markdown换行(添加两个空格后的换行) |
|||
// 这样即使不是段落分隔的换行也能正确显示 |
|||
const processedText = text.replace(/\n/g, ' \n'); |
|||
return md.render(processedText); |
|||
}; |
|||
|
|||
const [registerDrawer, { closeDrawer, setDrawerProps }] = useDrawerInner(async (data) => { |
|||
setDrawerProps({ loading: true }); |
|||
try { |
|||
if (data?.taskResultDetail) { |
|||
// 存储接收到的任务类型 |
|||
receivedTaskType.value = data.taskInfo.taskName || ''; |
|||
console.log('receivedTaskType', receivedTaskType.value); |
|||
// 不再修改每个category的type属性 |
|||
taskResultDetail.value = data.taskResultDetail; |
|||
taskInfo.value = data.taskInfo || {}; |
|||
|
|||
// 默认选中第一个分类 |
|||
if (taskResultDetail.value.length > 0) { |
|||
activeCollapseKeys.value = ['0']; |
|||
} |
|||
|
|||
// 初始化项目折叠状态 |
|||
updateActiveItemKeys(); |
|||
} |
|||
} catch (error) { |
|||
console.error('初始化详情抽屉时出错', error); |
|||
} finally { |
|||
setDrawerProps({ loading: false }); |
|||
} |
|||
}); |
|||
|
|||
async function handleStatusChange(id: string, field: 'isRead' | 'isAdopted', value: '0' | '1') { |
|||
if (!id) { |
|||
message.error('缺少记录ID,无法更新状态'); |
|||
return; |
|||
} |
|||
|
|||
loading.value = true; |
|||
currentOpId.value = id; |
|||
currentOpField.value = field; |
|||
|
|||
try { |
|||
await updateResultItemStatus(id, field, value); |
|||
// message.success(`状态更新成功`); |
|||
|
|||
// 更新本地状态 |
|||
taskResultDetail.value.forEach(category => { |
|||
category.results.forEach(item => { |
|||
if (item.id === id) { |
|||
item[field] = value; |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
// 更新折叠状态 |
|||
updateActiveItemKeys(); |
|||
} catch (error) { |
|||
console.error('更新状态失败', error); |
|||
message.error('更新状态失败'); |
|||
} finally { |
|||
loading.value = false; |
|||
currentOpId.value = ''; |
|||
currentOpField.value = ''; |
|||
} |
|||
} |
|||
|
|||
function handleClose() { |
|||
closeDrawer(); |
|||
} |
|||
|
|||
// 监听状态变化,更新折叠状态 |
|||
watch([expandReadItems, expandAdoptedItems], () => { |
|||
updateActiveItemKeys(); |
|||
}); |
|||
|
|||
// 复制内容到剪贴板 |
|||
const copyContent = (content: any) => { |
|||
if (!content) return; |
|||
|
|||
// 如果是对象,可能需要特殊处理 |
|||
let textToCopy = ''; |
|||
|
|||
if (typeof content === 'object') { |
|||
// 例如 reviewBasis 对象的特殊处理 |
|||
if (content.reviewContent) { |
|||
textToCopy = content.reviewContent; |
|||
|
|||
// 如果有审查要点,也添加到复制内容中 |
|||
if (content.reviewPoints && content.reviewPoints.length) { |
|||
textToCopy += '\n\n审查要点:\n'; |
|||
content.reviewPoints.forEach((point, index) => { |
|||
textToCopy += `${index + 1}. ${point}\n`; |
|||
}); |
|||
} |
|||
} else { |
|||
// 其他对象类型转为JSON字符串 |
|||
try { |
|||
textToCopy = JSON.stringify(content, null, 2); |
|||
} catch (e) { |
|||
textToCopy = String(content); |
|||
} |
|||
} |
|||
} else { |
|||
// 字符串或其他类型直接转换 |
|||
textToCopy = String(content); |
|||
} |
|||
|
|||
// 复制到剪贴板 |
|||
navigator.clipboard.writeText(textToCopy) |
|||
.then(() => { |
|||
message.success('内容已复制到剪贴板'); |
|||
}) |
|||
.catch((err) => { |
|||
console.error('复制失败:', err); |
|||
message.error('复制失败,请手动选择并复制'); |
|||
|
|||
// 备用复制方法(兼容性更好但不太推荐) |
|||
const textarea = document.createElement('textarea'); |
|||
textarea.value = textToCopy; |
|||
document.body.appendChild(textarea); |
|||
textarea.select(); |
|||
try { |
|||
document.execCommand('copy'); |
|||
message.success('内容已复制到剪贴板'); |
|||
} catch (e) { |
|||
console.error('备用复制方法失败:', e); |
|||
message.error('复制失败,请手动选择并复制'); |
|||
} |
|||
document.body.removeChild(textarea); |
|||
}); |
|||
}; |
|||
|
|||
onMounted(() => { |
|||
// 初始化后执行的代码 |
|||
}); |
|||
</script> |
|||
|
|||
<style lang="less" scoped> |
|||
:deep(.ant-drawer-body) { |
|||
padding: 16px; |
|||
height: calc(100% - 100px); |
|||
overflow: auto; |
|||
} |
|||
|
|||
.document-review-container { |
|||
height: 100%; |
|||
} |
|||
|
|||
.tasks-container { |
|||
height: 100%; |
|||
overflow: auto; |
|||
} |
|||
|
|||
:deep(.ant-tabs-nav) { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
:deep(.ant-tabs-tab) { |
|||
padding: 8px 16px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.items-card-list { |
|||
:deep(.ant-card) { |
|||
margin-bottom: 12px; |
|||
border: 1px solid #e8e8e8; |
|||
border-radius: 4px !important; |
|||
overflow: hidden; |
|||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); |
|||
background-color: #fff; |
|||
} |
|||
|
|||
:deep(.ant-card-head) { |
|||
padding: 0 !important; |
|||
background-color: #f8f8f8; |
|||
min-height: 40px; |
|||
|
|||
.ant-card-head-title { |
|||
padding: 8px 16px !important; |
|||
} |
|||
} |
|||
|
|||
:deep(.ant-card-body) { |
|||
transition: all 0.3s ease; |
|||
} |
|||
} |
|||
|
|||
.item-card { |
|||
width: 100%; |
|||
margin-bottom: 10px; |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.expand-icon { |
|||
font-size: 12px; |
|||
color: rgba(0, 0, 0, 0.45); |
|||
} |
|||
|
|||
.item-header-content { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 100%; |
|||
} |
|||
|
|||
.item-info { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.item-serial { |
|||
margin-right: 12px; |
|||
font-weight: bold; |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.item-title { |
|||
font-weight: 500; |
|||
font-size: 14px; |
|||
flex: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
|
|||
.item-actions { |
|||
display: flex; |
|||
align-items: center; |
|||
margin-left: 16px; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.item-content { |
|||
padding: 16px; |
|||
} |
|||
|
|||
.content-section { |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.section-title { |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
color: #333; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.copy-btn { |
|||
padding: 0 4px; |
|||
font-size: 12px; |
|||
height: 24px; |
|||
|
|||
:deep(.anticon) { |
|||
font-size: 12px; |
|||
} |
|||
} |
|||
|
|||
.section-content { |
|||
padding: 0 0 0 24px; |
|||
background-color: #f9fbfd; |
|||
border-radius: 4px; |
|||
padding: 12px; |
|||
white-space: pre-wrap; |
|||
max-height: 300px; |
|||
overflow: auto; |
|||
} |
|||
|
|||
.markdown-content { |
|||
:deep(p) { |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
:deep(ul), :deep(ol) { |
|||
padding-left: 24px; |
|||
} |
|||
|
|||
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) { |
|||
margin-top: 16px; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
:deep(code) { |
|||
background-color: #f5f5f5; |
|||
padding: 2px 4px; |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
:deep(pre) { |
|||
background-color: #f5f5f5; |
|||
padding: 12px; |
|||
border-radius: 4px; |
|||
overflow: auto; |
|||
} |
|||
|
|||
:deep(blockquote) { |
|||
border-left: 4px solid #ddd; |
|||
padding-left: 16px; |
|||
color: #666; |
|||
margin: 16px 0; |
|||
} |
|||
|
|||
:deep(table) { |
|||
border-collapse: collapse; |
|||
width: 100%; |
|||
margin: 16px 0; |
|||
|
|||
th, td { |
|||
border: 1px solid #e8e8e8; |
|||
padding: 8px 12px; |
|||
} |
|||
|
|||
th { |
|||
background-color: #f8f8f8; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.review-points { |
|||
list-style-type: disc; |
|||
padding-left: 24px; |
|||
|
|||
li { |
|||
margin-bottom: 4px; |
|||
} |
|||
} |
|||
|
|||
.ml-3 { |
|||
margin-left: 12px; |
|||
} |
|||
|
|||
.drawer-footer { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
width: 100%; |
|||
} |
|||
|
|||
.status-switches { |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
</style> |
@ -0,0 +1,151 @@ |
|||
import { BasicColumn } from '@/components/Table'; |
|||
import { FormSchema } from '@/components/Form'; |
|||
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('tender_review'), |
|||
mode: 'multiple', |
|||
defaultValue:"inconsistency" |
|||
}, |
|||
}, |
|||
{ |
|||
label: '文档名称', |
|||
field: 'documentName', |
|||
component: 'Input', |
|||
}, |
|||
{ |
|||
label: '状态', |
|||
field: 'progressStatus', |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: getDictOptions('document_task_status'), |
|||
}, |
|||
}, |
|||
]; |
|||
|
|||
const { renderDict } = useRender(); |
|||
export const columns: BasicColumn[] = [ |
|||
{ |
|||
title: '任务名称', |
|||
dataIndex: 'taskName', |
|||
customRender: ({ value }) => renderDict(value, 'tender_review'), |
|||
|
|||
}, |
|||
{ |
|||
title: '文档名称', |
|||
dataIndex: 'documentName', |
|||
}, |
|||
{ |
|||
title: '模型所属区域', |
|||
dataIndex: 'taskRegion', |
|||
customRender: ({ value }) => renderDict(value, 'model_region'), |
|||
}, |
|||
{ |
|||
title: '模型所属行业', |
|||
dataIndex: 'taskIndustry', |
|||
customRender: ({ value }) => renderDict(value, 'model_industry'), |
|||
}, |
|||
{ |
|||
title: '上传时间', |
|||
dataIndex: 'createTime', |
|||
}, |
|||
{ |
|||
title: '提交人', |
|||
dataIndex: 'createUser', |
|||
auth: 'documentReview:DocumentTasks:tableShow', |
|||
}, |
|||
{ |
|||
title: '任务耗时', |
|||
dataIndex: 'taskDuration', |
|||
}, |
|||
{ |
|||
title: '状态', |
|||
dataIndex: 'progressStatus', |
|||
customRender: ({ value }) => renderDict(value, 'document_task_status'), |
|||
}, |
|||
]; |
|||
|
|||
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: 'taskNameList', |
|||
required: true, |
|||
component: 'Select', |
|||
componentProps: { |
|||
options: getDictOptions('tender_review') , |
|||
mode: 'multiple', |
|||
}, |
|||
}, |
|||
{ |
|||
label: '文档名称', |
|||
field: 'ossId', |
|||
required: true, |
|||
component: 'Upload', |
|||
componentProps: { |
|||
accept: ['.docx', '.doc', '.wps'], |
|||
maxSize: 500, |
|||
multiple: false, |
|||
resultField: 'ossId', |
|||
api: uploadDocument, |
|||
beforeUploadPrompt:"严禁在本互联网非涉密平台处理、传输国家秘密。请再次确认您上传的文件资料不涉及国家秘密。" |
|||
}, |
|||
}, |
|||
]; |
@ -0,0 +1,72 @@ |
|||
<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 { TenderTaskInfo, TenderTaskAdd, TenderTaskUpdate } from '@/api/tenderReview/TenderTask'; |
|||
import { modalSchemas } from './TenderTask.data'; |
|||
import { ModelUserPromptssettingInfoByUserId } from '@/api/modelConfiguration/ModelUserPromptssetting/index'; |
|||
|
|||
defineOptions({ name: 'TenderTaskModal' }); |
|||
|
|||
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 settings = await ModelUserPromptssettingInfoByUserId(); |
|||
await setFieldsValue(settings); |
|||
const { record, update } = data; |
|||
isUpdate.value = update; |
|||
if (update && record) { |
|||
await setFieldsValue(record); |
|||
} |
|||
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(); |
|||
data['ossId'] = data['ossId'][0]; |
|||
if (unref(isUpdate)) { |
|||
await TenderTaskUpdate(data); |
|||
} else { |
|||
await TenderTaskAdd(data); |
|||
} |
|||
emit('reload'); |
|||
closeModal(); |
|||
await resetForm(); |
|||
} catch (e) { |
|||
} finally { |
|||
modalLoading(false); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style scoped></style> |
@ -0,0 +1,181 @@ |
|||
<template> |
|||
<PageWrapper dense> |
|||
<BasicTable @register="registerTable"> |
|||
<template #toolbar> |
|||
<a-button |
|||
@click=" |
|||
downloadExcel(TenderTaskExport, '招标摘要任务数据', getForm().getFieldsValue()) |
|||
" |
|||
v-auth="'productManagement:TenderTask:export'" |
|||
>导出</a-button |
|||
> |
|||
<a-button |
|||
type="primary" |
|||
danger |
|||
@click="multipleRemove(TenderTaskRemove)" |
|||
:disabled="!selected" |
|||
v-auth="'productManagement:TenderTask:remove'" |
|||
>删除</a-button |
|||
> |
|||
<a-button |
|||
type="primary" |
|||
@click="handleAdd" |
|||
v-auth="'productManagement:TenderTask: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> |
|||
<TenderTaskModal @register="registerModal" @reload="reload" /> |
|||
<DocsDrawer @register="registerDrawer" /> |
|||
</PageWrapper> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { PageWrapper } from '@/components/Page'; |
|||
import { BasicTable, useTable, TableAction } from '@/components/Table'; |
|||
import { |
|||
TenderTaskList, |
|||
TenderTaskExport, |
|||
TenderTaskRemove, |
|||
} from '@/api/tenderReview/TenderTask'; |
|||
import { downloadExcel } from '@/utils/file/download'; |
|||
import { useModal } from '@/components/Modal'; |
|||
import TenderTaskModal from './TenderTaskModal.vue'; |
|||
import { formSchemas, columns } from './TenderTask.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 { |
|||
DocumentTaskResultsInfoByTaskId, |
|||
DocumentTaskResultDownload, |
|||
} from '@/api/documentReview/DocumentTaskResults'; |
|||
const [registerDrawer, { openDrawer }] = useDrawer(); |
|||
|
|||
defineOptions({ name: 'TenderTask' }); |
|||
|
|||
const [registerTable, { reload, multipleRemove, selected, getForm }] = useTable({ |
|||
rowSelection: { |
|||
type: 'checkbox', |
|||
}, |
|||
title: '招标审核任务列表', |
|||
api: TenderTaskList, |
|||
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) { |
|||
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' }); |
|||
} |
|||
//根据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(); |
|||
} |
|||
async function handleDelete(record: Recordable) { |
|||
await TenderTaskRemove([record.id]); |
|||
await reload(); |
|||
} |
|||
</script> |
|||
|
|||
<style scoped></style> |
Loading…
Reference in new issue