2025-08-13 10:07:11 +08:00
|
|
|
|
<template>
|
|
|
|
|
<GiPageLayout>
|
|
|
|
|
<div class="task-approval-page">
|
|
|
|
|
<div class="page-header">
|
|
|
|
|
<h2>任务审批</h2>
|
|
|
|
|
<p class="page-description">审批转交的任务</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-08-13 10:24:24 +08:00
|
|
|
|
<!-- 状态汇总,改为左边文字、右边数字布局。 -->
|
2025-08-13 10:07:11 +08:00
|
|
|
|
<div class="status-summary">
|
|
|
|
|
<div
|
|
|
|
|
class="status-item pending"
|
|
|
|
|
@click="activeTab = 'pending'"
|
|
|
|
|
:class="{ active: activeTab === 'pending' }"
|
|
|
|
|
>
|
|
|
|
|
<span class="status-text">待审批</span>
|
|
|
|
|
<span class="count">{{ pendingCount }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
class="status-item approved"
|
|
|
|
|
@click="activeTab = 'approved'"
|
|
|
|
|
:class="{ active: activeTab === 'approved' }"
|
|
|
|
|
>
|
|
|
|
|
<span class="status-text">已通过</span>
|
|
|
|
|
<span class="count">{{ approvedCount }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
class="status-item rejected"
|
|
|
|
|
@click="activeTab = 'rejected'"
|
|
|
|
|
:class="{ active: activeTab === 'rejected' }"
|
|
|
|
|
>
|
|
|
|
|
<span class="status-text">已拒绝</span>
|
|
|
|
|
<span class="count">{{ rejectedCount }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="content">
|
|
|
|
|
<div class="task-list">
|
|
|
|
|
<!-- 根据激活的标签,渲染对应状态的任务 -->
|
|
|
|
|
<div
|
|
|
|
|
class="task-item"
|
|
|
|
|
v-for="task in filteredTasks"
|
|
|
|
|
:key="task.id"
|
|
|
|
|
>
|
|
|
|
|
<div class="task-header">
|
|
|
|
|
<h3>{{ task.title }}</h3>
|
|
|
|
|
<span
|
|
|
|
|
class="task-status"
|
|
|
|
|
:class="task.status"
|
|
|
|
|
>
|
|
|
|
|
{{
|
|
|
|
|
task.status === 'pending' ? '待审批' :
|
|
|
|
|
task.status === 'approved' ? '已通过' : '已拒绝'
|
|
|
|
|
}}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p class="task-description">{{ task.description }}</p>
|
|
|
|
|
|
|
|
|
|
<div class="transfer-info">
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">转交人:</span>
|
|
|
|
|
<span>{{ task.from }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">接收人:</span>
|
|
|
|
|
<span>{{ task.to }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">部门:</span>
|
|
|
|
|
<span>{{ task.department }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">转交日期:</span>
|
|
|
|
|
<span>{{ task.transferDate }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-item">
|
|
|
|
|
<span class="label">转交原因:</span>
|
|
|
|
|
<span>{{ task.reason }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="action-buttons" v-if="task.status === 'pending'">
|
|
|
|
|
<button class="approve-btn" @click="handleApprove(task.id)">通过</button>
|
|
|
|
|
<button class="reject-btn" @click="handleReject(task.id)">拒绝</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</GiPageLayout>
|
|
|
|
|
</template>
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, computed } from 'vue';
|
|
|
|
|
|
|
|
|
|
// 模拟所有任务数据
|
|
|
|
|
const allTasks = ref([
|
|
|
|
|
{
|
|
|
|
|
id: '1',
|
|
|
|
|
title: '前端页面重构',
|
|
|
|
|
description: '重构用户管理页面,优化交互体验',
|
|
|
|
|
from: '张三',
|
|
|
|
|
to: '李四',
|
|
|
|
|
department: '前端开发部',
|
|
|
|
|
transferDate: '2025-08-10',
|
|
|
|
|
reason: '原负责人工作负荷过大',
|
|
|
|
|
status: 'pending'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '2',
|
|
|
|
|
title: 'API接口开发',
|
|
|
|
|
description: '开发用户管理相关API接口',
|
|
|
|
|
from: '王五',
|
|
|
|
|
to: '赵六',
|
|
|
|
|
department: '后端开发部',
|
|
|
|
|
transferDate: '2025-08-11',
|
|
|
|
|
reason: '技术栈更匹配',
|
|
|
|
|
status: 'pending'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '3',
|
|
|
|
|
title: '数据库设计',
|
|
|
|
|
description: '设计用户管理模块的数据库表结构',
|
|
|
|
|
from: '钱七',
|
|
|
|
|
to: '孙八',
|
|
|
|
|
department: '数据库组',
|
|
|
|
|
transferDate: '2025-08-12',
|
|
|
|
|
reason: '专业领域更对口',
|
|
|
|
|
status: 'pending'
|
|
|
|
|
}
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 激活的标签,默认待审批
|
|
|
|
|
const activeTab = ref('pending');
|
|
|
|
|
|
|
|
|
|
// 计算各状态任务数量
|
|
|
|
|
const pendingCount = computed(() => allTasks.value.filter(t => t.status === 'pending').length);
|
|
|
|
|
const approvedCount = computed(() => allTasks.value.filter(t => t.status === 'approved').length);
|
|
|
|
|
const rejectedCount = computed(() => allTasks.value.filter(t => t.status === 'rejected').length);
|
|
|
|
|
|
|
|
|
|
// 根据激活标签筛选任务
|
|
|
|
|
const filteredTasks = computed(() => {
|
|
|
|
|
return allTasks.value.filter(task => task.status === activeTab.value);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 审批操作
|
|
|
|
|
const handleApprove = (taskId: string) => {
|
|
|
|
|
const task = allTasks.value.find(t => t.id === taskId);
|
|
|
|
|
if (task) {
|
|
|
|
|
task.status = 'approved';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 拒绝操作
|
|
|
|
|
const handleReject = (taskId: string) => {
|
|
|
|
|
const task = allTasks.value.find(t => t.id === taskId);
|
|
|
|
|
if (task) {
|
|
|
|
|
task.status = 'rejected';
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
</script>
|
|
|
|
|
<style scoped>
|
|
|
|
|
.task-approval-page {
|
|
|
|
|
height: 100%;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-header {
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
padding-bottom: 16px;
|
|
|
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-description {
|
|
|
|
|
color: #666;
|
|
|
|
|
margin-top: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 状态汇总区域 */
|
|
|
|
|
.status-summary {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 20px;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.status-item {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between; /* 让文字居左、数字居右 */
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
cursor: pointer; /* 鼠标悬浮变手型,提示可点击 */
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
border-top: 4px solid transparent; /* 预留顶部边框位置 */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 待审批状态样式 */
|
|
|
|
|
.status-item.pending {
|
|
|
|
|
border-top-color: #f59e0b;
|
|
|
|
|
background-color: rgba(245, 158, 11, 0.1); /* 浅橙色背景 */
|
|
|
|
|
}
|
|
|
|
|
.status-item.pending .count {
|
|
|
|
|
color: #f59e0b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 已通过状态样式 */
|
|
|
|
|
.status-item.approved {
|
|
|
|
|
border-top-color: #10b981;
|
|
|
|
|
background-color: rgba(16, 185, 129, 0.1); /* 浅绿色背景 */
|
|
|
|
|
}
|
|
|
|
|
.status-item.approved .count {
|
|
|
|
|
color: #10b981;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 已拒绝状态样式 */
|
|
|
|
|
.status-item.rejected {
|
|
|
|
|
border-top-color: #ef4444;
|
|
|
|
|
background-color: rgba(239, 68, 68, 0.1); /* 浅红色背景 */
|
|
|
|
|
}
|
|
|
|
|
.status-item.rejected .count {
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 状态文字样式 */
|
|
|
|
|
.status-text {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
color: #333;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 数字样式 */
|
|
|
|
|
.count {
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 激活态样式(可选,点击后高亮) */
|
|
|
|
|
.status-item.active {
|
|
|
|
|
transform: scale(1.02);
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.content {
|
|
|
|
|
flex: 1;
|
|
|
|
|
background: #fff;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
|
|
|
padding: 20px;
|
|
|
|
|
overflow: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-list {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-item {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
border: 1px solid #e5e7eb;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-header h3 {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
color: #1f2937;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-status {
|
|
|
|
|
padding: 4px 8px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-status.pending {
|
|
|
|
|
background-color: #fef3c7;
|
|
|
|
|
color: #92400e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-status.approved {
|
|
|
|
|
background-color: #d1fae5;
|
|
|
|
|
color: #065f46;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-status.rejected {
|
|
|
|
|
background-color: #fee2e2;
|
|
|
|
|
color: #991b1b;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.task-description {
|
|
|
|
|
margin: 0 0 12px 0;
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.transfer-info {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
gap: 8px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-item .label {
|
|
|
|
|
color: #6b7280;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.action-buttons {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.approve-btn, .reject-btn {
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.approve-btn {
|
|
|
|
|
background-color: #10b981;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.reject-btn {
|
|
|
|
|
background-color: #ef4444;
|
|
|
|
|
color: white;
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
</style>
|