OfficeSystem/components/ContentDashboard.vue
2025-11-05 14:58:31 +08:00

507 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<scroll-view class="dashboard-scroll" scroll-y>
<view class="dashboard-content">
<!-- 任务概览 -->
<view class="task-overview">
<view class="task-card" v-for="item in taskStats" :key="item.label">
<text class="task-count">{{ item.count }}</text>
<view class="task-label-wrapper">
<text class="task-label">{{ item.label }}</text>
<uv-tags
v-if="item.label === '逾期任务'"
text="紧急"
type="error"
size="mini"
plain
:custom-style="{ marginTop: '4px' }"
></uv-tags>
<uv-tags
v-else-if="item.label === '即将预期'"
text="注意"
type="warning"
size="mini"
plain
:custom-style="{ marginTop: '4px' }"
></uv-tags>
</view>
</view>
</view>
<!-- 逾期任务详情 -->
<view class="overdue-section" v-if="overdueTasks.length > 0">
<view class="overdue-card" v-for="task in overdueTasks" :key="task.id" @click="goToTaskDetail(task)">
<view class="overdue-badge-wrapper">
<uv-tags text="逾期" type="error" size="mini"></uv-tags>
</view>
<view class="overdue-content">
<view class="overdue-header">
<text class="overdue-date">{{ task.date }}</text>
</view>
<text class="overdue-project">所属项目: {{ task.project }}</text>
<text class="overdue-desc">{{ task.description }}</text>
<view class="overdue-meta">
<text class="overdue-owner">负责人: {{ task.owner }}</text>
<text class="overdue-time">发布时间: {{ task.releaseTime }}</text>
</view>
</view>
<view class="overdue-action">
<uv-button type="error" size="small" @click.stop="handleOverdueTask(task)">立即处理</uv-button>
</view>
</view>
<view class="carousel-dots">
<view class="dot" :class="{ active: true }"></view>
<view class="dot"></view>
<view class="dot"></view>
</view>
</view>
<!-- 公告事项 -->
<view class="announcement-section">
<view class="section-header">
<text class="section-icon">📢</text>
<text class="section-title">公告事项</text>
</view>
<view class="announcement-item" v-for="announcement in announcements" :key="announcement.id" @click="viewAnnouncement(announcement)">
<view class="announcement-content">
<text class="announcement-title">{{ announcement.title }}</text>
<text class="announcement-desc">{{ announcement.description }}</text>
<text class="announcement-time">{{ announcement.time }}</text>
</view>
<text class="arrow"></text>
</view>
</view>
<!-- 项目状态 -->
<view class="project-status-section">
<view class="section-header">
<text class="section-icon">💎</text>
<text class="section-title">项目状态</text>
</view>
<view class="status-grid">
<view class="status-card" v-for="status in projectStatus" :key="status.label">
<text class="status-count">{{ status.count }}</text>
<view class="status-label-wrapper">
<text class="status-label">{{ status.label }}</text>
<uv-tags
:text="getProjectStatusTag(status.label)"
:type="getProjectStatusType(status.label)"
size="mini"
plain
:custom-style="{ marginTop: '4px' }"
></uv-tags>
</view>
</view>
</view>
</view>
<!-- 客户状态 -->
<view class="customer-status-section">
<view class="section-header">
<text class="section-icon">👤</text>
<text class="section-title">客户状态</text>
</view>
<view class="status-grid">
<view class="status-card" v-for="status in customerStatus" :key="status.label">
<text class="status-count">{{ status.count }}</text>
<view class="status-label-wrapper">
<text class="status-label">{{ status.label }}</text>
<uv-tags
:text="getCustomerStatusTag(status.label)"
:type="getCustomerStatusType(status.label)"
size="mini"
plain
:custom-style="{ marginTop: '4px' }"
></uv-tags>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</template>
<script setup>
import { ref } from 'vue';
// 任务统计
const taskStats = ref([
{ label: '完成任务', count: 78 },
{ label: '待完成任务', count: 28 },
{ label: '即将预期', count: 8 },
{ label: '逾期任务', count: 1 }
]);
// 逾期任务
const overdueTasks = ref([
{
id: 1,
date: '2025-10-15',
project: '创特项目管理系统',
description: '项目内容项目内容项目内容项目内容项目内容项目...',
owner: '张珊珊、李志',
releaseTime: '2025-03-21'
}
]);
// 公告事项
const announcements = ref([
{
id: 1,
title: '·国庆放假通知',
description: '国庆放假安排1号至6号,前后不调休...',
time: '2025-09-26 16:54:46'
}
]);
// 项目状态
const projectStatus = ref([
{ label: '运行中', count: 1 },
{ label: '运维中', count: 1 },
{ label: '即将到期', count: 1 },
{ label: '开发超期', count: 1 }
]);
// 客户状态
const customerStatus = ref([
{ label: '今日新增', count: 1 },
{ label: '今日已跟进', count: 1 },
{ label: '今日待跟进', count: 1 },
{ label: '即将跟进', count: 1 }
]);
// 跳转到任务详情页
const goToTaskDetail = (task) => {
// 将任务数据存储到本地,供详情页使用
uni.setStorageSync('taskDetailData', {
id: task.id,
name: task.description || '待办任务名称',
project: task.project || '所属项目',
statusTags: ['已逾期', '紧急'],
deadline: task.date || '2025-10-14 18:00',
creator: '张珊珊',
responsible: task.owner || '张珊珊、李志',
publishTime: task.releaseTime || '2025-10-17',
content: task.description || '任务内容任务。这里是详细的任务描述,可以包含多行文本。根据实际需求,这里可以展示任务的详细要求、步骤说明、注意事项等。任务内容应该清晰明了,便于负责人理解和执行。',
submitRecords: []
});
uni.navigateTo({
url: `/pages/task-detail/index?id=${task.id}`
});
};
// 处理逾期任务
const handleOverdueTask = (task) => {
goToTaskDetail(task);
};
// 查看公告
const viewAnnouncement = (announcement) => {
console.log('查看公告:', announcement);
uni.showToast({
title: '查看公告详情',
icon: 'none'
});
};
// 获取项目状态标签类型
const getProjectStatusType = (label) => {
const typeMap = {
'运行中': 'success',
'运维中': 'primary',
'即将到期': 'warning',
'开发超期': 'error'
};
return typeMap[label] || 'primary';
};
// 获取项目状态标签文本
const getProjectStatusTag = (label) => {
const tagMap = {
'运行中': '正常',
'运维中': '进行中',
'即将到期': '待处理',
'开发超期': '超期'
};
return tagMap[label] || '';
};
// 获取客户状态标签类型
const getCustomerStatusType = (label) => {
const typeMap = {
'今日新增': 'success',
'今日已跟进': 'primary',
'今日待跟进': 'warning',
'即将跟进': 'info'
};
return typeMap[label] || 'primary';
};
// 获取客户状态标签文本
const getCustomerStatusTag = (label) => {
const tagMap = {
'今日新增': '新增',
'今日已跟进': '已完成',
'今日待跟进': '待处理',
'即将跟进': '即将'
};
return tagMap[label] || '';
};
</script>
<style lang="scss" scoped>
.dashboard-scroll {
width: 100%;
height: 100%;
}
.dashboard-content {
padding: 10px 30rpx;
}
.task-overview {
display: flex;
justify-content: space-between;
margin-top: 10px;
margin-bottom: 20px;
gap: 12px;
}
.task-card {
flex: 1;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
padding: 12px 8px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.04);
}
.task-card:active {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.task-count {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.task-label-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.task-label {
font-size: 12px;
color: #666;
}
.overdue-section {
margin-bottom: 20px;
}
.overdue-card {
background: linear-gradient(135deg, #fff5f5 0%, #ffe6e6 100%);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
display: flex;
align-items: flex-start;
position: relative;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 12px rgba(255, 68, 68, 0.1);
border: 1px solid rgba(255, 68, 68, 0.1);
}
.overdue-card:active {
transform: scale(0.98);
box-shadow: 0 1px 8px rgba(255, 68, 68, 0.15);
opacity: 0.9;
}
.overdue-badge-wrapper {
position: absolute;
left: 0;
top: 0;
z-index: 1;
}
.overdue-content {
flex: 1;
margin-left: 50px;
display: flex;
flex-direction: column;
gap: 8px;
}
.overdue-header {
display: flex;
align-items: center;
gap: 8px;
}
.overdue-meta {
display: flex;
flex-direction: column;
gap: 4px;
}
.overdue-date {
font-size: 14px;
color: #333;
font-weight: 500;
}
.overdue-project,
.overdue-desc,
.overdue-owner,
.overdue-time {
font-size: 12px;
color: #666;
line-height: 1.5;
}
.overdue-action {
margin-left: 12px;
display: flex;
align-items: center;
}
.carousel-dots {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 8px;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #ddd;
}
.dot.active {
background: #2885ff;
}
.announcement-section,
.project-status-section,
.customer-status-section {
margin-top: 8px;
}
.section-header {
display: flex;
align-items: center;
margin-bottom: 12px;
gap: 8px;
}
.section-icon {
font-size: 18px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
}
.announcement-item {
background: #fff;
border-radius: 8px;
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.announcement-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.announcement-title {
font-size: 14px;
font-weight: 500;
color: #333;
}
.announcement-desc {
font-size: 12px;
color: #666;
line-height: 1.5;
}
.announcement-time {
font-size: 12px;
color: #999;
}
.arrow {
font-size: 20px;
color: #999;
margin-left: 12px;
}
.status-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.status-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
padding: 12px 8px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid rgba(0, 0, 0, 0.04);
}
.status-card:active {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}
.status-count {
font-size: 20px;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.status-label-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: 100%;
}
.status-label {
font-size: 12px;
color: #666;
text-align: center;
}
</style>