OfficeSystem/pages/task-list/index.vue
2025-11-06 14:33:07 +08:00

489 lines
12 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 }}天
</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 transformTaskData = (item) => {
const expireTime = item.expireTime || item.expire_time || '';
const remainingDays = calculateRemainingDays(expireTime);
// 直接使用后端返回的状态,如果后端没有返回状态且有状态过滤,使用过滤的状态
const taskStatus = statusFilter.value || 'pending';
return {
id: item.id || '',
status: taskStatus,
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;
}
// 转换数据格式
tasks.value = taskList.map(item => transformTaskData(item));
} 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>