OfficeSystem/pages/task/list/index.vue
2025-11-07 11:40:13 +08:00

562 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="task-list-page">
<scroll-view class="task-scroll" scroll-y>
<view class="task-container">
<!-- 任务卡片列表 -->
<template v-if="!loading">
<view
class="task-card"
v-for="task in tasks"
:key="task.id"
:class="getTaskCardClass(task.status)"
@click="goToTaskDetail(task)"
>
<!-- 状态标签和日期 -->
<view class="task-header">
<view class="task-badge-wrapper">
<uv-tags
:text="getStatusText(task.status)"
:type="getTaskStatusType(task.status)"
size="mini"
:plain="false"
:custom-style="getTagCustomStyle(task.status)"
></uv-tags>
</view>
<view class="task-date-wrapper">
<text class="task-date">{{ task.date }}</text>
</view>
</view>
<!-- 任务内容 -->
<view class="task-content">
<text class="task-project">所属项目: {{ task.project }}</text>
<text class="task-description">{{ task.description }}</text>
<view class="task-meta">
<text class="task-owner">负责人: {{ task.owner }}</text>
<text class="task-owner">创建人: {{ task.createName }}</text>
<view class="task-time-row">
<text class="task-time">发布时间: {{ task.releaseTime }}</text>
<view class="task-countdown" v-if="task.status !== 'completed' && task.remainingDays !== null">
<text class="countdown-icon">🕐</text>
<text class="countdown-text" :class="getCountdownClass(task.status)">
{{ task.remainingDays < 0 ? `已逾期${Math.abs(task.remainingDays)}天` : `剩余${task.remainingDays}天` }}
</text>
</view>
</view>
</view>
</view>
<!-- 立即处理按钮 -->
<view class="task-action" v-if="task.status !== 'completed'">
<uv-button
:type="getButtonType(task.status)"
size="small"
@click.stop="handleTask(task)"
>
立即处理
</uv-button>
</view>
</view>
</template>
<!-- 加载状态 -->
<view class="empty-state" v-if="loading">
<text class="empty-text">加载中...</text>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="tasks.length === 0">
<text class="empty-text">暂无{{ getStatusText(statusFilter) || '' }}任务</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { getStatusText, getTaskStatusType, getTaskStatusStyle } from '@/utils/taskConfig.js';
import { getTaskList } from '@/common/api';
import { useTaskStore } from '@/store/task';
// 获取页面参数
const statusFilter = ref('');
const loading = ref(false);
// 任务状态映射
const statusMap = {
'completed': '完成任务',
'pending': '待完成任务',
'imminent': '即将预期',
'overdue': '逾期任务'
};
// 反向映射(从中文到英文)
const statusReverseMap = {
'完成任务': 'completed',
'待完成任务': 'pending',
'即将预期': 'imminent',
'逾期任务': 'overdue'
};
// 任务列表数据
const tasks = ref([]);
// 使用全局配置获取标签自定义样式
const getTagCustomStyle = (status) => {
const styleConfig = getTaskStatusStyle(status);
return {
backgroundColor: styleConfig.backgroundColor,
color: styleConfig.color,
borderColor: styleConfig.borderColor
};
};
// 获取卡片样式类
const getTaskCardClass = (status) => {
return {
'task-card-imminent': status === 'imminent',
'task-card-pending': status === 'pending',
'task-card-completed': status === 'completed',
'task-card-overdue': status === 'overdue'
};
};
// 获取按钮类型
const getButtonType = (status) => {
const typeMap = {
'imminent': 'warning',
'pending': 'primary',
'overdue': 'error'
};
return typeMap[status] || 'primary';
};
// 获取倒计时样式类
const getCountdownClass = (status) => {
return {
'countdown-warning': status === 'imminent',
'countdown-primary': status === 'pending',
'countdown-error': status === 'overdue'
};
};
// 处理任务
const handleTask = (task) => {
goToTaskDetail(task);
};
// 跳转到任务详情页
const goToTaskDetail = (task) => {
// 使用 Pinia store 存储任务详情数据
const taskStore = useTaskStore();
taskStore.setTaskDetail({
id: task.id,
name: task.description || '待办任务名称',
project: task.project || '所属项目',
statusTags: task.status === 'overdue' ? ['已逾期', '紧急'] :
task.status === 'imminent' ? ['即将逾期'] :
task.status === 'pending' ? ['待完成'] :
['已完成'],
deadline: task.date || '2025-10-14 18:00',
creator: task.createName,
responsible: task.owner || '张珊珊、李志',
publishTime: task.releaseTime || '2025-10-17',
content: task.description || '任务内容任务。这里是详细的任务描述,可以包含多行文本。根据实际需求,这里可以展示任务的详细要求、步骤说明、注意事项等。任务内容应该清晰明了,便于负责人理解和执行。',
submitRecords: []
});
uni.navigateTo({
url: `/pages/task/detail/index?id=${task.id}`
});
};
// 格式化日期:将 "2024-10-31 23:59:59" 转换为 "2024-10-31"
const formatDate = (dateStr) => {
if (!dateStr) return '';
// 如果包含空格,取日期部分
return dateStr.split(' ')[0];
};
// 格式化日期时间:格式化为 yyyy-MM-dd HH:mm:ss
const formatDateTime = (date) => {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const hours = String(d.getHours()).padStart(2, '0');
const minutes = String(d.getMinutes()).padStart(2, '0');
const seconds = String(d.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
// 获取即将逾期的时间范围当前时间到3天后
const getImminentDateRange = () => {
const now = new Date();
const endDate = new Date(now.getTime() + 3 * 24 * 60 * 60 * 1000);
return {
expireTimeStart: formatDateTime(now),
expireTimeEnd: formatDateTime(endDate)
};
};
// 计算剩余天数
const calculateRemainingDays = (expireTime) => {
if (!expireTime) return null;
const expireDate = new Date(expireTime);
const now = new Date();
now.setHours(0, 0, 0, 0);
expireDate.setHours(0, 0, 0, 0);
const diffTime = expireDate.getTime() - now.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
// 提取负责人:从 memberList 中提取所有成员的名称
const getOwnerNames = (memberList) => {
if (!Array.isArray(memberList) || memberList.length === 0) return '';
return memberList.map(member => member.userName || member.name || '').filter(name => name).join('、');
};
// 根据过期时间判断任务状态
const determineTaskStatus = (item, expireTime) => {
// 如果任务已完成状态为4直接返回 completed
const taskStatusFromBackend = item.status;
if (taskStatusFromBackend === 4 || taskStatusFromBackend === 'completed') {
return 'completed';
}
// 如果没有过期时间,使用过滤状态或默认 pending
if (!expireTime) {
return statusFilter.value || 'pending';
}
const expireDate = new Date(expireTime);
const now = new Date();
// 设置时间到当天0点便于日期比较
now.setHours(0, 0, 0, 0);
expireDate.setHours(23, 59, 59, 999);
// 如果已过期,标记为逾期
if (expireDate.getTime() < now.getTime()) {
return 'overdue';
}
// 计算距离过期的天数
const diffTime = expireDate.getTime() - now.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
// 如果3天内到期标记为即将逾期
if (diffDays <= 3 && diffDays > 0) {
return 'imminent';
}
// 否则返回待完成状态
return 'pending';
};
// 将接口数据转换为页面需要的格式
const transformTaskData = (item) => {
const expireTime = item.expireTime || item.expire_time || '';
// 最高优先级:判断任务状态 - status===4 或 status==='4' 直接返回completed不做任何其他校验
// 支持多种字段名和数据类型status、taskStatus、statusId 等
const taskStatus = item.status !== undefined ? item.status :
item.taskStatus !== undefined ? item.taskStatus :
item.statusId !== undefined ? item.statusId : null;
// 检查是否已完成支持数字4、字符串'4'、字符串'completed'等多种格式)
const isCompleted = taskStatus === 4 ||
taskStatus === '4' ||
taskStatus === 'completed' ||
String(taskStatus) === '4';
// 如果已完成,直接返回完成状态,不做过期校验,不计算剩余天数
if (isCompleted) {
return {
id: item.id || '',
status: 'completed', // 固定为completed确保显示灰色样式
createName: item.createName || '',
date: formatDate(expireTime) || '',
project: item.projectName || item.project_name || '',
description: item.description || item.task_name || '',
owner: getOwnerNames(item.memberList || item.member_list || []),
releaseTime: formatDate(item.createTime || item.create_time) || '',
remainingDays: null // 已完成任务不计算剩余天数
};
}
// 未完成的任务才计算剩余天数并进行过期校验
const remainingDays = calculateRemainingDays(expireTime);
const finalStatus = determineTaskStatus(item, expireTime);
return {
id: item.id || '',
status: finalStatus,
createName: item.createName || '',
date: formatDate(expireTime) || '',
project: item.projectName || item.project_name || '',
description: item.description || item.task_name || '',
owner: getOwnerNames(item.memberList || item.member_list || []),
releaseTime: formatDate(item.createTime || item.create_time) || '',
remainingDays: remainingDays
};
};
// 加载任务列表数据
const loadTaskList = async () => {
try {
loading.value = true;
let res;
// 根据状态类型调用不同的接口
if (statusFilter.value === 'completed') {
res = await getTaskList({ statusList: [4] });
} else if (statusFilter.value === 'overdue') {
res = await getTaskList({ overdue: true });
} else if (statusFilter.value === 'pending') {
res = await getTaskList({ statusList: [2] });
} else if (statusFilter.value === 'imminent') {
const dateRange = getImminentDateRange();
res = await getTaskList({
statusList: [2],
expireTimeStart: dateRange.expireTimeStart,
expireTimeEnd: dateRange.expireTimeEnd
});
} else {
res = await getTaskList({});
}
console.log('任务列表加载成功:', res);
// 处理返回的数据
let taskList = [];
if (res && res.rows && Array.isArray(res.rows)) {
taskList = res.rows;
} else if (res && res.data && Array.isArray(res.data)) {
taskList = res.data;
} else if (res && Array.isArray(res)) {
taskList = res;
}
// 转换数据格式
let transformedTasks = taskList.map(item => transformTaskData(item));
// 如果是逾期任务列表过滤掉已完成的任务status === 4
// transformTaskData 已经将 status === 4 转换为 status === 'completed'
if (statusFilter.value === 'overdue') {
transformedTasks = transformedTasks.filter(task => task.status !== 'completed');
}
tasks.value = transformedTasks;
} catch (err) {
console.error('加载任务列表失败:', err);
uni.showToast({
title: '加载数据失败',
icon: 'none'
});
tasks.value = [];
} finally {
loading.value = false;
}
};
// 页面加载时获取参数
onLoad((options) => {
// 获取状态参数
if (options.status) {
statusFilter.value = options.status;
} else if (options.label) {
// 如果传入的是中文标签,转换为英文状态
statusFilter.value = statusReverseMap[decodeURIComponent(options.label)] || '';
}
// 设置页面标题
if (statusFilter.value && statusMap[statusFilter.value]) {
uni.setNavigationBarTitle({
title: statusMap[statusFilter.value]
});
}
// 加载任务列表数据
loadTaskList();
});
</script>
<style lang="scss" scoped>
.task-list-page {
width: 100%;
height: 100vh;
background: #f5f5f5;
}
.task-scroll {
width: 100%;
height: 100%;
}
.task-container {
padding: 16px;
display: flex;
flex-direction: column;
gap: 12px;
}
.task-card {
background: #fff;
border-radius: 12px;
padding: 16px;
display: flex;
flex-direction: column;
position: relative;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
}
.task-card:active {
transform: scale(0.98);
opacity: 0.9;
}
// 即将逾期卡片样式
.task-card-imminent {
border-left: 4px solid #ff9800;
}
// 待完成卡片样式
.task-card-pending {
border-left: 4px solid #2885ff;
}
// 已完成卡片样式
.task-card-completed {
border-left: 4px solid #909399;
opacity: 0.85;
}
// 逾期卡片样式
.task-card-overdue {
background: linear-gradient(135deg, #fff5f5 0%, #ffe6e6 100%);
border-left: 4px solid #f56c6c;
box-shadow: 0 2px 12px rgba(255, 68, 68, 0.1);
}
.task-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.task-badge-wrapper {
flex-shrink: 0;
}
.task-date-wrapper {
background: rgba(0, 0, 0, 0.04);
border-radius: 4px;
padding: 4px 8px;
}
.task-date {
font-size: 14px;
color: #333;
font-weight: 500;
}
.task-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.task-project {
font-size: 12px;
color: #666;
line-height: 1.5;
}
.task-description {
font-size: 14px;
color: #333;
line-height: 1.5;
margin-bottom: 4px;
}
.task-meta {
display: flex;
flex-direction: column;
gap: 4px;
}
.task-owner {
font-size: 12px;
color: #666;
}
.task-time-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.task-time {
font-size: 12px;
color: #666;
}
.task-countdown {
display: flex;
align-items: center;
gap: 4px;
}
.countdown-icon {
font-size: 14px;
}
.countdown-text {
font-size: 12px;
font-weight: 500;
}
.countdown-warning {
color: #ff9800;
}
.countdown-primary {
color: #2885ff;
}
.countdown-error {
color: #f56c6c;
}
.task-action {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
.empty-state {
padding: 60px 20px;
text-align: center;
}
.empty-text {
font-size: 14px;
color: #999;
}
</style>