You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

725 lines
16 KiB

<template>
<div class="user-home-layout">
<!-- 左侧导航栏 -->
<div class="sidebar">
<!-- 品牌区域 -->
<div class="brand-section">
<div class="brand-logo">
<img src="/logo.png" alt="小研智审" class="logo-img" />
<span class="brand-text">小研智审</span>
</div>
</div>
<!-- 导航菜单 -->
<div class="nav-menu">
<div class="menu-section">
<div
:class="['menu-item', { active: activeTab === 'review' }]"
@click="setActiveTab('review')"
>
<span class="menu-icon">🏠</span>
<span class="menu-text">开始</span>
</div>
</div>
<div class="menu-section">
<div class="menu-title">合同审查</div>
<div
:class="['menu-item', 'sub-menu', { active: activeTab === 'records' }]"
@click="setActiveTab('records')"
>
<span class="menu-icon">📊</span>
<span class="menu-text">审查记录</span>
</div>
</div>
<div class="menu-section">
<div class="menu-title">合同配置</div>
<div
:class="['menu-item', 'sub-menu', { active: activeTab === 'checklist' }]"
@click="setActiveTab('checklist')"
>
<span class="menu-icon"></span>
<span class="menu-text">审查清单</span>
</div>
</div>
</div>
<!-- 底部用户区域 -->
<div class="user-section">
<Dropdown :trigger="['click']" placement="topRight">
<div class="user-info-card">
<div class="user-avatar-section">
<Avatar size="default" class="user-avatar-card">
<template #icon>
<UserOutlined />
</template>
</Avatar>
</div>
<div class="user-details">
<div class="user-name-card">
{{ userInfo.nickName || '超级管理员' }}
<Tag v-if="isVipMember" color="gold" class="vip-tag">
<CrownOutlined />
VIP
</Tag>
</div>
</div>
<div class="user-menu-trigger">
<DownOutlined class="dropdown-icon-card" />
</div>
</div>
<template #overlay>
<Menu @click="handleMenuClick">
<MenuItem key="profile">
<UserOutlined />
个人信息
</MenuItem>
<MenuItem key="membership">
<CrownOutlined />
开通会员
</MenuItem>
<MenuItem key="settings">
<SettingOutlined />
系统设置
</MenuItem>
<MenuDivider />
<MenuItem key="logout" class="logout-item">
<LogoutOutlined />
退出登录
</MenuItem>
</Menu>
</template>
</Dropdown>
</div>
</div>
<!-- 右侧主内容区 -->
<div class="main-area">
<!-- 主内容区 -->
<div class="content-area">
<!-- 开始审核页面 -->
<ReviewPage
v-if="activeTab === 'review'"
@review-success="handleReviewSuccess"
/>
<!-- 审查记录页面 -->
<RecordsPage
v-if="activeTab === 'records'"
ref="recordsPageRef"
@detail="handleDetail"
@result-detail-visible="resultDetailDrawerVisible = $event"
@result-detail="taskResultDetail = $event"
@task-info="currentTaskInfo = $event"
/>
<!-- 审查清单页面 -->
<ChecklistPage v-if="activeTab === 'checklist'" />
</div>
</div>
<!-- 弹窗组件 -->
<ContractualTasksModal @register="registerModal" @reload="() => recordsPageRef.value?.reload()" />
<DocsDrawer @register="registerDrawer" />
<ContractualResultDetailDrawer
:visible="resultDetailDrawerVisible"
:taskResultDetail="taskResultDetail"
:taskInfo="currentTaskInfo"
@update:visible="resultDetailDrawerVisible = $event"
@close="handleResultDetailDrawerClose"
/>
<MembershipModal
:visible="membershipModalVisible"
@update:visible="membershipModalVisible = $event"
@purchase-success="handleMembershipPurchaseSuccess"
/>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { useRouter } from 'vue-router';
import { message, Modal, Dropdown, Menu, MenuItem, MenuDivider, Avatar, Tag } from 'ant-design-vue';
import {
UserOutlined,
DownOutlined,
SettingOutlined,
LogoutOutlined,
CrownOutlined
} from '@ant-design/icons-vue';
import { BasicTable, useTable, TableAction } from '@/components/Table';
import { useModal } from '@/components/Modal';
import { useDrawer } from '@/components/Drawer';
import { IconEnum } from '@/enums/appEnum';
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 ContractualTasksModal from '@/views/contractReview/ContractualTasks/ContractualTasksModal.vue';
import DocsDrawer from '@/views/documentReview/DocumentTasks/DocsDrawer.vue';
import ContractualResultDetailDrawer from '@/views/contractReview/ContractualTasks/ContractualResultDetailDrawer.vue';
import MembershipModal from './components/MembershipModal.vue';
// 导入类型
import { ContractualTaskResultDetailVO } from '@/api/contractReview/ContractualTaskResults/model';
defineOptions({ name: 'UserHome' });
const router = useRouter();
const userStore = useUserStore();
// 状态管理
const activeTab = ref<'review' | 'records' | 'checklist'>('review');
const resultDetailDrawerVisible = ref(false);
const taskResultDetail = ref<ContractualTaskResultDetailVO[]>([]);
const currentTaskInfo = ref<Recordable>({});
const membershipModalVisible = ref(false);
const isVipMember = ref(false); // 会员状态
// 用户信息
const userInfo = computed(() => userStore.getUserInfo || {});
// 组件引用
const recordsPageRef = ref();
// 钩子
const [registerDrawer, { openDrawer }] = useDrawer();
const [registerModal, { openModal }] = useModal();
// 方法
function setActiveTab(tab: 'review' | 'records' | 'checklist') {
activeTab.value = tab;
if (tab === 'records' && recordsPageRef.value) {
recordsPageRef.value.reload();
}
}
// 处理用户菜单点击
function handleMenuClick({ key }: any) {
switch (key) {
case 'profile':
// 个人信息功能
message.info('个人信息功能开发中');
break;
case 'membership':
// 开通会员
membershipModalVisible.value = true;
break;
case 'settings':
// 系统设置功能
message.info('系统设置功能开发中');
break;
case 'logout':
// 退出登录
Modal.confirm({
title: '确认退出',
content: '您确定要退出登录吗?',
okText: '确定',
cancelText: '取消',
onOk() {
handleLogout();
},
});
break;
}
}
// 退出登录
async function handleLogout() {
try {
await userStore.logout();
message.success('退出登录成功');
router.push('/user/login');
} catch (error) {
console.error('退出登录失败:', error);
message.error('退出登录失败');
}
}
function handleReviewSuccess(data: any) {
console.log('审查任务创建成功:', data);
message.success('合同审查任务已创建成功,正在后台处理');
// 切换到审查记录页签并刷新
setTimeout(() => {
setActiveTab('records');
}, 1000);
}
function handleDetail(record: Recordable) {
// 这个方法现在由RecordsPage组件处理
console.log('handleDetail called with:', record);
}
function handleResultDetailDrawerClose() {
resultDetailDrawerVisible.value = false;
}
function handleMembershipPurchaseSuccess(plan: any) {
console.log('会员购买成功:', plan);
message.success(`恭喜!${plan.duration}会员开通成功`);
// 更新会员状态
isVipMember.value = true;
// 这里可以调用API更新用户会员状态
}
// 监听路由变化,当有refresh参数时刷新表格
watch(
() => router.currentRoute.value.query.refresh,
(newVal) => {
if (newVal && activeTab.value === 'records' && recordsPageRef.value) {
recordsPageRef.value.reload();
router.replace({
path: router.currentRoute.value.path,
query: { ...router.currentRoute.value.query, refresh: undefined }
});
}
}
);
</script>
<style lang="less" scoped>
.user-home-layout {
display: flex;
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}
// 左侧导航栏
.sidebar {
width: 280px;
background: #fff;
padding: 2rem 1.5rem;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: 4px 0 20px rgba(0, 0, 0, 0.08);
position: fixed;
top: 0;
left: 0;
height: 100%;
z-index: 100;
}
.brand-section {
text-align: center;
margin-bottom: 2.5rem;
.brand-logo {
display: flex;
align-items: center;
justify-content: center;
gap: 0.8rem;
margin-bottom: 1rem;
.logo-img {
width: 50px;
height: 50px;
border-radius: 10px;
box-shadow: 0 6px 20px rgba(0,0,0,0.15);
}
.brand-text {
font-size: 1.8rem;
font-weight: 700;
color: #333;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
}
}
.nav-menu {
flex: 1;
overflow-y: auto;
padding-right: 10px;
.menu-section {
margin-bottom: 1.5rem;
&:last-child {
margin-bottom: 0;
}
.menu-title {
font-size: 0.9rem;
color: #888;
margin-bottom: 0.5rem;
padding-left: 0.5rem;
}
}
}
.menu-item {
display: flex;
align-items: center;
padding: 0.8rem 1rem;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
color: #666;
font-weight: 500;
&:hover {
background: #f8f9fa;
color: #333;
}
&.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
transform: translateY(-2px);
}
.menu-icon {
font-size: 1.2rem;
margin-right: 0.8rem;
flex-shrink: 0;
}
.menu-text {
font-size: 0.95rem;
}
&.sub-menu {
padding-left: 1.5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
border-left: 2px solid #e8e8e8;
&:hover {
background: #f8f9fa;
border-left-color: #667eea;
}
&.active {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
transform: translateY(-2px);
border-left-color: #667eea;
}
}
}
.user-section {
margin-top: 2rem;
.user-info-card {
display: flex;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border-radius: 12px;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
cursor: pointer;
&:hover {
background: #e9ecef;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-1px);
}
.user-avatar-section {
margin-right: 0.8rem;
.user-avatar-card {
background: #667eea;
color: white;
border: none;
}
}
.user-details {
flex: 1;
.user-name-card {
font-size: 0.9rem;
font-weight: 600;
color: #333;
margin-bottom: 0.2rem;
display: flex;
align-items: center;
gap: 0.5rem;
.vip-tag {
font-size: 0.7rem;
padding: 0.1rem 0.3rem;
border-radius: 4px;
display: flex;
align-items: center;
gap: 0.2rem;
}
}
.user-role {
font-size: 0.75rem;
color: #666;
}
}
.user-menu-trigger {
padding: 0.3rem;
border-radius: 6px;
transition: all 0.3s ease;
.dropdown-icon-card {
color: #666;
font-size: 0.8rem;
}
}
}
}
// 右侧主内容区
.main-area {
flex: 1;
margin-left: 280px; /* Match sidebar width */
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
.content-area {
flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
padding: 1rem;
}
// 用户菜单下拉项
:deep(.ant-dropdown-menu) {
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
padding: 0.5rem 0;
.ant-dropdown-menu-item {
padding: 0.6rem 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
&:hover {
background: #f8f9fa;
}
}
.logout-item {
color: #ff4d4f;
border-top: 1px solid #f0f0f0;
margin-top: 0.25rem;
&:hover {
background: #fff2f0;
color: #ff4d4f;
}
}
}
// 响应式设计
@media (max-width: 1200px) {
.sidebar {
width: 220px;
padding: 1.5rem 1rem;
}
.brand-section .brand-text {
font-size: 1.5rem;
}
.nav-menu .menu-item .menu-text {
font-size: 0.85rem;
}
.main-area {
margin-left: 220px;
padding: 0;
}
.content-area {
padding: 1rem;
}
}
@media (max-width: 992px) {
.user-home-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
height: auto;
flex-direction: row;
justify-content: space-around;
padding: 1rem 0.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.brand-section {
flex: 1;
text-align: left;
margin-bottom: 0;
padding-left: 1rem;
}
.nav-menu {
flex: 2;
padding: 0 1rem;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.user-section {
margin-top: 0;
padding: 1rem 0.5rem;
border-top: 1px solid #eee;
}
.main-area {
margin-left: 0;
padding: 0;
}
.content-area {
padding: 1rem;
}
.content-area {
flex-direction: row;
}
}
@media (max-width: 768px) {
.sidebar {
flex-direction: column;
padding: 0.8rem 0.5rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
.brand-section {
padding-left: 0;
margin-bottom: 1rem;
}
.nav-menu {
padding: 0 0.5rem;
overflow-x: hidden;
}
.nav-menu .menu-section {
margin-bottom: 1rem;
}
.nav-menu .menu-title {
font-size: 0.8rem;
padding-left: 0;
}
.nav-menu .menu-item .menu-text {
font-size: 0.8rem;
}
.user-section {
padding: 0.8rem 0.5rem;
border-top: none;
}
.user-info-card {
padding: 0.8rem;
.user-name-card {
display: none;
}
.user-role {
display: none;
}
.dropdown-icon-card {
font-size: 0.7rem;
}
}
.main-area {
padding: 0;
}
.content-area {
padding: 1rem;
}
.content-area {
flex-direction: column;
}
}
@media (max-width: 480px) {
.sidebar {
padding: 0.6rem 0.4rem;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.03);
}
.brand-section .brand-text {
font-size: 1.3rem;
}
.nav-menu .menu-item .menu-text {
font-size: 0.75rem;
}
.user-section {
padding: 0.6rem 0.4rem;
}
.user-info-card {
padding: 0.6rem;
border-radius: 16px;
.user-name-card {
display: none;
}
.user-role {
display: none;
}
.dropdown-icon-card {
font-size: 0.6rem;
}
}
.main-area {
padding: 0;
}
.content-area {
padding: 0.8rem;
}
}
</style>