OfficeSystem/pages/task/manage/index.vue
2025-11-24 09:36:33 +08:00

1154 lines
32 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-manage-page">
<!-- 顶部标题栏 -->
<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>
<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>
<view class="filter-btn" @click="showFilter = !showFilter">
<text class="filter-text">筛选</text>
</view>
</view>
<!-- 筛选面板 -->
<view class="filter-panel" v-if="showFilter">
<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>
<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>
</view>
</view>
<view class="filter-actions">
<uv-button size="small" @click="handleReset">重置</uv-button>
<uv-button type="primary" size="small" @click="handleSearch">确定</uv-button>
</view>
</view>
<!-- 任务列表 -->
<scroll-view
class="task-scroll"
:class="{ 'with-filter': showFilter }"
scroll-y
:lower-threshold="50"
@scrolltolower="handleScrollToLower"
>
<view class="task-container">
<!-- 任务卡片列表 -->
<view
class="task-card"
v-for="task in tasks"
:key="task.id"
:class="getTaskCardClass(task.status)"
@click="goToTaskDetail(task)"
>
<!-- 状态标签和日期 -->
<view class="task-header">
<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">
<text class="task-date">{{ task.date }}</text>
</view>
</view>
<!-- 立即处理按钮 -->
<view class="task-action" v-if="task.status !== 'completed'&&task.status !=='cancelled'">
<uv-button
:type="getButtonType(task.status)"
size="small"
@click.stop="handleTask(task)"
>
立即处理
</uv-button>
</view>
</view>
<!-- 任务内容 -->
<view class="task-content">
<text class="task-project">所属项目: {{ task.project }}</text>
<text class="task-description">{{truncateText(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>
<!-- 加载状态 -->
<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';
import { useTaskStore } from '@/store/task';
import { usePagination } from '@/composables';
import { getDictLabel } from '@/utils/dict';
import { truncateText } from '@/utils/textSolve/truncateText';
import { getStatusText, getTaskStatusType, getTaskStatusStyle } from '@/utils/taskConfig.js';
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: ''
});
// 显示筛选面板
const showFilter = ref(false);
// 状态标签
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: {}
});
// 格式化日期:将 "2024-10-31 23:59:59" 转换为 "2024-10-31"
const formatDate = (dateStr) => {
if (!dateStr) return '';
// 如果包含空格,取日期部分
return dateStr.split(' ')[0];
};
// 计算剩余天数
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 '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';
};
// 将接口数据转换为页面需要的格式
const transformTaskData = (item) => {
const expireTime = item.expireTime || item.expire_time || '';
// 最高优先级:判断任务状态 - 先检查已取消,再检查已完成
// 支持多种字段名和数据类型status、taskStatus、statusId 等
const taskStatus = item.status !== undefined ? item.status :
item.taskStatus !== undefined ? item.taskStatus :
item.statusId !== undefined ? item.statusId : null;
// 检查是否已取消支持数字6、字符串'6'等多种格式)- 优先级最高
const isCancelled = taskStatus === 6 ||
taskStatus === '6' ||
String(taskStatus) === '6';
// 如果已取消,直接返回取消状态,不做过期校验,不计算剩余天数
if (isCancelled) {
return {
id: item.id || '',
status: 'cancelled', // 固定为cancelled确保显示已取消样式
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 // 已取消任务不计算剩余天数
};
}
// 检查是否已完成支持数字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
};
};
// 将分页器的 list 转换为任务列表格式
const tasks = computed(() => {
return list.value.map(item => transformTaskData(item));
});
// 使用全局配置获取标签自定义样式
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',
'task-card-cancelled': status === 'cancelled'
};
};
// 获取按钮类型
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 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();
}
};
// 跳转到任务详情页
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}`
});
};
// 跳转到任务搜索页面
const goToTaskSearch = () => {
uni.navigateTo({
url: '/pages/task/search/index'
});
};
// 跳转到创建任务
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;
}
.header {
display: flex;
align-items: center;
gap: 16px;
padding: 0px 24px;
background-color: #fff;
border-bottom: 1px solid #e4e7ed;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 100;
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;
}
.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;
padding: 16px;
border-bottom: 1px solid #e4e7ed;
position: fixed;
top: 48px;
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;
margin-top: 0;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.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;
color: #606266;
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;
justify-content: flex-end;
}
.btn-icon {
margin-right: 4px;
}
.task-scroll {
flex: 1;
width: 100%;
height: 0; /* 关键flex布局中需要设置为0才能正确计算高度 */
padding-top: 56px; /* header高度 */
transition: padding-top 0.3s ease;
box-sizing: border-box;
/* 确保scroll-view有明确的高度这样才能触发scrolltolower事件 */
overflow: hidden;
}
.status-tabs {
display: flex;
gap: 8px;
flex-wrap: nowrap;
}
.status-tab {
display: flex;
align-items: center;
gap: 4px;
padding: 4px 12px;
background: transparent;
border-radius: 0;
font-size: 14px;
color: #666;
transition: all 0.2s;
white-space: nowrap;
cursor: pointer;
position: relative;
&:active {
opacity: 0.7;
}
&.active {
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;
}
}
.count {
font-size: 12px;
opacity: 0.8;
}
}
.sort-options-filter {
display: flex;
gap: 12px;
margin-top: 8px;
}
.sort-option-filter {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
background: #f5f5f5;
border-radius: 8px;
padding: 8px;
border: 2px solid transparent;
font-size: 14px;
color: #666;
transition: all 0.2s;
&.active {
background: #e3f2fd;
border-color: #2885ff;
color: #2885ff;
font-weight: 500;
}
.sort-arrow {
font-size: 12px;
}
}
@import '@/styles/task-card.scss';
</style>