<template>
|
<div class="app-container">
|
<!-- 顶部操作栏 -->
|
<div class="header-actions">
|
<div class="left-actions">
|
<el-select v-model="currentLevel" placeholder="选择计划级别" style="width: 150px" @change="handleLevelChange">
|
<el-option label="个人计划" value="personal" />
|
<el-option label="小组计划" value="group" />
|
<el-option label="部门计划" value="department" />
|
<el-option label="公司计划" value="company" />
|
</el-select>
|
<el-select v-model="currentPeriod" placeholder="选择时间周期" style="width: 120px; margin-left: 10px" @change="handlePeriodChange">
|
<el-option label="周计划" value="week" />
|
<el-option label="月计划" value="month" />
|
<el-option label="年计划" value="year" />
|
</el-select>
|
<el-date-picker
|
v-model="currentDate"
|
:type="datePickerType"
|
placeholder="选择日期"
|
style="width: 180px; margin-left: 10px"
|
@change="handleDateChange"
|
/>
|
</div>
|
<div class="right-actions">
|
<el-button type="primary" @click="handleAddPlan">新增计划</el-button>
|
<el-button @click="handleExport">导出计划</el-button>
|
<el-button @click="handleShare">共享计划</el-button>
|
</div>
|
</div>
|
|
<!-- 计划概览卡片 -->
|
<div class="overview-cards">
|
<el-row :gutter="20">
|
<el-col :span="6">
|
<el-card class="overview-card">
|
<div class="card-content">
|
<div class="card-icon personal">
|
<el-icon><User /></el-icon>
|
</div>
|
<div class="card-info">
|
<div class="card-title">个人计划</div>
|
<div class="card-number">{{ overviewData.personal.total }}</div>
|
<div class="card-progress">
|
<el-progress :percentage="overviewData.personal.completion" :stroke-width="6" />
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="overview-card">
|
<div class="card-content">
|
<div class="card-icon group">
|
<el-icon><UserFilled /></el-icon>
|
</div>
|
<div class="card-info">
|
<div class="card-title">小组计划</div>
|
<div class="card-number">{{ overviewData.group.total }}</div>
|
<div class="card-progress">
|
<el-progress :percentage="overviewData.group.completion" :stroke-width="6" />
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="overview-card">
|
<div class="card-content">
|
<div class="card-icon department">
|
<el-icon><OfficeBuilding /></el-icon>
|
</div>
|
<div class="card-info">
|
<div class="card-title">部门计划</div>
|
<div class="card-number">{{ overviewData.department.total }}</div>
|
<div class="card-progress">
|
<el-progress :percentage="overviewData.department.completion" :stroke-width="6" />
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
<el-col :span="6">
|
<el-card class="overview-card">
|
<div class="card-content">
|
<div class="card-icon company">
|
<el-icon><House /></el-icon>
|
</div>
|
<div class="card-info">
|
<div class="card-title">公司计划</div>
|
<div class="card-number">{{ overviewData.company.total }}</div>
|
<div class="card-progress">
|
<el-progress :percentage="overviewData.company.completion" :stroke-width="6" />
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</el-col>
|
</el-row>
|
</div>
|
|
<!-- 计划列表 -->
|
<div class="plan-content">
|
<el-card>
|
<template #header>
|
<div class="card-header">
|
<span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span>
|
<div>
|
<el-button size="small" @click="handleRefresh">刷新</el-button>
|
<el-button size="small" @click="handleFilter">筛选</el-button>
|
</div>
|
</div>
|
</template>
|
|
<div class="plan-list">
|
<div v-for="plan in planList" :key="plan.id" class="plan-item">
|
<div class="plan-header">
|
<div class="plan-title">
|
<el-tag :type="getPriorityType(plan.priority)" size="small">{{ getPriorityText(plan.priority) }}</el-tag>
|
<span class="title-text">{{ plan.title }}</span>
|
</div>
|
<div class="plan-actions">
|
<el-button size="small" @click="handleEditPlan(plan)">编辑</el-button>
|
<el-button size="small" @click="handleViewDetail(plan)">详情</el-button>
|
<el-dropdown @command="handleMoreAction">
|
<el-button size="small">
|
更多<el-icon class="el-icon--right"><ArrowDown /></el-icon>
|
</el-button>
|
<template #dropdown>
|
<el-dropdown-menu>
|
<el-dropdown-item command="share">共享</el-dropdown-item>
|
<el-dropdown-item command="copy">复制</el-dropdown-item>
|
<el-dropdown-item command="delete" divided>删除</el-dropdown-item>
|
</el-dropdown-menu>
|
</template>
|
</el-dropdown>
|
</div>
|
</div>
|
|
<div class="plan-content">
|
<div class="plan-description">{{ plan.description }}</div>
|
<div class="plan-meta">
|
<div class="meta-item">
|
<el-icon><Calendar /></el-icon>
|
<span>{{ plan.startDate }} - {{ plan.endDate }}</span>
|
</div>
|
<div class="meta-item">
|
<el-icon><User /></el-icon>
|
<span>{{ plan.assignee }}</span>
|
</div>
|
<div class="meta-item">
|
<el-icon><Clock /></el-icon>
|
<span>进度: {{ plan.progress }}%</span>
|
</div>
|
<div class="meta-item">
|
<el-icon><Flag /></el-icon>
|
<span>{{ getStatusText(plan.status) }}</span>
|
</div>
|
</div>
|
|
<div class="plan-progress">
|
<el-progress
|
:percentage="plan.progress"
|
:color="getProgressColor(plan.progress)"
|
:stroke-width="8"
|
/>
|
</div>
|
|
<div class="plan-tags">
|
<el-tag v-for="tag in plan.tags" :key="tag" size="small" style="margin-right: 5px">
|
{{ tag }}
|
</el-tag>
|
</div>
|
</div>
|
</div>
|
</div>
|
</el-card>
|
</div>
|
|
<!-- 新增/编辑计划对话框 -->
|
<el-dialog
|
v-model="planDialogVisible"
|
:title="dialogTitle"
|
width="600px"
|
@close="handleDialogClose"
|
>
|
<el-form :model="planForm" :rules="planRules" ref="planFormRef" label-width="100px">
|
<el-form-item label="计划标题" prop="title">
|
<el-input v-model="planForm.title" placeholder="请输入计划标题" />
|
</el-form-item>
|
<el-form-item label="计划描述" prop="description">
|
<el-input
|
v-model="planForm.description"
|
type="textarea"
|
:rows="3"
|
placeholder="请输入计划描述"
|
/>
|
</el-form-item>
|
<el-form-item label="计划级别" prop="level">
|
<el-select v-model="planForm.level" placeholder="选择计划级别" style="width: 100%">
|
<el-option label="个人计划" value="personal" />
|
<el-option label="小组计划" value="group" />
|
<el-option label="部门计划" value="department" />
|
<el-option label="公司计划" value="company" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="时间周期" prop="period">
|
<el-select v-model="planForm.period" placeholder="选择时间周期" style="width: 100%">
|
<el-option label="周计划" value="week" />
|
<el-option label="月计划" value="month" />
|
<el-option label="年计划" value="year" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="开始时间" prop="startDate">
|
<el-date-picker
|
v-model="planForm.startDate"
|
type="date"
|
placeholder="选择开始时间"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="结束时间" prop="endDate">
|
<el-date-picker
|
v-model="planForm.endDate"
|
type="date"
|
placeholder="选择结束时间"
|
style="width: 100%"
|
/>
|
</el-form-item>
|
<el-form-item label="负责人" prop="assignee">
|
<el-input v-model="planForm.assignee" placeholder="请输入负责人" />
|
</el-form-item>
|
<el-form-item label="优先级" prop="priority">
|
<el-select v-model="planForm.priority" placeholder="选择优先级" style="width: 100%">
|
<el-option label="高" value="high" />
|
<el-option label="中" value="medium" />
|
<el-option label="低" value="low" />
|
</el-select>
|
</el-form-item>
|
<el-form-item label="标签">
|
<el-input v-model="planForm.tags" placeholder="请输入标签,用逗号分隔" />
|
</el-form-item>
|
</el-form>
|
<template #footer>
|
<span class="dialog-footer">
|
<el-button @click="planDialogVisible = false">取消</el-button>
|
<el-button type="primary" @click="handleSavePlan">保存</el-button>
|
</span>
|
</template>
|
</el-dialog>
|
</div>
|
</template>
|
|
<script setup>
|
import { ref, reactive, computed, onMounted } from 'vue'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
import {
|
User,
|
UserFilled,
|
OfficeBuilding,
|
House,
|
Calendar,
|
Clock,
|
Flag,
|
ArrowDown
|
} from '@element-plus/icons-vue'
|
|
// 响应式数据
|
const currentLevel = ref('personal')
|
const currentPeriod = ref('week')
|
const currentDate = ref(new Date())
|
const planDialogVisible = ref(false)
|
const dialogTitle = ref('新增计划')
|
const planFormRef = ref()
|
|
// 表单数据
|
const planForm = reactive({
|
title: '',
|
description: '',
|
level: 'personal',
|
period: 'week',
|
startDate: '',
|
endDate: '',
|
assignee: '',
|
priority: 'medium',
|
tags: ''
|
})
|
|
// 表单验证规则
|
const planRules = {
|
title: [{ required: true, message: '请输入计划标题', trigger: 'blur' }],
|
description: [{ required: true, message: '请输入计划描述', trigger: 'blur' }],
|
level: [{ required: true, message: '请选择计划级别', trigger: 'change' }],
|
period: [{ required: true, message: '请选择时间周期', trigger: 'change' }],
|
startDate: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
|
endDate: [{ required: true, message: '请选择结束时间', trigger: 'change' }],
|
assignee: [{ required: true, message: '请输入负责人', trigger: 'blur' }],
|
priority: [{ required: true, message: '请选择优先级', trigger: 'change' }]
|
}
|
|
// 概览数据
|
const overviewData = reactive({
|
personal: { total: 12, completion: 75 },
|
group: { total: 8, completion: 60 },
|
department: { total: 15, completion: 45 },
|
company: { total: 6, completion: 30 }
|
})
|
|
// 计划列表数据
|
const planList = ref([
|
{
|
id: 1,
|
title: '产品需求分析',
|
description: '对新产品进行详细的需求分析和市场调研,制定产品规划方案',
|
level: 'personal',
|
period: 'week',
|
startDate: '2024-01-15',
|
endDate: '2024-01-21',
|
assignee: '陈志强',
|
priority: 'high',
|
status: 'in_progress',
|
progress: 80,
|
tags: ['产品', '分析', '调研']
|
},
|
{
|
id: 2,
|
title: '技术架构设计',
|
description: '设计系统技术架构,包括数据库设计、接口设计等',
|
level: 'group',
|
period: 'month',
|
startDate: '2024-01-01',
|
endDate: '2024-01-31',
|
assignee: '刘雅婷',
|
priority: 'high',
|
status: 'completed',
|
progress: 100,
|
tags: ['技术', '架构', '设计']
|
},
|
{
|
id: 3,
|
title: '市场推广计划',
|
description: '制定年度市场推广策略和营销计划',
|
level: 'department',
|
period: 'year',
|
startDate: '2024-01-01',
|
endDate: '2024-12-31',
|
assignee: '王建国',
|
priority: 'medium',
|
status: 'not_started',
|
progress: 0,
|
tags: ['市场', '推广', '营销']
|
},
|
{
|
id: 4,
|
title: '团队建设活动',
|
description: '组织团队建设活动,提升团队凝聚力和协作效率',
|
level: 'company',
|
period: 'month',
|
startDate: '2024-01-15',
|
endDate: '2024-02-15',
|
assignee: '赵丽华',
|
priority: 'low',
|
status: 'in_progress',
|
progress: 30,
|
tags: ['团队', '建设', '活动']
|
}
|
])
|
|
// 计算属性
|
const datePickerType = computed(() => {
|
switch (currentPeriod.value) {
|
case 'week':
|
return 'week'
|
case 'month':
|
return 'month'
|
case 'year':
|
return 'year'
|
default:
|
return 'date'
|
}
|
})
|
|
// 方法
|
const handleLevelChange = (value) => {
|
console.log('计划级别变更:', value)
|
// 这里可以根据级别筛选数据
|
}
|
|
const handlePeriodChange = (value) => {
|
console.log('时间周期变更:', value)
|
// 这里可以根据周期筛选数据
|
}
|
|
const handleDateChange = (value) => {
|
console.log('日期变更:', value)
|
// 这里可以根据日期筛选数据
|
}
|
|
const handleAddPlan = () => {
|
dialogTitle.value = '新增计划'
|
planDialogVisible.value = true
|
// 重置表单
|
Object.keys(planForm).forEach(key => {
|
planForm[key] = ''
|
})
|
planForm.level = 'personal'
|
planForm.period = 'week'
|
planForm.priority = 'medium'
|
}
|
|
const handleEditPlan = (plan) => {
|
dialogTitle.value = '编辑计划'
|
planDialogVisible.value = true
|
// 填充表单数据
|
Object.keys(planForm).forEach(key => {
|
if (key === 'tags') {
|
planForm[key] = plan[key].join(', ')
|
} else {
|
planForm[key] = plan[key]
|
}
|
})
|
}
|
|
const handleViewDetail = (plan) => {
|
ElMessage.info(`查看计划详情: ${plan.title}`)
|
}
|
|
const handleMoreAction = (command) => {
|
switch (command) {
|
case 'share':
|
ElMessage.success('计划已共享')
|
break
|
case 'copy':
|
ElMessage.success('计划已复制')
|
break
|
case 'delete':
|
ElMessageBox.confirm('确定要删除这个计划吗?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
ElMessage.success('计划已删除')
|
})
|
break
|
}
|
}
|
|
const handleSavePlan = async () => {
|
try {
|
await planFormRef.value.validate()
|
ElMessage.success('计划保存成功')
|
planDialogVisible.value = false
|
} catch (error) {
|
console.log('表单验证失败:', error)
|
}
|
}
|
|
const handleDialogClose = () => {
|
planFormRef.value?.resetFields()
|
}
|
|
const handleRefresh = () => {
|
ElMessage.success('数据已刷新')
|
}
|
|
const handleFilter = () => {
|
ElMessage.info('打开筛选面板')
|
}
|
|
const handleExport = () => {
|
ElMessage.success('计划已导出')
|
}
|
|
const handleShare = () => {
|
ElMessage.success('计划已共享')
|
}
|
|
const getCurrentLevelText = () => {
|
const levelMap = {
|
personal: '个人计划',
|
group: '小组计划',
|
department: '部门计划',
|
company: '公司计划'
|
}
|
return levelMap[currentLevel.value] || '个人计划'
|
}
|
|
const getCurrentPeriodText = () => {
|
const periodMap = {
|
week: '周计划',
|
month: '月计划',
|
year: '年计划'
|
}
|
return periodMap[currentPeriod.value] || '周计划'
|
}
|
|
const getPriorityType = (priority) => {
|
const typeMap = {
|
high: 'danger',
|
medium: 'warning',
|
low: 'info'
|
}
|
return typeMap[priority] || 'info'
|
}
|
|
const getPriorityText = (priority) => {
|
const textMap = {
|
high: '高',
|
medium: '中',
|
low: '低'
|
}
|
return textMap[priority] || '中'
|
}
|
|
const getStatusText = (status) => {
|
const statusMap = {
|
not_started: '未开始',
|
in_progress: '进行中',
|
completed: '已完成',
|
paused: '已暂停'
|
}
|
return statusMap[status] || '未知'
|
}
|
|
const getProgressColor = (progress) => {
|
if (progress >= 80) return '#67C23A'
|
if (progress >= 50) return '#E6A23C'
|
return '#F56C6C'
|
}
|
|
onMounted(() => {
|
console.log('多级计划模板页面已加载')
|
})
|
</script>
|
|
<style scoped>
|
.app-container {
|
padding: 20px;
|
background-color: #f5f5f5;
|
min-height: 100vh;
|
}
|
|
.header-actions {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 20px;
|
background: white;
|
padding: 20px;
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
}
|
|
.left-actions {
|
display: flex;
|
align-items: center;
|
}
|
|
.right-actions {
|
display: flex;
|
gap: 10px;
|
}
|
|
.overview-cards {
|
margin-bottom: 20px;
|
}
|
|
.overview-card {
|
height: 120px;
|
}
|
|
.card-content {
|
display: flex;
|
align-items: center;
|
height: 100%;
|
}
|
|
.card-icon {
|
width: 60px;
|
height: 60px;
|
border-radius: 50%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-right: 15px;
|
font-size: 24px;
|
color: white;
|
}
|
|
.card-icon.personal {
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
}
|
|
.card-icon.group {
|
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
}
|
|
.card-icon.department {
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
}
|
|
.card-icon.company {
|
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
}
|
|
.card-info {
|
flex: 1;
|
}
|
|
.card-title {
|
font-size: 14px;
|
color: #666;
|
margin-bottom: 5px;
|
}
|
|
.card-number {
|
font-size: 24px;
|
font-weight: bold;
|
color: #333;
|
margin-bottom: 10px;
|
}
|
|
.card-progress {
|
width: 100%;
|
}
|
|
.plan-content {
|
background: white;
|
border-radius: 8px;
|
overflow: hidden;
|
}
|
|
.card-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.header-actions {
|
display: flex;
|
gap: 10px;
|
}
|
|
.plan-list {
|
padding: 20px 0;
|
}
|
|
.plan-item {
|
border: 1px solid #e4e7ed;
|
border-radius: 8px;
|
margin-bottom: 15px;
|
padding: 20px;
|
transition: all 0.3s ease;
|
}
|
|
.plan-item:hover {
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
transform: translateY(-2px);
|
}
|
|
.plan-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
margin-bottom: 15px;
|
}
|
|
.plan-title {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.title-text {
|
font-size: 16px;
|
font-weight: bold;
|
color: #333;
|
}
|
|
.plan-actions {
|
display: flex;
|
gap: 10px;
|
}
|
|
.plan-content {
|
margin-bottom: 15px;
|
}
|
|
.plan-description {
|
color: #666;
|
margin-bottom: 15px;
|
line-height: 1.6;
|
}
|
|
.plan-meta {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 20px;
|
margin-bottom: 15px;
|
}
|
|
.meta-item {
|
display: flex;
|
align-items: center;
|
gap: 5px;
|
color: #666;
|
font-size: 14px;
|
}
|
|
.plan-progress {
|
margin-bottom: 15px;
|
}
|
|
.plan-tags {
|
display: flex;
|
flex-wrap: wrap;
|
gap: 5px;
|
}
|
|
.dialog-footer {
|
display: flex;
|
justify-content: flex-end;
|
gap: 10px;
|
}
|
|
/* 响应式设计 */
|
@media (max-width: 768px) {
|
.header-actions {
|
flex-direction: column;
|
gap: 15px;
|
}
|
|
.left-actions {
|
flex-wrap: wrap;
|
gap: 10px;
|
}
|
|
.plan-meta {
|
flex-direction: column;
|
gap: 10px;
|
}
|
|
.plan-header {
|
flex-direction: column;
|
align-items: flex-start;
|
gap: 10px;
|
}
|
}
|
</style>
|