Compare commits
No commits in common. "b504f7f755585230a72b3403786f750d4126609c" and "b2f09d3474b543f08d8c3e3ddb0ffcb7148a4054" have entirely different histories.
b504f7f755
...
b2f09d3474
|
@ -200,51 +200,47 @@ const handleReject = (taskId: string) => {
|
||||||
|
|
||||||
/* 待审批状态样式 */
|
/* 待审批状态样式 */
|
||||||
.status-item.pending {
|
.status-item.pending {
|
||||||
background-color: #f59e0b;
|
border-top-color: #f59e0b;
|
||||||
background-image: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
background-color: rgba(245, 158, 11, 0.1); /* 浅橙色背景 */
|
||||||
}
|
}
|
||||||
.status-item.pending .count {
|
.status-item.pending .count {
|
||||||
color: #fff;
|
color: #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 已通过状态样式 */
|
/* 已通过状态样式 */
|
||||||
.status-item.approved {
|
.status-item.approved {
|
||||||
background-color: #10b981;
|
border-top-color: #10b981;
|
||||||
background-image: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
background-color: rgba(16, 185, 129, 0.1); /* 浅绿色背景 */
|
||||||
}
|
}
|
||||||
.status-item.approved .count {
|
.status-item.approved .count {
|
||||||
color: #fff;
|
color: #10b981;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 已拒绝状态样式 */
|
/* 已拒绝状态样式 */
|
||||||
.status-item.rejected {
|
.status-item.rejected {
|
||||||
background-color: #ef4444;
|
border-top-color: #ef4444;
|
||||||
background-image: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
background-color: rgba(239, 68, 68, 0.1); /* 浅红色背景 */
|
||||||
}
|
}
|
||||||
.status-item.rejected .count {
|
.status-item.rejected .count {
|
||||||
color: #fff;
|
color: #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 状态文字样式 */
|
/* 状态文字样式 */
|
||||||
.status-text {
|
.status-text {
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
color: #333;
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 数字样式 */
|
/* 数字样式 */
|
||||||
.count {
|
.count {
|
||||||
font-size: 28px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: bold;
|
||||||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 激活态样式(可选,点击后高亮) */
|
/* 激活态样式(可选,点击后高亮) */
|
||||||
.status-item.active {
|
.status-item.active {
|
||||||
transform: scale(1.02);
|
transform: scale(1.02);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
|
|
@ -1,692 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<GiPageLayout>
|
<GiPageLayout>
|
||||||
<div class="task-tracking-page">
|
<div class="task-progress-page">
|
||||||
<!-- 固定标题和表头容器 -->
|
<!-- 页面标题 dwadw-->
|
||||||
<div class="sticky-headers">
|
|
||||||
<!-- 页面标题 -->
|
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h2>任务跟踪</h2>
|
<h2>任务跟踪</h2>
|
||||||
<p class="page-description">跟踪、监控和评估任务的完成情况</p>
|
<p class="page-description">跟踪、监控和评估任务的完成情况</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 公共表头(仅显示一次) -->
|
|
||||||
<div class="shared-header">
|
|
||||||
<div class="header-row">
|
|
||||||
<div class="col" style="width: 100px">任务描述</div>
|
|
||||||
<div class="col" style="width: 180px">任务情况总结</div>
|
|
||||||
<div class="col" style="width: 80px">任务执行人</div>
|
|
||||||
<div class="col" style="width: 60px">进展</div>
|
|
||||||
<div class="col" style="width: 120px">开始日期</div>
|
|
||||||
<div class="col" style="width: 120px">预计完成日期</div>
|
|
||||||
<div class="col" style="width: 80px">是否延期</div>
|
|
||||||
<div class="col" style="width: 120px">实际完成日期</div>
|
|
||||||
<div class="col" style="width: 180px">最新进展记录</div>
|
|
||||||
<div class="col" style="width: 100px">重要紧急程度</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分组容器:按重要紧急程度分组 -->
|
|
||||||
<div
|
|
||||||
v-for="(group, groupKey) in groupedTasks"
|
|
||||||
:key="groupKey"
|
|
||||||
class="task-group"
|
|
||||||
>
|
|
||||||
<!-- 分组标题(带专门的折叠/展开按钮) -->
|
|
||||||
<div class="group-header">
|
|
||||||
<span class="group-title-text">{{ groupKey }}</span>
|
|
||||||
<button
|
|
||||||
class="toggle-btn"
|
|
||||||
@click="toggleGroup(groupKey)"
|
|
||||||
:aria-expanded="!group.collapsed"
|
|
||||||
>
|
|
||||||
<i class="icon" :class="group.collapsed ? 'el-icon-plus' : 'el-icon-minus'" />
|
|
||||||
<span class="toggle-text">{{ group.collapsed ? '展开' : '收起' }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 任务列表:折叠时隐藏,展开时显示 -->
|
|
||||||
<div class="task-list" v-show="!group.collapsed">
|
|
||||||
<div
|
|
||||||
v-for="(task, index) in group.tasks"
|
|
||||||
:key="index"
|
|
||||||
class="task-row"
|
|
||||||
>
|
|
||||||
<!-- 任务描述 -->
|
|
||||||
<div class="col" style="width: 100px">{{ task.taskDesc }}</div>
|
|
||||||
<!-- 任务情况总结(带弹窗) -->
|
|
||||||
<div class="col info-cell" style="width: 180px">
|
|
||||||
<span @click="openPopup($event, task.summaryDetail, '任务情况总结')"
|
|
||||||
@mouseenter="cancelClosePopup"
|
|
||||||
@mouseleave="closePopup">
|
|
||||||
{{ task.summary }}
|
|
||||||
<i class="el-icon-info" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- 任务执行人 -->
|
|
||||||
<div class="col" style="width: 80px">{{ task.executor }}</div>
|
|
||||||
<!-- 进展(标签化) -->
|
|
||||||
<div class="col progress-tag" :class="task.progress" style="width: 60px">
|
|
||||||
{{ task.progress }}
|
|
||||||
</div>
|
|
||||||
<!-- 开始日期 -->
|
|
||||||
<div class="col" style="width: 120px">{{ task.startDate }}</div>
|
|
||||||
<!-- 预计完成日期 -->
|
|
||||||
<div class="col" style="width: 120px">{{ task.expectEndDate }}</div>
|
|
||||||
<!-- 是否延期(标签化) -->
|
|
||||||
<div class="col delay-tag" :class="task.isDelay" style="width: 80px">
|
|
||||||
{{ task.isDelay }}
|
|
||||||
</div>
|
|
||||||
<!-- 实际完成日期 -->
|
|
||||||
<div class="col" style="width: 120px">{{ task.actualEndDate }}</div>
|
|
||||||
<!-- 最新进展记录(带弹窗) -->
|
|
||||||
<div class="col info-cell" style="width: 180px">
|
|
||||||
<span @click="openPopup($event, task.progressDetail, '最新进展记录')"
|
|
||||||
@mouseenter="cancelClosePopup"
|
|
||||||
@mouseleave="closePopup">
|
|
||||||
{{ task.latestProgress }}
|
|
||||||
<i class="el-icon-info" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<!-- 重要紧急程度(标签化) -->
|
|
||||||
<div class="col priority-tag" :class="groupKey" style="width: 100px">
|
|
||||||
{{ groupKey }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 全局弹窗:所有详情共用 -->
|
|
||||||
<transition name="popup">
|
|
||||||
<div
|
|
||||||
class="popup"
|
|
||||||
v-if="popupVisible"
|
|
||||||
:style="{
|
|
||||||
top: popupTop + 'px',
|
|
||||||
left: popupLeft + 'px'
|
|
||||||
}"
|
|
||||||
@mouseenter="cancelClosePopup"
|
|
||||||
@mouseleave="closePopup"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<div class="popup-title">{{ popupTitle }}</div>
|
|
||||||
<div class="popup-content">{{ popupContent }}</div>
|
|
||||||
<div class="popup-arrow"></div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</GiPageLayout>
|
</GiPageLayout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
||||||
|
|
||||||
// 定义任务结构
|
|
||||||
interface Task {
|
|
||||||
taskDesc: string;
|
|
||||||
summary: string; // 任务情况总结(简略)
|
|
||||||
summaryDetail: string; // 任务情况总结(详细)
|
|
||||||
executor: string;
|
|
||||||
progress: string; // 进展(如:进行中、已完成)
|
|
||||||
priority: string; // 重要紧急程度(如:重要紧急、紧急不重要等)
|
|
||||||
startDate: string;
|
|
||||||
expectEndDate: string;
|
|
||||||
isDelay: string; // 是否延期(如:已延期、正常)
|
|
||||||
actualEndDate: string;
|
|
||||||
latestProgress: string; // 最新进展(简略)
|
|
||||||
progressDetail: string; // 最新进展(详细)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 定义分组结构
|
|
||||||
interface TaskGroup {
|
|
||||||
collapsed: boolean; // 是否折叠
|
|
||||||
tasks: Task[]; // 该分组下的任务
|
|
||||||
}
|
|
||||||
|
|
||||||
// 原始任务数据(模拟,实际从接口获取)
|
|
||||||
const rawTasks = ref<Task[]>([
|
|
||||||
{
|
|
||||||
taskDesc: '完成年度财务报告',
|
|
||||||
summary: '1. 任务执行人于小宁正...',
|
|
||||||
summaryDetail: '任务执行人于小宁正按流程推进,已梳理数据框架,待最终核算。目前已完成资产负债表初步编制,利润表数据核对中,预计下周完成全部核算工作。',
|
|
||||||
executor: '周北北',
|
|
||||||
progress: '进行中',
|
|
||||||
priority: '重要紧急',
|
|
||||||
startDate: '2023/02/05',
|
|
||||||
expectEndDate: '2024/11/25',
|
|
||||||
isDelay: '已延期',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '已经收集了所有必要的财...',
|
|
||||||
progressDetail: '已收集资产负债表、利润表原始数据,待合并现金流量表。本周重点完成了各部门费用核算,正在处理年末调整事项。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '更新公司官网内容',
|
|
||||||
summary: '1. 正在收集各部门最新...',
|
|
||||||
summaryDetail: '正在收集各部门最新资料,市场部和销售部已提交更新内容,技术部和人力资源部资料待收。预计下周一开始页面制作。',
|
|
||||||
executor: '李小华',
|
|
||||||
progress: '进行中',
|
|
||||||
priority: '重要紧急',
|
|
||||||
startDate: '2023/11/01',
|
|
||||||
expectEndDate: '2023/11/30',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '设计稿已确认,等待内容...',
|
|
||||||
progressDetail: '设计稿已确认,等待各部门内容素材。目前已完成首页和产品页的设计,正在准备关于我们页面的素材。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '制定明年培训计划',
|
|
||||||
summary: '1. 已完成需求调研,正...',
|
|
||||||
summaryDetail: '已完成需求调研,正在整理各部门培训需求。调研显示,技术类和管理类培训需求最高,分别占比42%和35%。',
|
|
||||||
executor: '张明明',
|
|
||||||
progress: '进行中',
|
|
||||||
priority: '重要不紧急',
|
|
||||||
startDate: '2023/10/15',
|
|
||||||
expectEndDate: '2023/12/15',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '正在分析培训需求数据...',
|
|
||||||
progressDetail: '正在分析培训需求数据,计划11月中旬完成初稿,11月底组织各部门负责人评审。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '组织年度员工团建活动',
|
|
||||||
summary: '1. 任务已经完成,因特...',
|
|
||||||
summaryDetail: '活动已落地执行,含团队协作游戏、主题分享环节,反馈良好。参与率达到95%,收集到23条有效反馈,其中85%为正面评价。',
|
|
||||||
executor: '周北北',
|
|
||||||
progress: '已完成',
|
|
||||||
priority: '紧急不重要',
|
|
||||||
startDate: '2023/01/18',
|
|
||||||
expectEndDate: '2024/12/02',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '2023/05/25',
|
|
||||||
latestProgress: '已经确定了活动日期和地...',
|
|
||||||
progressDetail: '选定XX营地,日期2023/05/20,含露营、烧烤、团队挑战。活动预算控制在计划内,实际花费比预算节省8%。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '办公室绿植更换',
|
|
||||||
summary: '1. 已联系3家供应商,...',
|
|
||||||
summaryDetail: '已联系3家供应商,正在比较报价和服务。现有绿植约60%需要更换,主要是走廊和公共区域的大型绿植。',
|
|
||||||
executor: '王静静',
|
|
||||||
progress: '待开始',
|
|
||||||
priority: '不紧急不重要',
|
|
||||||
startDate: '2023/11/20',
|
|
||||||
expectEndDate: '2023/11/30',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '正在筛选供应商,等待批...',
|
|
||||||
progressDetail: '正在筛选供应商,等待审批。初步选定两家供应商,报价相差约15%,正在核实服务内容差异。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '更新员工通讯录',
|
|
||||||
summary: '1. 收集各部门最新联...',
|
|
||||||
summaryDetail: '正在收集各部门最新联系方式,已完成市场部和销售部的信息更新,技术部和人力资源部资料待收。',
|
|
||||||
executor: '李小明',
|
|
||||||
progress: '待开始',
|
|
||||||
priority: '不紧急不重要',
|
|
||||||
startDate: '2023/11/25',
|
|
||||||
expectEndDate: '2023/12/15',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '等待各部门提交最新联...',
|
|
||||||
progressDetail: '已发送通知邮件给各部门负责人,要求提供最新员工联系方式,目前收到60%的回复。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
taskDesc: '整理归档旧项目文档',
|
|
||||||
summary: '1. 开始整理2022年...',
|
|
||||||
summaryDetail: '开始整理2022年度已完成项目的文档,按照项目类型和日期进行分类归档,预计需要两周时间完成。',
|
|
||||||
executor: '张小红',
|
|
||||||
progress: '待开始',
|
|
||||||
priority: '不紧急不重要',
|
|
||||||
startDate: '2023/12/01',
|
|
||||||
expectEndDate: '2023/12/15',
|
|
||||||
isDelay: '正常',
|
|
||||||
actualEndDate: '',
|
|
||||||
latestProgress: '准备归档工具和分类标...',
|
|
||||||
progressDetail: '已准备好归档所需的文件夹和标签,正在制定分类标准,等待主管审批。'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 分组键(固定四个重要紧急程度)
|
|
||||||
const groupKeys = ref(['重要紧急', '紧急不重要', '重要不紧急', '不紧急不重要'])
|
|
||||||
|
|
||||||
// 分组状态管理
|
|
||||||
const groupCollapseState = ref<Record<string, boolean>>({})
|
|
||||||
|
|
||||||
// 构建分组数据:按重要紧急程度分组
|
|
||||||
const groupedTasks = computed(() => {
|
|
||||||
const groups: Record<string, TaskGroup> = {}
|
|
||||||
|
|
||||||
// 初始化分组
|
|
||||||
groupKeys.value.forEach(key => {
|
|
||||||
// 初始化折叠状态,如果已有状态则使用,否则默认展开(false)
|
|
||||||
groupCollapseState.value[key] = groupCollapseState.value[key] ?? false
|
|
||||||
|
|
||||||
groups[key] = {
|
|
||||||
collapsed: groupCollapseState.value[key],
|
|
||||||
tasks: []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 将任务分配到对应分组
|
|
||||||
rawTasks.value.forEach(task => {
|
|
||||||
const groupKey = task.priority
|
|
||||||
if (groups[groupKey]) {
|
|
||||||
groups[groupKey].tasks.push(task)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return groups
|
|
||||||
})
|
|
||||||
|
|
||||||
// 折叠/展开分组
|
|
||||||
function toggleGroup(groupKey: string) {
|
|
||||||
groupCollapseState.value[groupKey] = !groupCollapseState.value[groupKey]
|
|
||||||
// 保存状态到本地存储
|
|
||||||
localStorage.setItem('taskGroupCollapse', JSON.stringify(groupCollapseState.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 组件挂载时恢复折叠状态
|
|
||||||
onMounted(() => {
|
|
||||||
const savedState = localStorage.getItem('taskGroupCollapse')
|
|
||||||
if (savedState) {
|
|
||||||
groupCollapseState.value = JSON.parse(savedState)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 弹窗状态
|
|
||||||
const popupVisible = ref(false)
|
|
||||||
const popupTitle = ref('')
|
|
||||||
const popupContent = ref('')
|
|
||||||
const popupTop = ref(0)
|
|
||||||
const popupLeft = ref(0)
|
|
||||||
let popupTimer: number | null = null
|
|
||||||
|
|
||||||
// 打开详情弹窗
|
|
||||||
function openPopup(event: MouseEvent, content: string, title: string) {
|
|
||||||
// 清除之前的关闭计时器
|
|
||||||
if (popupTimer) {
|
|
||||||
clearTimeout(popupTimer)
|
|
||||||
popupTimer = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = event.currentTarget as HTMLElement
|
|
||||||
const rect = target.getBoundingClientRect()
|
|
||||||
|
|
||||||
popupVisible.value = true
|
|
||||||
popupTitle.value = title
|
|
||||||
popupContent.value = content
|
|
||||||
|
|
||||||
// 固定居中显示
|
|
||||||
popupTop.value = window.scrollY + window.innerHeight / 2 - 100
|
|
||||||
popupLeft.value = window.innerWidth / 2 - 150
|
|
||||||
}
|
|
||||||
|
|
||||||
// 延迟关闭弹窗(防止鼠标移动时意外关闭)
|
|
||||||
function closePopup() {
|
|
||||||
popupTimer = setTimeout(() => {
|
|
||||||
popupVisible.value = false
|
|
||||||
popupTimer = null
|
|
||||||
}, 200)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消延迟关闭
|
|
||||||
function cancelClosePopup() {
|
|
||||||
if (popupTimer) {
|
|
||||||
clearTimeout(popupTimer)
|
|
||||||
popupTimer = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击页面其他区域关闭弹窗
|
|
||||||
function handleDocumentClick(e: MouseEvent) {
|
|
||||||
const popup = document.querySelector('.popup')
|
|
||||||
const infoCells = document.querySelectorAll('.info-cell')
|
|
||||||
|
|
||||||
// 如果点击的不是弹窗也不是信息单元格,则关闭弹窗
|
|
||||||
if (popup && !popup.contains(e.target as Node) &&
|
|
||||||
!Array.from(infoCells).some(cell => cell.contains(e.target as Node))) {
|
|
||||||
closePopup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
document.addEventListener('click', handleDocumentClick)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('click', handleDocumentClick)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 页面基础样式 */
|
.task-progress-page {
|
||||||
.task-tracking-page {
|
height: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #fff;
|
display: flex;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 24px;
|
||||||
}
|
padding-bottom: 16px;
|
||||||
|
|
||||||
.page-header h2 {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: #333;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-description {
|
.page-description {
|
||||||
color: #666;
|
color: #666;
|
||||||
|
margin-top: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 公共表头 */
|
|
||||||
.shared-header {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-row .col {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
border-right: 1px solid #eee;
|
|
||||||
padding: 0 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-row .col:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 分组样式 */
|
|
||||||
.task-group {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 分组标题(带折叠/展开按钮) */
|
|
||||||
.group-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px 15px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-title-text {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 折叠/展开按钮 */
|
|
||||||
.toggle-btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
background-color: #e9ecef;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #495057;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-btn:hover {
|
|
||||||
background-color: #dee2e6;
|
|
||||||
color: #212529;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-btn .icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-text {
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 任务列表(展开时显示) */
|
|
||||||
.task-tracking-page {
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
||||||
height: calc(100vh - 100px);
|
|
||||||
overflow-y: auto;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-headers {
|
|
||||||
position: sticky;
|
|
||||||
top: -20px; /* 向上移动消除空隙 */
|
|
||||||
background: #fff;
|
|
||||||
z-index: 10;
|
|
||||||
padding: 20px 0 10px;
|
|
||||||
margin-top: -20px; /* 消除外部空隙 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
padding: 20px 0 10px;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shared-header {
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #eee;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-list {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义滚动条样式 */
|
|
||||||
.task-tracking-page::-webkit-scrollbar {
|
|
||||||
width: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-tracking-page::-webkit-scrollbar-track {
|
|
||||||
background: #f1f1f1;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-tracking-page::-webkit-scrollbar-thumb {
|
|
||||||
background: #c1c1c1;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-tracking-page::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #a8a8a8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 任务行样式 */
|
|
||||||
.task-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
padding: 10px 0;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-row:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-row .col {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
border-right: 1px solid #eee;
|
|
||||||
padding: 0 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.task-row .col:last-child {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 信息单元格(带弹窗) */
|
|
||||||
.info-cell {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #1890ff;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-cell:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-cell .el-icon-info {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 进展标签样式 */
|
|
||||||
.progress-tag {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-tag.进行中 {
|
|
||||||
background: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-tag.已完成 {
|
|
||||||
background: #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-tag.待开始 {
|
|
||||||
background: #ff9800;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 是否延期标签 */
|
|
||||||
.delay-tag {
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delay-tag.正常 {
|
|
||||||
color: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delay-tag.已延期 {
|
|
||||||
color: #f44336;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 重要紧急程度标签 */
|
|
||||||
.priority-tag {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #fff;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priority-tag.重要紧急 {
|
|
||||||
background: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priority-tag.紧急不重要 {
|
|
||||||
background: #fd7e14;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priority-tag.重要不紧急 {
|
|
||||||
background: #ffc107;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.priority-tag.不紧急不重要 {
|
|
||||||
background: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 弹窗样式 */
|
|
||||||
.popup {
|
|
||||||
/* 定位到页面中间 */
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
position: fixed;
|
|
||||||
width: 300px;
|
|
||||||
max-width: 80vw;
|
|
||||||
background: #fff;
|
|
||||||
border: 1px solid #f70b0b;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 12px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
||||||
z-index: 999;
|
|
||||||
animation: fadeIn 0.15s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; transform: translateY(5px); }
|
|
||||||
to { opacity: 1; transform: translateY(0); }
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-title {
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
color: #333;
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-content {
|
|
||||||
color: #555;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* 弹窗过渡动画 */
|
|
||||||
.popup-enter-active {
|
|
||||||
transition: all 0.15s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-enter-to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -173,7 +173,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
|
||||||
import TaskForm from './components/TaskForm.vue';
|
import TaskForm from './components/TaskForm.vue';
|
||||||
import AssigneeSelector from './components/AssigneeSelector.vue';
|
import AssigneeSelector from './components/AssigneeSelector.vue';
|
||||||
const taskFormRef = ref<InstanceType<typeof TaskForm> | null>(null);
|
const taskFormRef = ref<InstanceType<typeof TaskForm> | null>(null);
|
||||||
|
@ -331,15 +330,15 @@ const resetSearch = () => {
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (!taskFormRef.value?.form.taskName) {
|
if (!taskFormRef.value?.form.taskName) {
|
||||||
Message.error('请填写任务名称');
|
alert('请填写任务名称');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!taskFormRef.value?.form.dueDate) {
|
if (!taskFormRef.value?.form.dueDate) {
|
||||||
Message.error('请设置截止日期');
|
alert('请设置截止日期');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!assigneeRef.value?.assignees.leader) {
|
if (!assigneeRef.value?.assignees.leader) {
|
||||||
Message.error('请选择任务负责人');
|
alert('请选择任务负责人');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const taskData = {
|
const taskData = {
|
||||||
|
@ -362,7 +361,7 @@ const handleSubmit = () => {
|
||||||
priority: taskData.priority || 'medium'
|
priority: taskData.priority || 'medium'
|
||||||
});
|
});
|
||||||
console.log('发布任务数据:', taskData);
|
console.log('发布任务数据:', taskData);
|
||||||
Message.success('任务发布成功!');
|
alert('任务发布成功!');
|
||||||
showPublishModal.value = false;
|
showPublishModal.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -56,7 +56,7 @@ const filteredUsers = ref<typeof users.value>([]);
|
||||||
const filterUsersByDepartment = () => {
|
const filterUsersByDepartment = () => {
|
||||||
if (!selectedDepartment.value) {
|
if (!selectedDepartment.value) {
|
||||||
filteredUsers.value = [];
|
filteredUsers.value = [];
|
||||||
assignees.value.leader = -1;
|
assignees.value.leader = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ const filterUsersByDepartment = () => {
|
||||||
|
|
||||||
// 如果当前选择的负责人不在筛选后的列表中,则清空选择
|
// 如果当前选择的负责人不在筛选后的列表中,则清空选择
|
||||||
if (assignees.value.leader && !filteredUsers.value.some(u => u.id === assignees.value.leader)) {
|
if (assignees.value.leader && !filteredUsers.value.some(u => u.id === assignees.value.leader)) {
|
||||||
assignees.value.leader = 0;
|
assignees.value.leader = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ const departments = ref([
|
||||||
|
|
||||||
// 分配数据
|
// 分配数据
|
||||||
const assignees = ref({
|
const assignees = ref({
|
||||||
leader: 0
|
leader: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
// 暴露分配数据给父组件
|
// 暴露分配数据给父组件
|
||||||
|
|
Loading…
Reference in New Issue