|
|
@ -3,17 +3,618 @@ |
|
|
|
<div class="page-header"> |
|
|
|
<h2 class="page-title">审查清单</h2> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="checklist-content"> |
|
|
|
<div class="empty-state"> |
|
|
|
<div class="empty-icon">📋</div> |
|
|
|
<p>功能开发中,敬请期待...</p> |
|
|
|
<div class="container"> |
|
|
|
<!-- 左侧合同类型 --> |
|
|
|
<div class="rule-category w-64 border-r border-gray-200 p-4"> |
|
|
|
<div class="category-title font-bold text-lg mb-4">合同类型</div> |
|
|
|
<Button type="primary" class="w-full mb-4" @click="handleCreateCategory"> |
|
|
|
<template #icon><PlusOutlined /></template> |
|
|
|
新建 |
|
|
|
</Button> |
|
|
|
<div class="category-list overflow-y-auto"> |
|
|
|
<!-- 合同类型加载状态 --> |
|
|
|
<div v-if="categoriesLoading" class="loading-container"> |
|
|
|
<Spin size="small" /> |
|
|
|
<span class="ml-2">正在加载合同类型...</span> |
|
|
|
</div> |
|
|
|
<!-- 合同类型列表 --> |
|
|
|
<div v-else> |
|
|
|
<div |
|
|
|
v-for="category in ruleCategories" |
|
|
|
:key="category.id" |
|
|
|
:class="['category-item p-3 cursor-pointer rounded mb-2 flex justify-between', |
|
|
|
{ 'selected': selectedCategory === String(category.id) }]" |
|
|
|
> |
|
|
|
<div class="flex-1" @click="() => category.id !== undefined && selectCategory(category.id)"> |
|
|
|
{{ category.contractName }} |
|
|
|
</div> |
|
|
|
<Button |
|
|
|
type="text" |
|
|
|
size="small" |
|
|
|
danger |
|
|
|
@click.stop="confirmDeleteCategory(category)" |
|
|
|
v-if="ruleCategories.length > 1" |
|
|
|
> |
|
|
|
<template #icon><DeleteOutlined /></template> |
|
|
|
</Button> |
|
|
|
</div> |
|
|
|
<!-- 无数据状态 --> |
|
|
|
<div v-if="ruleCategories.length === 0" class="empty-state-small"> |
|
|
|
<div class="text-gray-400 text-center py-4">暂无合同类型</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 右侧审查要点 --> |
|
|
|
<div class="rule-detail flex-1 flex flex-col"> |
|
|
|
<!-- 上部分:审查要点列表 --> |
|
|
|
<div class="rule-list-section flex-1 p-4"> |
|
|
|
<div class="review-header flex justify-between items-center mb-4"> |
|
|
|
<div class="text-lg font-bold">审查要点</div> |
|
|
|
<div class="actions flex space-x-2"> |
|
|
|
<Button type="primary" danger @click="handleBatchDelete" :disabled="!hasChecked"> |
|
|
|
<template #icon><DeleteOutlined /></template> |
|
|
|
批量删除 |
|
|
|
</Button> |
|
|
|
<Button type="primary" @click="handleAddPoint"> |
|
|
|
<template #icon><PlusOutlined /></template> |
|
|
|
新建要点 |
|
|
|
</Button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div class="rule-list overflow-y-auto"> |
|
|
|
<!-- 审查要点加载状态 --> |
|
|
|
<div v-if="rulesLoading" class="loading-container"> |
|
|
|
<Spin size="small" /> |
|
|
|
<span class="ml-2">正在加载审查要点...</span> |
|
|
|
</div> |
|
|
|
<!-- 审查要点列表 --> |
|
|
|
<div v-else> |
|
|
|
<div v-for="rule in filteredRules" :key="rule.id" class="rule-item mb-3 border border-gray-200 rounded"> |
|
|
|
<div class="rule-header flex items-center p-2"> |
|
|
|
<Checkbox v-model:checked="rule.checked" class="mr-2"></Checkbox> |
|
|
|
<div :class="['risk-level px-2 py-1 rounded mr-3 text-sm', riskLevelClass(rule.riskLevel)]"> |
|
|
|
{{ riskLevelText(rule.riskLevel) }} |
|
|
|
</div> |
|
|
|
<div class="rule-title flex-1 cursor-pointer" @click="handleSelectRule(rule)">{{ rule.title }}</div> |
|
|
|
<div class="action-buttons flex space-x-1"> |
|
|
|
<Button type="link" size="small" danger @click="confirmDeleteRule(rule)">删除</Button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 无数据状态 --> |
|
|
|
<div v-if="!selectedCategory" class="empty-state-small"> |
|
|
|
<div class="text-gray-400 text-center py-4">请先选择合同类型</div> |
|
|
|
</div> |
|
|
|
<div v-else-if="filteredRules.length === 0" class="empty-state-small"> |
|
|
|
<div class="text-gray-400 text-center py-4">暂无审查要点</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 下部分:详情/编辑区域 --> |
|
|
|
<div class="rule-detail-section border-t border-gray-200 p-2"> |
|
|
|
<div v-if="!selectedRule && !isAddingNew" class="empty-state text-center py-4"> |
|
|
|
<div class="text-gray-400 text-lg mb-2">📋</div> |
|
|
|
<div class="text-gray-500">请选择一个审查要点查看详情,或点击"新建要点"添加新的审查要点</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<div v-else class="rule-detail-content"> |
|
|
|
<div class="detail-header flex justify-between items-center mb-2"> |
|
|
|
<h3 class="text-lg font-bold"> |
|
|
|
{{ isAddingNew ? '新建审查要点' : (isEditing ? '编辑审查要点' : '审查要点详情') }} |
|
|
|
</h3> |
|
|
|
<div class="actions flex space-x-2"> |
|
|
|
<Button v-if="!isEditing && !isAddingNew" type="primary" @click="handleEdit">编辑</Button> |
|
|
|
<Button v-if="isEditing || isAddingNew" type="primary" @click="handleSave">保存</Button> |
|
|
|
<Button v-if="isEditing || isAddingNew" @click="handleCancel">取消</Button> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<Form |
|
|
|
:model="editFormData" |
|
|
|
ref="editFormRef" |
|
|
|
:label-col="{ span: 3 }" |
|
|
|
:wrapper-col="{ span: 21 }" |
|
|
|
class="detail-form" |
|
|
|
:rules="formRules" |
|
|
|
> |
|
|
|
<FormItem label="要点名称" name="title"> |
|
|
|
<Input |
|
|
|
v-if="isEditing || isAddingNew" |
|
|
|
v-model:value="editFormData.title" |
|
|
|
placeholder="请输入要点名称" |
|
|
|
/> |
|
|
|
<span v-else>{{ selectedRule?.title }}</span> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="合同类型" name="typeId"> |
|
|
|
<Select |
|
|
|
v-if="isEditing || isAddingNew" |
|
|
|
v-model:value="editFormData.typeId" |
|
|
|
placeholder="请选择合同类型" |
|
|
|
> |
|
|
|
<SelectOption v-for="category in ruleCategories" :key="category.id" :value="String(category.id)"> |
|
|
|
{{ category.contractName }} |
|
|
|
</SelectOption> |
|
|
|
</Select> |
|
|
|
<span v-else>{{ getCategoryName(selectedRule?.typeId) }}</span> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="风险等级" name="riskLevel"> |
|
|
|
<Select |
|
|
|
v-if="isEditing || isAddingNew" |
|
|
|
v-model:value="editFormData.riskLevel" |
|
|
|
placeholder="请选择风险等级" |
|
|
|
> |
|
|
|
<SelectOption value="H">高风险</SelectOption> |
|
|
|
<SelectOption value="M">中风险</SelectOption> |
|
|
|
<SelectOption value="L">低风险</SelectOption> |
|
|
|
</Select> |
|
|
|
<span v-else-if="selectedRule" :class="['risk-badge px-2 py-1 rounded text-sm', riskLevelClass(selectedRule.riskLevel)]"> |
|
|
|
{{ riskLevelText(selectedRule.riskLevel) }} |
|
|
|
</span> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="排序" name="sortOrder"> |
|
|
|
<InputNumber |
|
|
|
v-if="isEditing || isAddingNew" |
|
|
|
v-model:value="editFormData.sortOrder" |
|
|
|
placeholder="请输入排序" |
|
|
|
style="width: 200px" |
|
|
|
/> |
|
|
|
<span v-else>{{ selectedRule?.sortOrder }}</span> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="要点描述" name="description"> |
|
|
|
<Textarea |
|
|
|
v-if="isEditing || isAddingNew" |
|
|
|
v-model:value="editFormData.description" |
|
|
|
placeholder="请输入要点描述" |
|
|
|
:auto-size="{ minRows: 2, maxRows: 4 }" |
|
|
|
/> |
|
|
|
<div v-else class="description-content">{{ selectedRule?.description }}</div> |
|
|
|
</FormItem> |
|
|
|
</Form> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 删除确认模态框 --> |
|
|
|
<Modal |
|
|
|
v-model:visible="deleteModalVisible" |
|
|
|
:title="deleteModalTitle" |
|
|
|
@ok="handleDeleteConfirm" |
|
|
|
@cancel="deleteModalVisible = false" |
|
|
|
okText="确认删除" |
|
|
|
cancelText="取消" |
|
|
|
okType="danger" |
|
|
|
> |
|
|
|
<p>{{ deleteModalContent }}</p> |
|
|
|
</Modal> |
|
|
|
|
|
|
|
<!-- 合同类型表单模态框 --> |
|
|
|
<Modal |
|
|
|
v-model:visible="categoryModalVisible" |
|
|
|
:title="categoryModalTitle" |
|
|
|
@ok="handleCategorySave" |
|
|
|
@cancel="categoryModalVisible = false" |
|
|
|
okText="保存" |
|
|
|
cancelText="取消" |
|
|
|
> |
|
|
|
<Form |
|
|
|
:model="categoryForm" |
|
|
|
ref="categoryFormRef" |
|
|
|
:label-col="{ span: 4 }" |
|
|
|
:wrapper-col="{ span: 20 }" |
|
|
|
> |
|
|
|
<FormItem label="类型名称" name="contractName" :rules="[{ required: true, message: '请输入类型名称' }]"> |
|
|
|
<Input v-model:value="categoryForm.contractName" placeholder="请输入类型名称" /> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="显示顺序" name="sort"> |
|
|
|
<InputNumber v-model:value="categoryForm.sort" placeholder="请输入显示顺序" style="width: 100%" /> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="状态" name="status"> |
|
|
|
<Select v-model:value="categoryForm.status" placeholder="请选择状态"> |
|
|
|
<SelectOption value="0">正常</SelectOption> |
|
|
|
<SelectOption value="1">停用</SelectOption> |
|
|
|
</Select> |
|
|
|
</FormItem> |
|
|
|
|
|
|
|
<FormItem label="备注" name="remark"> |
|
|
|
<Textarea v-model:value="categoryForm.remark" placeholder="请输入备注信息" :rows="4" /> |
|
|
|
</FormItem> |
|
|
|
</Form> |
|
|
|
</Modal> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup lang="ts"> |
|
|
|
// 组件逻辑 |
|
|
|
import { ref, computed, onMounted, reactive } from 'vue'; |
|
|
|
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons-vue'; |
|
|
|
import { Button, Checkbox, Modal, message, Form, FormItem, Input, InputNumber, Select, SelectOption, Textarea, Spin } from 'ant-design-vue'; |
|
|
|
import { FormInstance } from 'ant-design-vue/es/form'; |
|
|
|
import { ContractualTaskChecklistList, ContractualTaskChecklistRemove, ContractualTaskChecklistAdd, ContractualTaskChecklistUpdate } from '@/api/contractReview/ContractualTaskChecklist'; |
|
|
|
import { ContractualTaskTypeList, ContractualTaskTypeAdd, ContractualTaskTypeUpdate, ContractualTaskTypeRemove } from '@/api/contractReview/ContractualTaskType'; |
|
|
|
import { ContractualTaskChecklistVO, ContractualTaskChecklistForm } from '@/api/contractReview/ContractualTaskChecklist/model'; |
|
|
|
import { ContractualTaskTypeVO, ContractualTaskTypeForm } from '@/api/contractReview/ContractualTaskType/model'; |
|
|
|
|
|
|
|
defineOptions({ name: 'ChecklistPage' }); |
|
|
|
|
|
|
|
// 类型扩展,添加checked字段 |
|
|
|
type CheckableRule = ContractualTaskChecklistVO & { checked: boolean }; |
|
|
|
|
|
|
|
// 合同类型相关状态 |
|
|
|
const ruleCategories = ref<ContractualTaskTypeVO[]>([]); |
|
|
|
const selectedCategory = ref<string | number>(''); |
|
|
|
const categoryModalVisible = ref(false); |
|
|
|
const categoryModalTitle = ref('新增合同类型'); |
|
|
|
const categoryForm = reactive<ContractualTaskTypeForm>({ |
|
|
|
id: undefined, |
|
|
|
contractName: '', |
|
|
|
sort: 0, |
|
|
|
status: '0', |
|
|
|
remark: '' |
|
|
|
}); |
|
|
|
const categoryFormRef = ref<FormInstance>(); |
|
|
|
const categoriesLoading = ref(false); // 合同类型加载状态 |
|
|
|
|
|
|
|
// 审查要点相关状态 |
|
|
|
const rules = ref<CheckableRule[]>([]); |
|
|
|
const selectedRule = ref<CheckableRule | null>(null); |
|
|
|
const isEditing = ref(false); |
|
|
|
const isAddingNew = ref(false); |
|
|
|
const editFormData = reactive<ContractualTaskChecklistForm>({ |
|
|
|
id: undefined, |
|
|
|
title: '', |
|
|
|
typeId: '', |
|
|
|
riskLevel: 'M', |
|
|
|
sortOrder: 0, |
|
|
|
description: '' |
|
|
|
}); |
|
|
|
const editFormRef = ref<FormInstance>(); |
|
|
|
const rulesLoading = ref(false); // 审查要点加载状态 |
|
|
|
|
|
|
|
// 表单验证规则 |
|
|
|
const formRules: any = { |
|
|
|
title: [{ required: true, message: '请输入要点名称', trigger: 'blur' }], |
|
|
|
typeId: [{ required: true, message: '请选择合同类型', trigger: 'change' }], |
|
|
|
riskLevel: [{ required: true, message: '请选择风险等级', trigger: 'change' }], |
|
|
|
description: [{ required: true, message: '请输入要点描述', trigger: 'blur' }], |
|
|
|
}; |
|
|
|
|
|
|
|
// 删除模态框相关状态 |
|
|
|
const deleteModalVisible = ref(false); |
|
|
|
const deleteModalTitle = ref(''); |
|
|
|
const deleteModalContent = ref(''); |
|
|
|
const itemToDelete = ref<any>(null); |
|
|
|
const deleteType = ref<'category' | 'rule'>('rule'); |
|
|
|
|
|
|
|
// 是否有选中的要点 |
|
|
|
const hasChecked = computed(() => { |
|
|
|
return rules.value.some(r => r.checked); |
|
|
|
}); |
|
|
|
|
|
|
|
// 根据分类过滤规则 |
|
|
|
const filteredRules = computed(() => { |
|
|
|
if (!selectedCategory.value) return []; |
|
|
|
return rules.value.filter(rule => { |
|
|
|
return String(rule.typeId) === String(selectedCategory.value); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// 加载数据 |
|
|
|
onMounted(async () => { |
|
|
|
await loadCategories(); |
|
|
|
}); |
|
|
|
|
|
|
|
// 加载合同类型列表 |
|
|
|
const loadCategories = async () => { |
|
|
|
categoriesLoading.value = true; |
|
|
|
try { |
|
|
|
const res = await ContractualTaskTypeList({}); |
|
|
|
const data = res as any; |
|
|
|
console.log('合同类型数据:', data); |
|
|
|
if (data && data.length > 0) { |
|
|
|
const currentSelectedId = selectedCategory.value; |
|
|
|
|
|
|
|
ruleCategories.value = data; |
|
|
|
|
|
|
|
const shouldSelectFirst = !currentSelectedId || |
|
|
|
!ruleCategories.value.some(item => String(item.id) === currentSelectedId); |
|
|
|
|
|
|
|
if (shouldSelectFirst && ruleCategories.value.length > 0 && ruleCategories.value[0].id) { |
|
|
|
selectedCategory.value = String(ruleCategories.value[0].id); |
|
|
|
} else if (currentSelectedId) { |
|
|
|
selectedCategory.value = currentSelectedId; |
|
|
|
} |
|
|
|
|
|
|
|
await loadRules(); |
|
|
|
} else { |
|
|
|
ruleCategories.value = []; |
|
|
|
rules.value = []; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('加载合同类型失败', error); |
|
|
|
message.error('加载合同类型失败'); |
|
|
|
} finally { |
|
|
|
categoriesLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 加载审查要点列表 |
|
|
|
const loadRules = async () => { |
|
|
|
if (!selectedCategory.value) { |
|
|
|
rules.value = []; |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
rulesLoading.value = true; |
|
|
|
try { |
|
|
|
const res = await ContractualTaskChecklistList({ typeId: selectedCategory.value }); |
|
|
|
const data = res as any; |
|
|
|
console.log('审查要点数据:', data); |
|
|
|
if (data && data.length > 0) { |
|
|
|
rules.value = data.map(item => ({ |
|
|
|
...item, |
|
|
|
checked: false |
|
|
|
})); |
|
|
|
} else { |
|
|
|
rules.value = []; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('加载审查要点失败', error); |
|
|
|
message.error('加载审查要点失败'); |
|
|
|
} finally { |
|
|
|
rulesLoading.value = false; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 选择分类 |
|
|
|
const selectCategory = async (categoryId: string | number) => { |
|
|
|
if (categoryId !== undefined) { |
|
|
|
selectedCategory.value = String(categoryId); |
|
|
|
selectedRule.value = null; |
|
|
|
isEditing.value = false; |
|
|
|
isAddingNew.value = false; |
|
|
|
await loadRules(); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 选择审查要点 |
|
|
|
const handleSelectRule = (rule: CheckableRule) => { |
|
|
|
selectedRule.value = rule; |
|
|
|
isEditing.value = false; |
|
|
|
editFormData.id = rule.id; |
|
|
|
editFormData.title = rule.title || ''; |
|
|
|
editFormData.typeId = rule.typeId ? String(rule.typeId) : ''; |
|
|
|
editFormData.riskLevel = rule.riskLevel || 'M'; |
|
|
|
editFormData.sortOrder = rule.sortOrder || 0; |
|
|
|
editFormData.description = rule.description || ''; |
|
|
|
}; |
|
|
|
|
|
|
|
// 风险等级样式 |
|
|
|
const riskLevelClass = (level: string) => { |
|
|
|
switch (level) { |
|
|
|
case 'H': |
|
|
|
case '高风险': |
|
|
|
return 'bg-red-100 text-red-600'; |
|
|
|
case 'M': |
|
|
|
case '中风险': |
|
|
|
return 'bg-orange-100 text-orange-600'; |
|
|
|
case 'L': |
|
|
|
case '低风险': |
|
|
|
return 'bg-green-100 text-green-600'; |
|
|
|
default: |
|
|
|
return ''; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 风险等级文本显示 |
|
|
|
const riskLevelText = (level: string) => { |
|
|
|
switch (level) { |
|
|
|
case 'H': |
|
|
|
return '高风险'; |
|
|
|
case 'M': |
|
|
|
return '中风险'; |
|
|
|
case 'L': |
|
|
|
return '低风险'; |
|
|
|
case '高风险': |
|
|
|
case '中风险': |
|
|
|
case '低风险': |
|
|
|
return level; |
|
|
|
default: |
|
|
|
return level; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 新建审查要点 |
|
|
|
const handleAddPoint = () => { |
|
|
|
editFormData.id = undefined; |
|
|
|
editFormData.title = ''; |
|
|
|
editFormData.typeId = selectedCategory.value || ''; |
|
|
|
editFormData.riskLevel = 'M'; |
|
|
|
editFormData.sortOrder = 0; |
|
|
|
editFormData.description = ''; |
|
|
|
|
|
|
|
selectedRule.value = null; |
|
|
|
isAddingNew.value = true; |
|
|
|
isEditing.value = false; |
|
|
|
}; |
|
|
|
|
|
|
|
// 统一的保存方法 |
|
|
|
const handleSave = async () => { |
|
|
|
try { |
|
|
|
await editFormRef.value?.validate(); |
|
|
|
|
|
|
|
if (isAddingNew.value) { |
|
|
|
await ContractualTaskChecklistAdd(editFormData); |
|
|
|
message.success('新增审查要点成功'); |
|
|
|
} else { |
|
|
|
await ContractualTaskChecklistUpdate(editFormData); |
|
|
|
message.success('更新审查要点成功'); |
|
|
|
} |
|
|
|
|
|
|
|
isEditing.value = false; |
|
|
|
isAddingNew.value = false; |
|
|
|
selectedRule.value = null; |
|
|
|
|
|
|
|
await loadRules(); |
|
|
|
} catch (error) { |
|
|
|
console.error('保存审查要点失败', error); |
|
|
|
message.error('保存审查要点失败'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 统一的取消方法 |
|
|
|
const handleCancel = () => { |
|
|
|
isEditing.value = false; |
|
|
|
isAddingNew.value = false; |
|
|
|
|
|
|
|
if (isAddingNew.value) { |
|
|
|
selectedRule.value = null; |
|
|
|
} else if (selectedRule.value) { |
|
|
|
editFormData.title = selectedRule.value.title || ''; |
|
|
|
editFormData.typeId = selectedRule.value.typeId ? String(selectedRule.value.typeId) : ''; |
|
|
|
editFormData.riskLevel = selectedRule.value.riskLevel || 'M'; |
|
|
|
editFormData.sortOrder = selectedRule.value.sortOrder || 0; |
|
|
|
editFormData.description = selectedRule.value.description || ''; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 确认删除审查要点 |
|
|
|
const confirmDeleteRule = (rule: CheckableRule) => { |
|
|
|
deleteModalTitle.value = '删除审查要点'; |
|
|
|
deleteModalContent.value = `确定要删除审查要点 "${rule.title}" 吗?此操作不可恢复。`; |
|
|
|
itemToDelete.value = rule; |
|
|
|
deleteType.value = 'rule'; |
|
|
|
deleteModalVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 确认删除合同类型 |
|
|
|
const confirmDeleteCategory = async (category: ContractualTaskTypeVO) => { |
|
|
|
try { |
|
|
|
const res = await ContractualTaskChecklistList({ typeId: category.id }); |
|
|
|
const data = res as any; |
|
|
|
if (data?.data?.rows && data.data.rows.length > 0) { |
|
|
|
message.warning(`无法删除 "${category.contractName}",该类型下有 ${data.data.rows.length} 个审查要点。请先删除或移动其中的审查要点。`); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
deleteModalTitle.value = '删除合同类型'; |
|
|
|
deleteModalContent.value = `确定要删除合同类型 "${category.contractName}" 吗?此操作不可恢复。`; |
|
|
|
itemToDelete.value = category; |
|
|
|
deleteType.value = 'category'; |
|
|
|
deleteModalVisible.value = true; |
|
|
|
} catch (error) { |
|
|
|
console.error('检查合同类型下审查要点失败', error); |
|
|
|
message.error('操作失败'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 处理删除确认 |
|
|
|
const handleDeleteConfirm = async () => { |
|
|
|
try { |
|
|
|
if (deleteType.value === 'rule') { |
|
|
|
if (itemToDelete.value.batchDelete) { |
|
|
|
const selectedIds = rules.value |
|
|
|
.filter(r => r.checked && r.id !== undefined) |
|
|
|
.map(r => String(r.id)); |
|
|
|
|
|
|
|
if (selectedIds.length > 0) { |
|
|
|
const idsParam = selectedIds.join(','); |
|
|
|
await ContractualTaskChecklistRemove(idsParam); |
|
|
|
message.success(`成功删除 ${selectedIds.length} 个审查要点`); |
|
|
|
} |
|
|
|
await loadRules(); |
|
|
|
} else { |
|
|
|
if (itemToDelete.value.id !== undefined) { |
|
|
|
await ContractualTaskChecklistRemove(String(itemToDelete.value.id)); |
|
|
|
message.success('审查要点删除成功'); |
|
|
|
} |
|
|
|
await loadRules(); |
|
|
|
} |
|
|
|
deleteModalVisible.value = false; |
|
|
|
} else { |
|
|
|
if (itemToDelete.value.id !== undefined) { |
|
|
|
await ContractualTaskTypeRemove(String(itemToDelete.value.id)); |
|
|
|
message.success('合同类型删除成功'); |
|
|
|
await loadCategories(); |
|
|
|
} |
|
|
|
deleteModalVisible.value = false; |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('删除操作失败', error); |
|
|
|
message.error('删除操作失败'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 批量删除选中的审查要点 |
|
|
|
const handleBatchDelete = () => { |
|
|
|
const selectedPoints = rules.value.filter(r => r.checked); |
|
|
|
if (selectedPoints.length === 0) { |
|
|
|
message.warning('请先选择要删除的审查要点'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
deleteModalTitle.value = '批量删除审查要点'; |
|
|
|
deleteModalContent.value = `确定要删除选中的 ${selectedPoints.length} 个审查要点吗?此操作不可恢复。`; |
|
|
|
deleteType.value = 'rule'; |
|
|
|
deleteModalVisible.value = true; |
|
|
|
itemToDelete.value = { batchDelete: true }; |
|
|
|
}; |
|
|
|
|
|
|
|
// 创建合同类型 |
|
|
|
const handleCreateCategory = () => { |
|
|
|
categoryForm.id = undefined; |
|
|
|
categoryForm.contractName = ''; |
|
|
|
categoryForm.sort = 0; |
|
|
|
categoryForm.status = '0'; |
|
|
|
categoryForm.remark = ''; |
|
|
|
categoryModalTitle.value = '新增合同类型'; |
|
|
|
categoryModalVisible.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 保存合同类型 |
|
|
|
const handleCategorySave = async () => { |
|
|
|
try { |
|
|
|
await categoryFormRef.value?.validate(); |
|
|
|
|
|
|
|
if (categoryForm.id) { |
|
|
|
await ContractualTaskTypeUpdate(categoryForm); |
|
|
|
message.success('更新合同类型成功'); |
|
|
|
} else { |
|
|
|
await ContractualTaskTypeAdd(categoryForm); |
|
|
|
message.success('新增合同类型成功'); |
|
|
|
} |
|
|
|
|
|
|
|
categoryModalVisible.value = false; |
|
|
|
await loadCategories(); |
|
|
|
} catch (error) { |
|
|
|
console.error('保存合同类型失败', error); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 开始编辑 |
|
|
|
const handleEdit = () => { |
|
|
|
isEditing.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 根据类型ID获取类型名称 |
|
|
|
const getCategoryName = (typeId: string | number | undefined) => { |
|
|
|
if (!typeId) return '未知类型'; |
|
|
|
const category = ruleCategories.value.find(cat => String(cat.id) === String(typeId)); |
|
|
|
return category ? category.contractName : '未知类型'; |
|
|
|
}; |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="less" scoped> |
|
|
@ -25,6 +626,7 @@ |
|
|
|
flex: 1; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
height: calc(100vh - 200px); |
|
|
|
} |
|
|
|
|
|
|
|
.page-header { |
|
|
@ -47,70 +649,292 @@ |
|
|
|
flex: 1; |
|
|
|
min-height: 0; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
padding: 2rem 0; |
|
|
|
flex-direction: column; |
|
|
|
} |
|
|
|
|
|
|
|
.empty-state { |
|
|
|
.container { |
|
|
|
min-height: 100%; |
|
|
|
height: 100%; |
|
|
|
background-color: #fff; |
|
|
|
display: flex; |
|
|
|
width: 100%; |
|
|
|
max-width: none; |
|
|
|
border-radius: 8px; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-category { |
|
|
|
min-width: 240px; |
|
|
|
width: 240px; |
|
|
|
height: 100%; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
background: #fafafa; |
|
|
|
} |
|
|
|
|
|
|
|
.category-list { |
|
|
|
flex: 1; |
|
|
|
overflow-y: auto; |
|
|
|
padding-right: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-detail { |
|
|
|
flex: 1; |
|
|
|
height: 100%; |
|
|
|
min-width: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-list-section { |
|
|
|
height: 65%; |
|
|
|
min-height: 350px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-list { |
|
|
|
flex: 1; |
|
|
|
overflow-y: auto; |
|
|
|
padding-right: 4px; |
|
|
|
margin-right: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-detail-section { |
|
|
|
height: 35%; |
|
|
|
min-height: 250px; |
|
|
|
overflow-y: auto; |
|
|
|
border-top: 2px solid #f0f0f0; |
|
|
|
} |
|
|
|
|
|
|
|
.category-item { |
|
|
|
transition: all 0.3s; |
|
|
|
border-radius: 6px; |
|
|
|
margin-bottom: 4px; |
|
|
|
background: white; |
|
|
|
border: 1px solid #e8e8e8; |
|
|
|
} |
|
|
|
|
|
|
|
.category-item:hover { |
|
|
|
background-color: #f0f7ff !important; |
|
|
|
border-color: #1890ff; |
|
|
|
} |
|
|
|
|
|
|
|
.category-item.selected { |
|
|
|
background-color: #e6f7ff !important; |
|
|
|
border-left: 3px solid #1890ff; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-item { |
|
|
|
transition: all 0.2s; |
|
|
|
border-radius: 6px; |
|
|
|
margin-bottom: 8px; |
|
|
|
background: white; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-item:hover { |
|
|
|
border-color: #1890ff; |
|
|
|
box-shadow: 0 2px 4px rgba(24, 144, 255, 0.1); |
|
|
|
} |
|
|
|
|
|
|
|
.risk-level, .risk-badge { |
|
|
|
min-width: 60px; |
|
|
|
text-align: center; |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
.empty-icon { |
|
|
|
font-size: 4rem; |
|
|
|
margin-bottom: 1rem; |
|
|
|
opacity: 0.6; |
|
|
|
} |
|
|
|
.rule-title { |
|
|
|
transition: color 0.2s; |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
p { |
|
|
|
font-size: 1.1rem; |
|
|
|
color: #999; |
|
|
|
margin: 0; |
|
|
|
.rule-title:hover { |
|
|
|
color: #1890ff; |
|
|
|
} |
|
|
|
|
|
|
|
.empty-state { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
height: 100%; |
|
|
|
background-color: #fafafa; |
|
|
|
border-radius: 8px; |
|
|
|
margin: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.detail-form { |
|
|
|
padding: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.detail-form .ant-form-item { |
|
|
|
margin-bottom: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.detail-form .ant-form-item-label { |
|
|
|
font-weight: 500; |
|
|
|
} |
|
|
|
|
|
|
|
.description-content { |
|
|
|
white-space: pre-wrap; |
|
|
|
word-break: break-word; |
|
|
|
line-height: 1.5; |
|
|
|
padding: 8px; |
|
|
|
background-color: #fafafa; |
|
|
|
border-radius: 6px; |
|
|
|
min-height: 40px; |
|
|
|
} |
|
|
|
|
|
|
|
.detail-header { |
|
|
|
padding: 8px 8px 0 8px; |
|
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
|
margin-bottom: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.review-header { |
|
|
|
padding-bottom: 12px; |
|
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
|
margin-bottom: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
/* 滚动条样式 */ |
|
|
|
.category-list::-webkit-scrollbar, |
|
|
|
.rule-list::-webkit-scrollbar, |
|
|
|
.rule-detail-section::-webkit-scrollbar { |
|
|
|
width: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.category-list::-webkit-scrollbar-track, |
|
|
|
.rule-list::-webkit-scrollbar-track, |
|
|
|
.rule-detail-section::-webkit-scrollbar-track { |
|
|
|
background: #f1f1f1; |
|
|
|
border-radius: 3px; |
|
|
|
} |
|
|
|
|
|
|
|
.category-list::-webkit-scrollbar-thumb, |
|
|
|
.rule-list::-webkit-scrollbar-thumb, |
|
|
|
.rule-detail-section::-webkit-scrollbar-thumb { |
|
|
|
background: #c1c1c1; |
|
|
|
border-radius: 3px; |
|
|
|
} |
|
|
|
|
|
|
|
.category-list::-webkit-scrollbar-thumb:hover, |
|
|
|
.rule-list::-webkit-scrollbar-thumb:hover, |
|
|
|
.rule-detail-section::-webkit-scrollbar-thumb:hover { |
|
|
|
background: #a1a1a1; |
|
|
|
} |
|
|
|
|
|
|
|
/* Firefox滚动条样式 */ |
|
|
|
.category-list, |
|
|
|
.rule-list, |
|
|
|
.rule-detail-section { |
|
|
|
scrollbar-width: thin; |
|
|
|
scrollbar-color: #c1c1c1 #f1f1f1; |
|
|
|
} |
|
|
|
|
|
|
|
/* 加载状态样式 */ |
|
|
|
.loading-container { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
padding: 2rem; |
|
|
|
color: #999; |
|
|
|
background-color: #fafafa; |
|
|
|
border-radius: 6px; |
|
|
|
margin: 0.5rem 0; |
|
|
|
border: 1px dashed #d9d9d9; |
|
|
|
} |
|
|
|
|
|
|
|
.empty-state-small { |
|
|
|
padding: 1rem; |
|
|
|
margin: 0.5rem 0; |
|
|
|
background-color: #fafafa; |
|
|
|
border-radius: 6px; |
|
|
|
border: 1px dashed #d9d9d9; |
|
|
|
} |
|
|
|
|
|
|
|
/* 响应式设计 */ |
|
|
|
@media (max-width: 1200px) { |
|
|
|
.rule-category { |
|
|
|
min-width: 200px; |
|
|
|
width: 200px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 响应式设计 |
|
|
|
@media (max-width: 992px) { |
|
|
|
.checklist-page { |
|
|
|
padding: 1.5rem; |
|
|
|
margin-bottom: 1.5rem; |
|
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.08); |
|
|
|
padding: 1rem; |
|
|
|
height: calc(100vh - 150px); |
|
|
|
} |
|
|
|
|
|
|
|
.page-header { |
|
|
|
margin-bottom: 1.5rem; |
|
|
|
padding-bottom: 0.8rem; |
|
|
|
.container { |
|
|
|
flex-direction: column; |
|
|
|
} |
|
|
|
|
|
|
|
.page-title { |
|
|
|
font-size: 1.1rem; |
|
|
|
.rule-category { |
|
|
|
width: 100%; |
|
|
|
min-width: auto; |
|
|
|
height: auto; |
|
|
|
max-height: 200px; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-detail { |
|
|
|
height: auto; |
|
|
|
flex: 1; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-list-section { |
|
|
|
height: auto; |
|
|
|
min-height: 300px; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-detail-section { |
|
|
|
height: auto; |
|
|
|
min-height: 200px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
|
.checklist-page { |
|
|
|
padding: 1.2rem; |
|
|
|
margin-bottom: 1.5rem; |
|
|
|
box-shadow: 0 4px 10px rgba(0,0,0,0.05); |
|
|
|
padding: 0.8rem; |
|
|
|
border-radius: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.page-header { |
|
|
|
margin-bottom: 1.5rem; |
|
|
|
padding-bottom: 0.8rem; |
|
|
|
margin-bottom: 0.8rem; |
|
|
|
} |
|
|
|
|
|
|
|
.page-title { |
|
|
|
font-size: 1rem; |
|
|
|
} |
|
|
|
|
|
|
|
.category-title { |
|
|
|
font-size: 1rem !important; |
|
|
|
} |
|
|
|
|
|
|
|
.review-header .text-lg { |
|
|
|
font-size: 1rem !important; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@media (max-width: 480px) { |
|
|
|
.checklist-page { |
|
|
|
padding: 1rem; |
|
|
|
margin-bottom: 1.5rem; |
|
|
|
padding: 0.6rem; |
|
|
|
border-radius: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.checklist-page { |
|
|
|
min-height: calc(100vh - 200px); |
|
|
|
.container { |
|
|
|
border-radius: 6px; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-category { |
|
|
|
padding: 0.8rem !important; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-list-section { |
|
|
|
padding: 0.8rem !important; |
|
|
|
} |
|
|
|
|
|
|
|
.rule-detail-section { |
|
|
|
padding: 0.8rem !important; |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |