OfficeSystem/pages/task/manage/index.vue

1131 lines
30 KiB
Vue
Raw Normal View History

2025-11-22 15:50:58 +08:00
<template>
<view class="task-manage-page">
2025-11-22 16:07:16 +08:00
<!-- 顶部标题栏 -->
<view class="header">
<view @click="goToTaskSearch">
<view style="height: 5px;"></view>
<img src="https://api.ccttiot.com/image-1763782244238.png" alt="" style="width: 20px !important; height: 20px !important;">
</view>
2025-11-22 16:17:08 +08:00
<view class="header-center">
<view class="status-tabs">
<view
class="status-tab"
:class="{ active: activeStatusTab === 'pending' }"
@click="selectStatusTab('pending')"
>
待完成
</view>
<view
class="status-tab"
:class="{ active: activeStatusTab === 'all' }"
@click="selectStatusTab('all')"
>
全部
</view>
</view>
</view>
2025-11-22 16:07:16 +08:00
<view class="filter-btn" @click="showFilter = !showFilter">
<text class="filter-text">筛选</text>
</view>
</view>
<!-- 筛选面板 -->
<view class="filter-panel" v-if="showFilter">
2025-11-22 15:50:58 +08:00
<view class="filter-row">
<view class="filter-item" @click="openProjectPicker">
<text class="filter-label">项目</text>
<view class="filter-value">
<text v-if="filterForm.projectName" class="value-text">{{ filterForm.projectName }}</text>
<text v-else class="placeholder">请选择项目</text>
</view>
</view>
<view class="filter-item" @click="openTypePicker">
<text class="filter-label">类型</text>
<view class="filter-value">
<text v-if="filterForm.typeName" class="value-text">{{ filterForm.typeName }}</text>
<text v-else class="placeholder">请选择类型</text>
</view>
</view>
<view class="filter-item" @click="openLevelPicker">
<text class="filter-label">优先级</text>
<view class="filter-value">
<text v-if="filterForm.levelName" class="value-text">{{ filterForm.levelName }}</text>
<text v-else class="placeholder">请选择优先级</text>
</view>
</view>
</view>
<view class="filter-row">
<view class="filter-item" @click="openCreateUserPicker">
<text class="filter-label">创建人</text>
<view class="filter-value">
<text v-if="filterForm.createUserName" class="value-text">{{ filterForm.createUserName }}</text>
<text v-else class="placeholder">请选择用户</text>
</view>
</view>
<view class="filter-item" @click="openOwnerPicker">
<text class="filter-label">负责人</text>
<view class="filter-value">
<text v-if="filterForm.ownerUserName" class="value-text">{{ filterForm.ownerUserName }}</text>
<text v-else class="placeholder">请选择用户</text>
</view>
</view>
</view>
<view class="filter-row">
<view class="filter-item full-width">
<text class="filter-label">是否逾期</text>
<view class="overdue-options">
<view
class="overdue-option"
:class="{ active: filterForm.overdue === '' }"
@click="selectOverdue('')"
>
全部
</view>
<view
class="overdue-option"
:class="{ active: filterForm.overdue === true }"
@click="selectOverdue(true)"
>
逾期
</view>
<view
class="overdue-option"
:class="{ active: filterForm.overdue === false }"
@click="selectOverdue(false)"
>
正常
</view>
</view>
</view>
</view>
<view class="filter-row">
<view class="filter-item" @click="openPassDatePicker">
<text class="filter-label">完成日期</text>
<view class="filter-value">
<text v-if="filterForm.passDateRangeText" class="value-text">{{ filterForm.passDateRangeText }}</text>
<text v-else class="placeholder">请选择日期</text>
</view>
</view>
<view class="filter-item" @click="openExpireDatePicker">
<text class="filter-label">开始日期</text>
<view class="filter-value">
<text v-if="filterForm.expireTimeStart" class="value-text">{{ filterForm.expireTimeStart }}</text>
<text v-else class="placeholder">请选择日期</text>
</view>
</view>
<view class="filter-item" @click="openExpireEndDatePicker">
<text class="filter-label">结束日期</text>
<view class="filter-value">
<text v-if="filterForm.expireTimeEnd" class="value-text">{{ filterForm.expireTimeEnd }}</text>
<text v-else class="placeholder">请选择日期</text>
</view>
</view>
</view>
2025-11-22 16:17:08 +08:00
<view class="filter-row">
<view class="filter-item full-width">
<text class="filter-label">排序方式</text>
<view class="sort-options-filter">
<view
class="sort-option-filter"
:class="{ active: sortBy === 'createTime' }"
@click="selectSort('createTime')"
>
发布时间<text v-if="sortBy === 'createTime'" class="sort-arrow">{{ sortAsc ? '' : '' }}</text>
</view>
<view
class="sort-option-filter"
:class="{ active: sortBy === 'expireTime' }"
@click="selectSort('expireTime')"
>
到期时间<text v-if="sortBy === 'expireTime'" class="sort-arrow">{{ sortAsc ? '' : '' }}</text>
</view>
<view
class="sort-option-filter"
:class="{ active: sortBy === 'passTime' }"
@click="selectSort('passTime')"
>
通过时间<text v-if="sortBy === 'passTime'" class="sort-arrow">{{ sortAsc ? '' : '' }}</text>
</view>
</view>
2025-11-22 15:50:58 +08:00
</view>
</view>
2025-11-22 16:17:08 +08:00
<view class="filter-actions">
<uv-button size="small" @click="handleReset">重置</uv-button>
<uv-button type="primary" size="small" @click="handleSearch">确定</uv-button>
2025-11-22 15:50:58 +08:00
</view>
</view>
<!-- 任务列表 -->
<scroll-view
class="task-scroll"
2025-11-22 16:07:16 +08:00
:class="{ 'with-filter': showFilter }"
2025-11-22 15:50:58 +08:00
scroll-y
@scrolltolower="handleScrollToLower"
>
<view class="task-container">
2025-11-22 16:35:11 +08:00
<!-- 任务卡片列表 -->
<view
class="task-card"
v-for="task in tasks"
:key="task.id"
:class="getTaskCardClass(task.status)"
@click="goToTaskDetail(task)"
2025-11-22 15:50:58 +08:00
>
2025-11-22 16:17:08 +08:00
<!-- 状态标签和日期 -->
2025-11-22 15:50:58 +08:00
<view class="task-header">
2025-11-22 16:17:08 +08:00
<view style="display: flex;align-items: center;gap: 12px">
<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">
2025-11-22 16:35:11 +08:00
<text class="task-date">{{ task.date }}</text>
2025-11-22 16:17:08 +08:00
</view>
2025-11-22 15:50:58 +08:00
</view>
2025-11-22 16:35:11 +08:00
<!-- 立即处理按钮 -->
<view class="task-action" v-if="task.status !== 'completed'">
<uv-button
:type="getButtonType(task.status)"
size="small"
@click.stop="handleTask(task)"
>
立即处理
</uv-button>
</view>
2025-11-22 15:50:58 +08:00
</view>
2025-11-22 16:17:08 +08:00
<!-- 任务内容 -->
2025-11-22 15:50:58 +08:00
<view class="task-content">
2025-11-22 16:35:11 +08:00
<text class="task-project">所属项目: {{ task.project }}</text>
<text class="task-description">{{truncateText(task.description)}}</text>
2025-11-22 16:17:08 +08:00
<view class="task-meta">
2025-11-22 16:35:11 +08:00
<text class="task-owner">负责人: {{ task.owner }}</text>
<text class="task-owner">创建人: {{ task.createName }}</text>
2025-11-22 16:17:08 +08:00
<view class="task-time-row">
2025-11-22 16:35:11 +08:00
<text class="task-time">发布时间: {{ task.releaseTime }}</text>
<view class="task-countdown" v-if="task.status !== 'completed' && task.remainingDays !== null">
2025-11-22 16:17:08 +08:00
<text class="countdown-icon">🕐</text>
2025-11-22 16:35:11 +08:00
<text class="countdown-text" :class="getCountdownClass(task.status)">
2025-11-22 16:17:08 +08:00
{{ task.remainingDays < 0 ? `已逾期${Math.abs(task.remainingDays)}` : `剩余${task.remainingDays}` }}
</text>
</view>
</view>
2025-11-22 15:50:58 +08:00
</view>
</view>
</view>
<!-- 加载状态 -->
<view class="empty-state" v-if="loading">
<text class="empty-text">加载中...</text>
</view>
<!-- 空状态 -->
<view class="empty-state" v-else-if="isEmpty">
<text class="empty-text">暂无任务</text>
</view>
<!-- 加载更多提示 -->
<view class="load-more-tip" v-if="!isEmpty && !loading && !noMore">
<text class="load-more-text">上拉加载更多</text>
</view>
<view class="load-more-tip" v-if="!isEmpty && noMore">
<text class="load-more-text">没有更多数据了</text>
</view>
</view>
</scroll-view>
<!-- 悬浮球按钮 -->
<FabPlus @click="goToCreateTask" />
<!-- 项目选择器 -->
<uv-picker
ref="projectPickerRef"
:columns="projectColumns"
keyName="label"
@confirm="handleProjectConfirm"
></uv-picker>
<!-- 类型选择器 -->
<uv-picker
ref="typePickerRef"
:columns="typeColumns"
keyName="label"
@confirm="handleTypeConfirm"
></uv-picker>
<!-- 优先级选择器 -->
<uv-picker
ref="levelPickerRef"
:columns="levelColumns"
keyName="label"
@confirm="handleLevelConfirm"
></uv-picker>
<!-- 创建人选择器 -->
<uv-picker
ref="createUserPickerRef"
:columns="userColumns"
keyName="label"
@confirm="handleCreateUserConfirm"
></uv-picker>
<!-- 负责人选择器 -->
<uv-picker
ref="ownerPickerRef"
:columns="userColumns"
keyName="label"
@confirm="handleOwnerConfirm"
></uv-picker>
<!-- 日期选择器 -->
<uv-datetime-picker
ref="passDateStartPickerRef"
v-model="passDateStartValue"
mode="date"
@confirm="onPassDateStartConfirm"
></uv-datetime-picker>
<uv-datetime-picker
ref="passDateEndPickerRef"
v-model="passDateEndValue"
mode="date"
@confirm="onPassDateEndConfirm"
></uv-datetime-picker>
<uv-datetime-picker
ref="expireDateStartPickerRef"
v-model="expireDateStartValue"
mode="date"
@confirm="onExpireDateStartConfirm"
></uv-datetime-picker>
<uv-datetime-picker
ref="expireDateEndPickerRef"
v-model="expireDateEndValue"
mode="date"
@confirm="onExpireDateEndConfirm"
></uv-datetime-picker>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { getTaskList, getProjectListAll, getUserList } from '@/api';
import { useDictStore } from '@/store/dict';
2025-11-22 16:35:11 +08:00
import { useTaskStore } from '@/store/task';
2025-11-22 15:50:58 +08:00
import { usePagination } from '@/composables';
import { getDictLabel } from '@/utils/dict';
import { truncateText } from '@/utils/textSolve/truncateText';
2025-11-22 16:35:11 +08:00
import { getStatusText, getTaskStatusType, getTaskStatusStyle } from '@/utils/taskConfig.js';
2025-11-22 15:50:58 +08:00
import FabPlus from '@/components/FabPlus.vue';
const dictStore = useDictStore();
// 筛选表单
const filterForm = ref({
projectId: '',
projectName: '',
type: '',
typeName: '',
level: '',
levelName: '',
createId: '',
createUserName: '',
ownerId: '',
ownerUserName: '',
overdue: '',
passDateRange: [],
passDateRangeText: '',
expireTimeStart: '',
expireTimeEnd: ''
});
2025-11-22 16:07:16 +08:00
// 显示筛选面板
const showFilter = ref(false);
2025-11-22 15:50:58 +08:00
// 状态标签
const activeStatusTab = ref('all');
// 排序
const sortBy = ref('expireTime');
const sortAsc = ref(true);
// 选择器引用
const projectPickerRef = ref(null);
const typePickerRef = ref(null);
const levelPickerRef = ref(null);
const createUserPickerRef = ref(null);
const ownerPickerRef = ref(null);
const passDateStartPickerRef = ref(null);
const passDateEndPickerRef = ref(null);
const expireDateStartPickerRef = ref(null);
const expireDateEndPickerRef = ref(null);
// 日期选择器值
const passDateStartValue = ref(Date.now());
const passDateEndValue = ref(Date.now());
const expireDateStartValue = ref(Date.now());
const expireDateEndValue = ref(Date.now());
// 选项数据
const projectOptions = ref([]);
const projectColumns = ref([[]]);
const typeOptions = ref([]);
const typeColumns = ref([[]]);
const levelOptions = ref([]);
const levelColumns = ref([[]]);
const userOptions = ref([]);
const userColumns = ref([[]]);
// 使用分页组合式函数
const {
list,
loading,
noMore,
isEmpty,
getList,
loadMore,
updateParams,
reset
} = usePagination({
fetchData: async (params) => {
// 构建请求参数
const requestParams = {
...params,
orderByColumn: sortBy.value,
isAsc: sortAsc.value ? 'ascending' : 'descending'
};
// 添加筛选参数
if (filterForm.value.projectId) {
requestParams.projectId = filterForm.value.projectId;
}
if (filterForm.value.type) {
requestParams.type = filterForm.value.type;
}
if (filterForm.value.level) {
requestParams.level = filterForm.value.level;
}
if (filterForm.value.createId) {
requestParams.createId = filterForm.value.createId;
}
if (filterForm.value.ownerId) {
requestParams.ownerId = filterForm.value.ownerId;
}
if (filterForm.value.overdue !== '') {
requestParams.overdue = filterForm.value.overdue;
}
if (filterForm.value.passDateRange.length === 2) {
requestParams.passDateRange = filterForm.value.passDateRange;
}
if (filterForm.value.expireTimeStart) {
requestParams.expireTimeStart = filterForm.value.expireTimeStart + ' 00:00:00';
}
if (filterForm.value.expireTimeEnd) {
requestParams.expireTimeEnd = filterForm.value.expireTimeEnd + ' 23:59:59';
}
// 根据状态标签添加状态筛选
if (activeStatusTab.value === 'pending') {
requestParams.statusList = [2]; // 进行中
} else if (activeStatusTab.value === 'completed') {
requestParams.statusList = [4]; // 已完成
} else if (activeStatusTab.value === 'cancelled') {
requestParams.statusList = [6]; // 已取消
}
// all 不添加状态筛选,显示所有
const res = await getTaskList(requestParams);
return res;
},
mode: 'loadMore',
pageSize: 20,
defaultParams: {}
});
2025-11-22 16:35:11 +08:00
// 格式化日期:将 "2024-10-31 23:59:59" 转换为 "2024-10-31"
const formatDate = (dateStr) => {
if (!dateStr) return '';
// 如果包含空格,取日期部分
return dateStr.split(' ')[0];
};
2025-11-22 16:17:08 +08:00
// 计算剩余天数
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;
};
2025-11-22 16:35:11 +08:00
// 提取负责人:从 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) => {
2025-11-22 16:17:08 +08:00
// 如果任务已完成状态为4直接返回 completed
2025-11-22 16:35:11 +08:00
const taskStatusFromBackend = item.status;
if (taskStatusFromBackend === 4 || taskStatusFromBackend === 'completed') {
2025-11-22 16:17:08 +08:00
return 'completed';
}
2025-11-22 16:35:11 +08:00
// 如果没有过期时间,使用默认 pending
2025-11-22 16:17:08 +08:00
if (!expireTime) {
return 'pending';
}
const expireDate = new Date(expireTime);
const now = new Date();
// 如果已过期,标记为逾期
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';
};
2025-11-22 16:35:11 +08:00
// 将接口数据转换为页面需要的格式
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) {
2025-11-22 16:17:08 +08:00
return {
2025-11-22 16:35:11 +08:00
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 // 已完成任务不计算剩余天数
2025-11-22 16:17:08 +08:00
};
2025-11-22 15:50:58 +08:00
}
2025-11-22 16:35:11 +08:00
// 未完成的任务才计算剩余天数并进行过期校验
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
2025-11-22 15:50:58 +08:00
};
};
2025-11-22 16:35:11 +08:00
// 将分页器的 list 转换为任务列表格式
const tasks = computed(() => {
return list.value.map(item => transformTaskData(item));
});
2025-11-22 15:50:58 +08:00
2025-11-22 16:35:11 +08:00
// 使用全局配置获取标签自定义样式
2025-11-22 15:50:58 +08:00
const getTagCustomStyle = (status) => {
2025-11-22 16:35:11 +08:00
const styleConfig = getTaskStatusStyle(status);
return {
backgroundColor: styleConfig.backgroundColor,
color: styleConfig.color,
borderColor: styleConfig.borderColor
2025-11-22 15:50:58 +08:00
};
};
2025-11-22 16:17:08 +08:00
// 获取卡片样式类
2025-11-22 16:35:11 +08:00
const getTaskCardClass = (status) => {
2025-11-22 16:17:08 +08:00
return {
2025-11-22 16:35:11 +08:00
'task-card-imminent': status === 'imminent',
'task-card-pending': status === 'pending',
'task-card-completed': status === 'completed',
'task-card-overdue': status === 'overdue'
2025-11-22 16:17:08 +08:00
};
};
2025-11-22 16:35:11 +08:00
// 获取按钮类型
const getButtonType = (status) => {
const typeMap = {
'imminent': 'warning',
'pending': 'primary',
'overdue': 'error'
2025-11-22 16:17:08 +08:00
};
2025-11-22 16:35:11 +08:00
return typeMap[status] || 'primary';
2025-11-22 16:17:08 +08:00
};
2025-11-22 16:35:11 +08:00
// 获取倒计时样式类
const getCountdownClass = (status) => {
return {
'countdown-warning': status === 'imminent',
'countdown-primary': status === 'pending',
'countdown-error': status === 'overdue'
};
2025-11-22 15:50:58 +08:00
};
2025-11-22 16:35:11 +08:00
// 处理任务
const handleTask = (task) => {
goToTaskDetail(task);
2025-11-22 15:50:58 +08:00
};
// 打开选择器
const openProjectPicker = () => {
projectPickerRef.value?.open();
};
const openTypePicker = () => {
typePickerRef.value?.open();
};
const openLevelPicker = () => {
levelPickerRef.value?.open();
};
const openCreateUserPicker = () => {
createUserPickerRef.value?.open();
};
const openOwnerPicker = () => {
ownerPickerRef.value?.open();
};
const openPassDatePicker = () => {
passDateStartPickerRef.value?.open();
};
const openExpireDatePicker = () => {
expireDateStartPickerRef.value?.open();
};
const openExpireEndDatePicker = () => {
expireDateEndPickerRef.value?.open();
};
// 选择器确认
const handleProjectConfirm = (e) => {
const selected = e.value[0];
filterForm.value.projectId = selected.value;
filterForm.value.projectName = selected.label;
};
const handleTypeConfirm = (e) => {
const selected = e.value[0];
filterForm.value.type = selected.value;
filterForm.value.typeName = selected.label;
};
const handleLevelConfirm = (e) => {
const selected = e.value[0];
filterForm.value.level = selected.value;
filterForm.value.levelName = selected.label;
};
const handleCreateUserConfirm = (e) => {
const selected = e.value[0];
filterForm.value.createId = selected.value;
filterForm.value.createUserName = selected.label;
};
const handleOwnerConfirm = (e) => {
const selected = e.value[0];
filterForm.value.ownerId = selected.value;
filterForm.value.ownerUserName = selected.label;
};
// 日期选择确认
const onPassDateStartConfirm = (e) => {
const date = formatDatePickerValue(e.value);
filterForm.value.passDateRange[0] = date;
// 如果结束日期已选择,更新文本
if (filterForm.value.passDateRange[1]) {
filterForm.value.passDateRangeText = `${date}${filterForm.value.passDateRange[1]}`;
} else {
filterForm.value.passDateRangeText = date;
// 自动打开结束日期选择器
setTimeout(() => {
passDateEndPickerRef.value?.open();
}, 300);
}
};
const onPassDateEndConfirm = (e) => {
const date = formatDatePickerValue(e.value);
filterForm.value.passDateRange[1] = date;
if (filterForm.value.passDateRange[0]) {
filterForm.value.passDateRangeText = `${filterForm.value.passDateRange[0]}${date}`;
} else {
filterForm.value.passDateRangeText = date;
}
};
const onExpireDateStartConfirm = (e) => {
filterForm.value.expireTimeStart = formatDatePickerValue(e.value);
};
const onExpireDateEndConfirm = (e) => {
filterForm.value.expireTimeEnd = formatDatePickerValue(e.value);
};
// 格式化日期选择器值
const formatDatePickerValue = (timestamp) => {
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 选择是否逾期
const selectOverdue = (value) => {
filterForm.value.overdue = value;
};
// 选择状态标签
const selectStatusTab = (tab) => {
activeStatusTab.value = tab;
reset();
getList();
};
// 选择排序
const selectSort = (field) => {
if (sortBy.value === field) {
sortAsc.value = !sortAsc.value;
} else {
sortBy.value = field;
sortAsc.value = true;
}
reset();
getList();
};
// 搜索
const handleSearch = () => {
reset();
getList();
};
// 重置
const handleReset = () => {
filterForm.value = {
projectId: '',
projectName: '',
type: '',
typeName: '',
level: '',
levelName: '',
createId: '',
createUserName: '',
ownerId: '',
ownerUserName: '',
overdue: '',
passDateRange: [],
passDateRangeText: '',
expireTimeStart: '',
expireTimeEnd: ''
};
activeStatusTab.value = 'all';
sortBy.value = 'expireTime';
sortAsc.value = true;
reset();
getList();
};
// 滚动到底部
const handleScrollToLower = () => {
if (!noMore.value && !loading.value) {
loadMore();
}
};
2025-11-22 16:35:11 +08:00
// 跳转到任务详情页
2025-11-22 15:50:58 +08:00
const goToTaskDetail = (task) => {
2025-11-22 16:35:11 +08:00
// 使用 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: []
});
2025-11-22 15:50:58 +08:00
uni.navigateTo({
url: `/pages/task/detail/index?id=${task.id}`
});
};
2025-11-22 16:07:16 +08:00
// 跳转到任务搜索页面
const goToTaskSearch = () => {
uni.navigateTo({
url: '/pages/task/search/index'
});
};
2025-11-22 15:50:58 +08:00
// 跳转到创建任务
const goToCreateTask = () => {
uni.navigateTo({
url: '/pages/task/add/index'
});
};
// 加载选项数据
const loadOptions = async () => {
try {
// 加载项目列表
const projectRes = await getProjectListAll();
if (projectRes && Array.isArray(projectRes)) {
projectOptions.value = projectRes.map(item => ({
label: item.name || item.projectName || '',
value: item.id || ''
})).filter(item => item.label && item.value);
projectColumns.value = [projectOptions.value];
}
// 加载任务类型
const typeDict = dictStore.getDictByType('task_type');
typeOptions.value = typeDict.map(item => ({
label: item.dictLabel,
value: item.dictValue
}));
typeColumns.value = [typeOptions.value];
// 加载优先级
const levelDict = dictStore.getDictByType('task_level');
levelOptions.value = levelDict.map(item => ({
label: item.dictLabel,
value: item.dictValue
}));
levelColumns.value = [levelOptions.value];
// 加载用户列表
const userRes = await getUserList({ pageSize: 1000 });
if (userRes && userRes.rows && Array.isArray(userRes.rows)) {
userOptions.value = userRes.rows.map(item => ({
label: item.userName || item.nickName || '',
value: item.userId || ''
})).filter(item => item.label && item.value);
userColumns.value = [userOptions.value];
}
} catch (error) {
console.error('加载选项数据失败:', error);
}
};
onMounted(() => {
loadOptions();
getList();
// 监听任务列表刷新事件
uni.$on('taskListRefresh', () => {
reset();
getList();
});
});
</script>
<style lang="scss" scoped>
.task-manage-page {
width: 100%;
height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
2025-11-22 16:07:16 +08:00
.header {
display: flex;
align-items: center;
gap: 16px;
2025-11-22 16:17:08 +08:00
padding: 8px 24px;
2025-11-22 16:07:16 +08:00
background-color: #fff;
border-bottom: 1px solid #e4e7ed;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 100;
2025-11-22 16:17:08 +08:00
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
min-height: 48px;
}
.header-center {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
2025-11-22 16:07:16 +08:00
}
.filter-btn {
display: flex;
align-items: center;
padding: 6px 0;
cursor: pointer;
flex-shrink: 0;
&:active {
opacity: 0.7;
}
}
.filter-text {
font-size: 14px;
color: #2885ff;
font-weight: 500;
}
.filter-panel {
background-color: #fff;
2025-11-22 15:50:58 +08:00
padding: 16px;
2025-11-22 16:07:16 +08:00
border-bottom: 1px solid #e4e7ed;
position: fixed;
2025-11-22 16:17:08 +08:00
top: 56px;
2025-11-22 16:07:16 +08:00
right: 0;
left: 0;
z-index: 99;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
animation: slideDown 0.3s ease;
max-height: 70vh;
overflow-y: auto;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
2025-11-22 15:50:58 +08:00
}
.filter-row {
display: flex;
gap: 12px;
margin-bottom: 12px;
&:last-child {
margin-bottom: 0;
}
}
.filter-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
&.full-width {
flex: 1 1 100%;
}
}
.filter-label {
font-size: 14px;
2025-11-22 16:07:16 +08:00
color: #606266;
2025-11-22 15:50:58 +08:00
font-weight: 500;
}
.filter-value {
display: flex;
align-items: center;
justify-content: space-between;
background: #f5f5f5;
border-radius: 8px;
padding: 8px 12px;
min-height: 36px;
}
.value-text {
font-size: 14px;
color: #333;
flex: 1;
}
.placeholder {
font-size: 14px;
color: #999;
flex: 1;
}
.overdue-options {
display: flex;
gap: 12px;
}
.overdue-option {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8px;
padding: 8px;
border: 2px solid transparent;
transition: all 0.2s;
&.active {
background: #e3f2fd;
border-color: #2885ff;
color: #2885ff;
font-weight: 500;
}
}
.filter-actions {
display: flex;
gap: 12px;
margin-top: 16px;
2025-11-22 16:07:16 +08:00
justify-content: flex-end;
2025-11-22 15:50:58 +08:00
}
.btn-icon {
margin-right: 4px;
}
2025-11-22 16:07:16 +08:00
.task-scroll {
flex: 1;
width: 100%;
2025-11-22 16:17:08 +08:00
padding-top: 56px; /* header高度 */
2025-11-22 16:07:16 +08:00
transition: padding-top 0.3s ease;
&.with-filter {
padding-top: 0;
}
2025-11-22 15:50:58 +08:00
}
.status-tabs {
display: flex;
gap: 8px;
2025-11-22 16:17:08 +08:00
flex-wrap: nowrap;
2025-11-22 15:50:58 +08:00
}
.status-tab {
display: flex;
align-items: center;
gap: 4px;
2025-11-22 16:17:08 +08:00
padding: 4px 12px;
2025-11-22 16:35:11 +08:00
background: transparent;
border-radius: 0;
font-size: 14px;
2025-11-22 15:50:58 +08:00
color: #666;
transition: all 0.2s;
2025-11-22 16:17:08 +08:00
white-space: nowrap;
cursor: pointer;
2025-11-22 16:35:11 +08:00
position: relative;
2025-11-22 16:17:08 +08:00
&:active {
opacity: 0.7;
}
2025-11-22 15:50:58 +08:00
&.active {
2025-11-22 16:35:11 +08:00
background: transparent;
color: #2885ff;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 12px;
right: 12px;
height: 2px;
background-color: #2885ff;
border-radius: 1px;
}
2025-11-22 15:50:58 +08:00
}
.count {
font-size: 12px;
opacity: 0.8;
}
}
2025-11-22 16:17:08 +08:00
.sort-options-filter {
2025-11-22 15:50:58 +08:00
display: flex;
2025-11-22 16:17:08 +08:00
gap: 12px;
margin-top: 8px;
2025-11-22 15:50:58 +08:00
}
2025-11-22 16:17:08 +08:00
.sort-option-filter {
flex: 1;
2025-11-22 15:50:58 +08:00
display: flex;
align-items: center;
2025-11-22 16:17:08 +08:00
justify-content: center;
2025-11-22 15:50:58 +08:00
gap: 4px;
2025-11-22 16:17:08 +08:00
background: #f5f5f5;
border-radius: 8px;
padding: 8px;
border: 2px solid transparent;
2025-11-22 15:50:58 +08:00
font-size: 14px;
color: #666;
transition: all 0.2s;
&.active {
2025-11-22 16:17:08 +08:00
background: #e3f2fd;
border-color: #2885ff;
2025-11-22 15:50:58 +08:00
color: #2885ff;
font-weight: 500;
}
.sort-arrow {
font-size: 12px;
}
}
2025-11-22 16:17:08 +08:00
@import '@/styles/task-card.scss';
2025-11-22 15:50:58 +08:00
</style>
2025-11-22 16:35:11 +08:00