Browse Source

合同新增支付和优化

hetong_dev
zhouhaibin 1 week ago
parent
commit
d4d461cd07
  1. 2
      src/views/homepage/index.vue
  2. 894
      src/views/user/home/components/ChecklistPage.vue
  3. 110
      src/views/user/home/components/InvoiceRecordsPage.vue
  4. 111
      src/views/user/home/components/PurchaseRecordsPage.vue
  5. 12
      src/views/user/home/components/ReviewPage.vue
  6. 34
      src/views/user/home/index.vue

2
src/views/homepage/index.vue

@ -68,7 +68,7 @@ const startTrial = () => {
const contactUs = () => {
//
alert('联系我们:contact@markup.ai');
alert('联系我们');
};
onMounted(() => {

894
src/views/user/home/components/ChecklistPage.vue

@ -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>

110
src/views/user/home/components/InvoiceRecordsPage.vue

@ -0,0 +1,110 @@
<template>
<div class="invoice-records-page">
<div class="page-header">
<h2 class="page-title">开票记录</h2>
<p class="page-description">查看您的所有开票记录和发票信息</p>
</div>
<div class="development-notice">
<div class="notice-content">
<div class="notice-icon">🚧</div>
<h3>即将推出</h3>
<p>开票记录功能即将推出敬请期待...</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'InvoiceRecordsPage' });
</script>
<style lang="less" scoped>
.invoice-records-page {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: #333;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-description {
font-size: 1.1rem;
color: #666;
margin: 0;
}
}
.development-notice {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
.notice-content {
text-align: center;
background: #fff;
padding: 3rem;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
max-width: 500px;
width: 100%;
.notice-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
h3 {
font-size: 1.8rem;
color: #333;
margin-bottom: 1rem;
font-weight: 600;
}
p {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.6;
}
}
}
@media (max-width: 768px) {
.invoice-records-page {
padding: 1rem;
}
.page-header .page-title {
font-size: 2rem;
}
.development-notice .notice-content {
padding: 2rem;
.notice-icon {
font-size: 3rem;
}
h3 {
font-size: 1.5rem;
}
}
}
</style>

111
src/views/user/home/components/PurchaseRecordsPage.vue

@ -0,0 +1,111 @@
<template>
<div class="purchase-records-page">
<div class="page-header">
<h2 class="page-title">购买记录</h2>
<p class="page-description">查看您的所有购买记录和订单信息</p>
</div>
<div class="development-notice">
<div class="notice-content">
<div class="notice-icon">🛒</div>
<h3>即将推出</h3>
<p>购买记录功能即将推出敬请期待...</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: 'PurchaseRecordsPage' });
</script>
<style lang="less" scoped>
.purchase-records-page {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
text-align: center;
margin-bottom: 3rem;
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: #333;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.page-description {
font-size: 1.1rem;
color: #666;
margin: 0;
}
}
.development-notice {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
.notice-content {
text-align: center;
background: #fff;
padding: 3rem;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
max-width: 500px;
width: 100%;
.notice-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
h3 {
font-size: 1.8rem;
color: #333;
margin-bottom: 1rem;
font-weight: 600;
}
p {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.6;
}
}
}
@media (max-width: 768px) {
.purchase-records-page {
padding: 1rem;
}
.page-header .page-title {
font-size: 2rem;
}
.development-notice .notice-content {
padding: 2rem;
.notice-icon {
font-size: 3rem;
}
h3 {
font-size: 1.5rem;
}
}
}
</style>

12
src/views/user/home/components/ReviewPage.vue

@ -23,7 +23,6 @@
<div class="option-content">
<div class="option-name">合规性审查</div>
<div class="option-desc">检查合同是否符合相关法律法规要求</div>
<div class="coming-soon">即将推出</div>
</div>
</div>
<div
@ -33,7 +32,6 @@
<div class="option-content">
<div class="option-name">一致性审查</div>
<div class="option-desc">检查合同与投标文件或招标文件是否一致</div>
<div class="coming-soon">即将推出</div>
</div>
</div>
</div>
@ -175,16 +173,6 @@ function handleReviewSuccess(data: any) {
color: #999;
}
.coming-soon {
font-size: 0.75rem;
color: #ff6b6b;
font-weight: 500;
margin-top: 0.3rem;
background: rgba(255, 107, 107, 0.1);
padding: 0.2rem 0.5rem;
border-radius: 4px;
display: inline-block;
}
}
.option-icon {

34
src/views/user/home/index.vue

@ -43,6 +43,24 @@
<span class="menu-text">审查清单</span>
</div>
</div>
<div class="menu-section">
<div class="menu-title">订单管理</div>
<div
:class="['menu-item', 'sub-menu', { active: activeTab === 'invoice' }]"
@click="setActiveTab('invoice')"
>
<span class="menu-icon">🧾</span>
<span class="menu-text">开票记录</span>
</div>
<div
:class="['menu-item', 'sub-menu', { active: activeTab === 'purchase' }]"
@click="setActiveTab('purchase')"
>
<span class="menu-icon">🛒</span>
<span class="menu-text">购买记录</span>
</div>
</div>
</div>
<!-- 底部用户区域 -->
@ -79,10 +97,10 @@
<CrownOutlined />
开通会员
</MenuItem>
<MenuItem key="settings">
<!-- <MenuItem key="settings">
<SettingOutlined />
系统设置
</MenuItem>
</MenuItem> -->
<MenuDivider />
<MenuItem key="logout" class="logout-item">
<LogoutOutlined />
@ -116,6 +134,12 @@
<!-- 审查清单页面 -->
<ChecklistPage v-if="activeTab === 'checklist'" />
<!-- 开票记录页面 -->
<InvoiceRecordsPage v-if="activeTab === 'invoice'" />
<!-- 购买记录页面 -->
<PurchaseRecordsPage v-if="activeTab === 'purchase'" />
</div>
</div>
@ -158,6 +182,8 @@ import { useUserStore } from '@/store/modules/user';
import ReviewPage from './components/ReviewPage.vue';
import RecordsPage from './components/RecordsPage.vue';
import ChecklistPage from './components/ChecklistPage.vue';
import InvoiceRecordsPage from './components/InvoiceRecordsPage.vue';
import PurchaseRecordsPage from './components/PurchaseRecordsPage.vue';
//
import ContractualTasksModal from '@/views/contractReview/ContractualTasks/ContractualTasksModal.vue';
@ -174,7 +200,7 @@ const router = useRouter();
const userStore = useUserStore();
//
const activeTab = ref<'review' | 'records' | 'checklist'>('review');
const activeTab = ref<'review' | 'records' | 'checklist' | 'invoice' | 'purchase'>('review');
const resultDetailDrawerVisible = ref(false);
const taskResultDetail = ref<ContractualTaskResultDetailVO[]>([]);
const currentTaskInfo = ref<Recordable>({});
@ -192,7 +218,7 @@ const [registerDrawer, { openDrawer }] = useDrawer();
const [registerModal, { openModal }] = useModal();
//
function setActiveTab(tab: 'review' | 'records' | 'checklist') {
function setActiveTab(tab: 'review' | 'records' | 'checklist' | 'invoice' | 'purchase') {
activeTab.value = tab;
if (tab === 'records' && recordsPageRef.value) {
recordsPageRef.value.reload();

Loading…
Cancel
Save