Industrial-image-management.../src/views/task/task-publish/TaskPublish.vue

791 lines
18 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>
<GiPageLayout>
<div class="task-publish-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-actions">
<h1>任务管理</h1>
<button class="new-task-btn" @click="showPublishModal = true">
<i class="icon-plus"></i> 发布新任务
</button>
</div>
<p class="page-description">查看历史任务或发布新任务</p>
</div>
<!-- 任务优先级统计方框可点击触发搜索 -->
<div class="stats-container">
<div
class="stat-box low"
@click="handleStatClick('low')"
:class="{ 'active': searchForm.priority === 'low' }"
>
<div class="stat-text">低优先级</div>
<div class="stat-number">{{ priorityStats.low }}</div>
</div>
<div
class="stat-box medium"
@click="handleStatClick('medium')"
:class="{ 'active': searchForm.priority === 'medium' }"
>
<div class="stat-text">中优先级</div>
<div class="stat-number">{{ priorityStats.medium }}</div>
</div>
<div
class="stat-box high"
@click="handleStatClick('high')"
:class="{ 'active': searchForm.priority === 'high' }"
>
<div class="stat-text">高优先级</div>
<div class="stat-number">{{ priorityStats.high }}</div>
</div>
<div
class="stat-box urgent"
@click="handleStatClick('urgent')"
:class="{ 'active': searchForm.priority === 'urgent' }"
>
<div class="stat-text">紧急</div>
<div class="stat-number">{{ priorityStats.urgent }}</div>
</div>
</div>
<!-- 历史任务列表表格形式 -->
<div class="history-tasks">
<div class="table-controls">
<h3>历史任务</h3>
<!-- <div class="table-actions">
<button class="refresh-btn" @click="resetSearch">刷新</button>
<button class="export-btn">导出</button>
</div> -->
</div>
<!-- 搜索区域 -->
<div class="search-container">
<div class="search-row">
<div class="search-item">
<label>任务名称:</label>
<input
type="text"
v-model="searchForm.name"
placeholder="请输入任务名称"
class="search-input"
>
</div>
<div class="search-item">
<label>优先级:</label>
<select v-model="searchForm.priority" class="search-select">
<option value="">全部</option>
<option value="low">低</option>
<option value="medium">中</option>
<option value="high">高</option>
<option value="urgent">紧急</option>
</select>
</div>
<div class="search-item">
<label>发布人:</label>
<select v-model="searchForm.publisher" class="search-select">
<option value="">全部</option>
<option v-for="user in allPublishers" :key="user" :value="user">{{ user }}</option>
</select>
</div>
</div>
<div class="search-row">
<div class="search-item">
<label>负责人:</label>
<select v-model="searchForm.assignee" class="search-select">
<option value="">全部</option>
<option v-for="user in allAssignees" :key="user" :value="user">{{ user }}</option>
</select>
</div>
<div class="search-item">
<label>发布日期从:</label>
<input type="date" v-model="searchForm.startDate" class="search-input">
</div>
<div class="search-item">
<label>到:</label>
<input type="date" v-model="searchForm.endDate" class="search-input">
</div>
<div class="search-actions">
<button class="search-btn" @click="handleSearch">搜索</button>
<button class="reset-btn" @click="resetSearch">重置</button>
</div>
</div>
</div>
<table class="task-table">
<thead>
<tr>
<th>任务名称</th>
<th>发布日期</th>
<th>发布人</th>
<th>负责人</th>
<th>截止日期</th>
<th>优先级</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="task in filteredTasks" :key="task.id" class="task-row">
<td class="task-title">{{ task.title }}</td>
<td>{{ task.publishDate }}</td>
<td>{{ task.publisher }}</td>
<td>{{ task.assignee }}</td>
<td>{{ task.dueDate }}</td>
<td><span class="priority-tag" :class="task.priority">{{ getPriorityText(task.priority) }}</span></td>
<td><span class="task-status" :class="task.status">{{ task.statusText }}</span></td>
<td class="operation-btns">
<button class="operate-btn">查看</button>
<button class="operate-btn">编辑</button>
</td>
</tr>
<tr v-if="filteredTasks.length === 0">
<td colspan="8" class="no-data">没有找到匹配的任务</td>
</tr>
</tbody>
</table>
<div class="pagination">
<span>共 {{ filteredTasks.length }} 条</span>
<span>10条/页</span>
</div>
</div>
<!-- 发布任务弹窗 -->
<div class="modal-overlay" v-if="showPublishModal" @click.self="showPublishModal = false">
<div class="publish-modal">
<button class="modal-close" @click="showPublishModal = false">
<i class="icon-close"></i>
</button>
<h3>发布新任务</h3>
<div class="modal-content">
<TaskForm ref="taskFormRef" />
<AssigneeSelector ref="assigneeRef" />
</div>
<div class="modal-actions">
<button class="cancel-btn" @click="showPublishModal = false">取消</button>
<button class="submit-btn" @click="handleSubmit">发布</button>
</div>
</div>
</div>
</div>
</GiPageLayout>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import TaskForm from './components/TaskForm.vue';
import AssigneeSelector from './components/AssigneeSelector.vue';
const taskFormRef = ref<InstanceType<typeof TaskForm> | null>(null);
const assigneeRef = ref<InstanceType<typeof AssigneeSelector> | null>(null);
const router = useRouter();
const showPublishModal = ref(false);
// 模拟历史任务数据
const historyTasks = ref([
{
id: 'task-001',
title: '前端页面开发',
description: '完成用户管理模块的前端页面开发',
publishDate: '2025-10-25',
dueDate: '2025-10-30',
publisher: '管理员',
assignee: '张三',
status: 'progress',
statusText: '进行中',
priority: 'medium'
},
{
id: 'task-002',
title: 'API接口开发',
description: '完成用户管理模块的后端API开发',
publishDate: '2025-10-20',
dueDate: '2025-10-28',
publisher: '管理员',
assignee: '李四',
status: 'completed',
statusText: '已完成',
priority: 'high'
},
{
id: 'task-003',
title: '测试用例编写',
description: '编写用户管理模块的测试用例',
publishDate: '2025-10-18',
dueDate: '2025-10-25',
publisher: '项目经理',
assignee: '王五',
status: 'pending',
statusText: '未开始',
priority: 'low'
}
]);
// 搜索表单数据
const searchForm = ref({
name: '',
priority: '',
publisher: '',
assignee: '',
startDate: '',
endDate: ''
});
// 计算过滤后的任务列表
const filteredTasks = computed(() => {
return historyTasks.value.filter(task => {
// 任务名称筛选
if (searchForm.value.name && !task.title.includes(searchForm.value.name)) {
return false;
}
// 优先级筛选(统计方框点击会设置此值)
if (searchForm.value.priority && task.priority !== searchForm.value.priority) {
return false;
}
// 发布人筛选
if (searchForm.value.publisher && task.publisher !== searchForm.value.publisher) {
return false;
}
// 负责人筛选
if (searchForm.value.assignee && task.assignee !== searchForm.value.assignee) {
return false;
}
// 开始日期筛选
if (searchForm.value.startDate && task.publishDate < searchForm.value.startDate) {
return false;
}
// 结束日期筛选
if (searchForm.value.endDate && task.publishDate > searchForm.value.endDate) {
return false;
}
return true;
});
});
// 统计方框点击事件处理
const handleStatClick = (priority: string) => {
// 如果点击的是当前已选中的优先级,则清除筛选
if (searchForm.value.priority === priority) {
searchForm.value.priority = '';
} else {
// 否则设置为对应优先级筛选条件
searchForm.value.priority = priority;
}
// 触发搜索逻辑
handleSearch();
};
// 获取所有发布人列表(去重)
const allPublishers = computed(() => {
const publishers = new Set<string>();
historyTasks.value.forEach(task => publishers.add(task.publisher));
return Array.from(publishers);
});
// 获取所有负责人列表(去重)
const allAssignees = computed(() => {
const assignees = new Set<string>();
historyTasks.value.forEach(task => assignees.add(task.assignee));
return Array.from(assignees);
});
// 计算各优先级任务数量
const priorityStats = computed(() => {
return {
low: historyTasks.value.filter(task => task.priority === 'low').length,
medium: historyTasks.value.filter(task => task.priority === 'medium').length,
high: historyTasks.value.filter(task => task.priority === 'high').length,
urgent: historyTasks.value.filter(task => task.priority === 'urgent').length
};
});
// 获取优先级文本
const getPriorityText = (priority: string) => {
const map: Record<string, string> = {
low: '低',
medium: '中',
high: '高',
urgent: '紧急'
};
return map[priority] || '';
};
// 搜索处理
const handleSearch = () => {
console.log('搜索条件:', searchForm.value);
// 滚动到表格顶部,提升用户体验
document.querySelector('.task-table')?.scrollIntoView({ behavior: 'smooth' });
};
// 重置搜索条件
const resetSearch = () => {
searchForm.value = {
name: '',
priority: '',
publisher: '',
assignee: '',
startDate: '',
endDate: ''
};
};
const handleSubmit = () => {
if (!taskFormRef.value?.form.taskName) {
Message.error('请填写任务名称');
return;
}
if (!taskFormRef.value?.form.dueDate) {
Message.error('请设置截止日期');
return;
}
if (!assigneeRef.value?.assignees.leader) {
Message.error('请选择任务负责人');
return;
}
const taskData = {
...taskFormRef.value?.form,
...assigneeRef.value?.assignees,
publishDate: new Date().toISOString().split('T')[0],
publisher: '当前用户' // 实际应用中应从登录信息获取
};
historyTasks.value.unshift({
id: `task-${Date.now()}`,
title: taskData.taskName,
description: taskData.taskDescription || '无描述',
publishDate: taskData.publishDate,
dueDate: taskData.dueDate,
publisher: taskData.publisher,
assignee: taskData.leader,
status: 'pending',
statusText: '未开始',
priority: taskData.priority || 'medium'
});
console.log('发布任务数据:', taskData);
Message.success('任务发布成功!');
showPublishModal.value = false;
};
</script>
<style scoped>
/* 新增统计方框点击样式 */
.stat-box {
cursor: pointer;
transition: all 0.2s ease;
}
.stat-box.active {
transform: scale(1.03);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.stat-box:active {
transform: translateY(0);
}
/* 搜索区域样式 */
.search-container {
background-color: #f8fafc;
padding: 16px;
border-radius: 4px;
margin-bottom: 16px;
border: 1px solid #e2e8f0;
}
.search-row {
display: flex;
gap: 16px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.search-item {
display: flex;
align-items: center;
gap: 8px;
min-width: 200px;
}
.search-item label {
font-size: 14px;
color: #4b5563;
white-space: nowrap;
}
.search-input, .search-select {
padding: 6px 10px;
border: 1px solid #d1d5db;
border-radius: 3px;
font-size: 14px;
width: 180px;
}
.search-actions {
display: flex;
gap: 8px;
align-items: center;
margin-left: auto;
}
.search-btn, .reset-btn {
padding: 6px 16px;
border-radius: 3px;
font-size: 14px;
cursor: pointer;
border: none;
}
.search-btn {
background-color: #3b82f6;
color: white;
}
.search-btn:hover {
background-color: #2563eb;
}
.reset-btn {
background-color: #f3f4f6;
color: #4b5563;
}
.reset-btn:hover {
background-color: #e5e7eb;
}
.no-data {
text-align: center;
padding: 24px;
color: #6b7280;
font-size: 14px;
}
/* 原有样式保持不变 */
.task-publish-container {
height: 100%;
padding: 20px;
box-sizing: border-box;
/* background-color: #f9fafb; */
}
/* 页面头部样式 */
.page-header {
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid #e5e7eb;
}
.header-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
h1 {
font-size: 21px;
color: #1f2937;
margin: 0;
}
.page-description {
color: #666;
font-size: 14px;
margin: 0;
}
.new-task-btn {
padding: 6px 12px;
background-color: #3989f8;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 6px;
}
.new-task-btn:hover {
background-color: #0077fe;
}
/* 优先级统计方框样式 */
.stats-container {
display: flex;
gap: 16px;
margin-bottom: 20px;
}
.stat-box {
flex: 1;
border-radius: 6px;
padding: 18px 20px;
display: flex;
justify-content: space-between;
align-items: center;
color: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.stat-box.low {
background-color: #4ade80;
background-image: linear-gradient(135deg, #4ade80 0%, #22c55e 100%);
}
.stat-box.medium {
background-color: #3b82f6;
background-image: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
}
.stat-box.high {
background-color: #f59e0b;
background-image: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
}
.stat-box.urgent {
background-color: #ef4444;
background-image: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
}
.stat-text {
font-size: 16px;
font-weight: 500;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.stat-number {
font-size: 28px;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
letter-spacing: 0.5px;
}
/* 历史任务表格样式 */
.history-tasks {
background-color: #fff;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 16px;
}
.table-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 1px solid #e5e7eb;
}
h3 {
font-size: 16px;
color: #1f2937;
margin: 0;
}
/* .table-actions {
display: flex;
gap: 8px;
}
.refresh-btn, .export-btn {
padding: 4px 10px;
background-color: #fff;
border: 1px solid #d1d5db;
border-radius: 3px;
cursor: pointer;
font-size: 13px;
}
.refresh-btn:hover, .export-btn:hover {
background-color: #f3f4f6;
} */
.task-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.task-table th, .task-table td {
padding: 10px 8px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.task-table th {
color: #4b5563;
font-weight: 500;
background-color: #f9fafb;
}
.task-row:hover {
background-color: #f9fafb;
}
.task-title {
color: #1f2937;
font-weight: 500;
}
/* 优先级标签样式 */
.priority-tag {
display: inline-block;
padding: 2px 6px;
font-size: 12px;
border-radius: 2px;
}
.priority-tag.low {
background-color: #e2e8f0;
color: #475569;
}
.priority-tag.medium {
background-color: #dbeafe;
color: #1e40af;
}
.priority-tag.high {
background-color: #fef3c7;
color: #92400e;
}
.priority-tag.urgent {
background-color: #fee2e2;
color: #b91c1c;
}
/* 状态标签样式 */
.task-status {
display: inline-block;
padding: 2px 6px;
font-size: 12px;
border-radius: 2px;
}
.task-status.pending {
background-color: #e5e7eb;
color: #4b5563;
}
.task-status.progress {
background-color: #fde68a;
color: #92400e;
}
.task-status.completed {
background-color: #a7f3d0;
color: #065f46;
}
/* 操作按钮样式 */
.operation-btns {
display: flex;
gap: 6px;
}
.operate-btn {
padding: 2px 8px;
background-color: #fff;
border: 1px solid #d1d5db;
border-radius: 2px;
cursor: pointer;
font-size: 12px;
}
.operate-btn:hover {
background-color: #f3f4f6;
}
/* 分页样式 */
.pagination {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 16px;
font-size: 13px;
color: #6b7280;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.publish-modal {
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 4px;
width: 800px;
max-width: 90%;
max-height: 90vh;
overflow-y: auto;
padding: 20px;
position: relative;
}
.modal-close {
position: absolute;
top: 12px;
right: 12px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #6b7280;
}
.modal-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 16px 0;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
padding-top: 12px;
border-top: 1px solid #e5e7eb;
}
.cancel-btn, .submit-btn {
padding: 6px 14px;
border-radius: 3px;
cursor: pointer;
font-size: 14px;
}
.cancel-btn {
background-color: #fff;
border: 1px solid #d1d5db;
color: #374151;
}
.cancel-btn:hover {
background-color: #f3f4f6;
}
.submit-btn {
background-color: #3e84e6;
color: white;
border: none;
}
.submit-btn:hover {
background-color: #0663f8;
}
</style>