| | |
| | | <!-- 顶部操作栏 --> |
| | | <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 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 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="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 180px; margin-left: 10px" |
| | | @change="handleDateChange" |
| | | /> |
| | | <el-date-picker v-model="currentDate" |
| | | :type="datePickerType" |
| | | placeholder="选择日期" |
| | | format="YYYY-MM-DD" |
| | | value-format="YYYY-MM-DD" |
| | | style="width: 180px; margin-left: 10px" |
| | | @change="handleDateChange" /> |
| | | </div> |
| | | <div class="right-actions"> |
| | | <el-button type="primary" @click="handleAddPlan">新增计划</el-button> |
| | | <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-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon personal"> |
| | | <el-icon><User /></el-icon> |
| | | <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" /> |
| | | <el-progress :percentage="overviewData.personal.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon group"> |
| | | <el-icon><UserFilled /></el-icon> |
| | | <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" /> |
| | | <el-progress :percentage="overviewData.group.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon department"> |
| | | <el-icon><OfficeBuilding /></el-icon> |
| | | <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" /> |
| | | <el-progress :percentage="overviewData.department.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <el-card class="overview-card"> |
| | | <div class="card-content"> |
| | | <div class="card-icon company"> |
| | | <el-icon><House /></el-icon> |
| | | <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" /> |
| | | <el-progress :percentage="overviewData.company.completion" |
| | | :stroke-width="6" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </el-col> |
| | | </el-row> |
| | | </div> |
| | | |
| | | <!-- 计划列表 --> |
| | | <div class="plan-content"> |
| | | <el-card> |
| | |
| | | <div class="card-header"> |
| | | <span>{{ getCurrentLevelText() }} - {{ getCurrentPeriodText() }}</span> |
| | | <div> |
| | | <el-button size="small" @click="handleRefresh">刷新</el-button> |
| | | <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 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> |
| | | <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-button size="small" |
| | | @click="handleEditPlan(plan)">编辑</el-button> |
| | | <el-button size="small" |
| | | @click="handleViewDetail(plan)">详情</el-button> |
| | | <el-dropdown @command="(command) => handleMoreAction(plan, command)"> |
| | | <el-button size="small"> |
| | | 更多<el-icon class="el-icon--right"><ArrowDown /></el-icon> |
| | | 更多<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-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> |
| | | <el-icon> |
| | | <Calendar /> |
| | | </el-icon> |
| | | <span>{{ plan.startDate }} - {{ plan.endDate }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><User /></el-icon> |
| | | <el-icon> |
| | | <User /> |
| | | </el-icon> |
| | | <span>{{ plan.assignee }}</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><Clock /></el-icon> |
| | | <el-icon> |
| | | <Clock /> |
| | | </el-icon> |
| | | <span>进度: {{ plan.progress }}%</span> |
| | | </div> |
| | | <div class="meta-item"> |
| | | <el-icon><Flag /></el-icon> |
| | | <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" |
| | | /> |
| | | <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"> |
| | | <el-tag v-for="tag in plan.tags" |
| | | :key="tag" |
| | | size="small" |
| | | style="margin-right: 5px"> |
| | | {{ tag }} |
| | | </el-tag> |
| | | </div> |
| | |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | |
| | | <!-- 新增/编辑计划对话框 --> |
| | | <el-dialog |
| | | v-model="planDialogVisible" |
| | | :title="operationType === 'add' ? '发布计划' : '编辑计划'" |
| | | 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-dialog v-model="planDialogVisible" |
| | | :title="operationType === 'add' ? '发布计划' : '编辑计划'" |
| | | 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 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-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-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" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择开始时间" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="开始时间" |
| | | prop="startDate"> |
| | | <el-date-picker v-model="planForm.startDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择开始时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="结束时间" prop="endDate"> |
| | | <el-date-picker |
| | | v-model="planForm.endDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择结束时间" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="结束时间" |
| | | prop="endDate"> |
| | | <el-date-picker v-model="planForm.endDate" |
| | | type="date" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | placeholder="选择结束时间" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="负责人" prop="assignee"> |
| | | <el-input v-model="planForm.assignee" placeholder="请输入负责人" /> |
| | | <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-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-item label="标签" prop="tags"> |
| | | <el-form-item label="标签" |
| | | prop="tags"> |
| | | <!-- <el-checkbox-group v-model="planForm.tags"> |
| | | <el-checkbox label="all"></el-checkbox> |
| | | <el-checkbox label="manager">管理层</el-checkbox> |
| | |
| | | <el-checkbox label="finance">财务部门</el-checkbox> |
| | | <el-checkbox label="tech">技术部门</el-checkbox> |
| | | </el-checkbox-group> --> |
| | | <el-select |
| | | v-model="planForm.tags" |
| | | multiple |
| | | placeholder="请选择标签" |
| | | style="width: 100%" |
| | | > |
| | | <el-option |
| | | v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" |
| | | /> |
| | | <el-select v-model="planForm.tags" |
| | | multiple |
| | | placeholder="请选择标签" |
| | | style="width: 100%"> |
| | | <el-option v-for="dept in departments" |
| | | :key="dept" |
| | | :label="dept" |
| | | :value="dept" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="状态" prop="status"> |
| | | <el-select v-model="planForm.status" placeholder="选择状态" style="width: 100%"> |
| | | <el-option label="未开始" value="not_started" /> |
| | | <el-option label="进行中" value="in_progress" /> |
| | | <el-option label="已完成" value="completed" /> |
| | | <el-option label="已暂停" value="paused" /> |
| | | <el-form-item label="状态" |
| | | prop="status"> |
| | | <el-select v-model="planForm.status" |
| | | placeholder="选择状态" |
| | | style="width: 100%"> |
| | | <el-option label="未开始" |
| | | value="not_started" /> |
| | | <el-option label="进行中" |
| | | value="in_progress" /> |
| | | <el-option label="已完成" |
| | | value="completed" /> |
| | | <el-option label="已暂停" |
| | | value="paused" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="进度" prop="progress"> |
| | | <el-input-number |
| | | v-model="planForm.progress" |
| | | min="0" |
| | | max="100" |
| | | step="1" |
| | | placeholder="请输入进度" |
| | | style="width: 100%" |
| | | /> |
| | | <el-form-item label="进度" |
| | | prop="progress"> |
| | | <el-input-number v-model="planForm.progress" |
| | | min="0" |
| | | max="100" |
| | | step="1" |
| | | placeholder="请输入进度" |
| | | style="width: 100%" /> |
| | | </el-form-item> |
| | | </el-form> |
| | | <template #footer> |
| | | <span class="dialog-footer"> |
| | | <el-button type="primary" |
| | | @click="handleSavePlan">保存</el-button> |
| | | <el-button @click="planDialogVisible = false">取消</el-button> |
| | | <el-button type="primary" @click="handleSavePlan">保存</el-button> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <!-- 计划详情对话框 --> |
| | | <el-dialog v-model="showPlanDetailDialog" title="计划详情" width="700px"> |
| | | <div v-if="currentPlanDetail" class="mb10"> |
| | | <el-descriptions :column="2" border> |
| | | <el-dialog v-model="showPlanDetailDialog" |
| | | title="计划详情" |
| | | width="700px"> |
| | | <div v-if="currentPlanDetail" |
| | | class="mb10"> |
| | | <el-descriptions :column="2" |
| | | border> |
| | | <el-descriptions-item label="计划标题">{{ currentPlanDetail.title }}</el-descriptions-item> |
| | | <el-descriptions-item label="计划描述">{{ currentPlanDetail.description }}</el-descriptions-item> |
| | | <el-descriptions-item label="计划级别">{{ getCurrentLevelText(currentPlanDetail.level) }}</el-descriptions-item> |
| | |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, reactive, computed, onMounted } from 'vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | User, |
| | | UserFilled, |
| | | OfficeBuilding, |
| | | House, |
| | | Calendar, |
| | | Clock, |
| | | Flag, |
| | | ArrowDown |
| | | } from '@element-plus/icons-vue' |
| | | import { listDutyPlan, addDutyPlan, updateDutyPlan, delDutyPlan,NumDutyPlan,exportDutyPlan } from '@/api/collaborativeApproval/planTemplate.js' |
| | | import { ref, reactive, computed, onMounted } from "vue"; |
| | | import { ElMessage, ElMessageBox } from "element-plus"; |
| | | const { proxy } = getCurrentInstance(); |
| | | import { |
| | | User, |
| | | UserFilled, |
| | | OfficeBuilding, |
| | | House, |
| | | Calendar, |
| | | Clock, |
| | | Flag, |
| | | ArrowDown, |
| | | } from "@element-plus/icons-vue"; |
| | | import { |
| | | listDutyPlan, |
| | | addDutyPlan, |
| | | updateDutyPlan, |
| | | delDutyPlan, |
| | | NumDutyPlan, |
| | | exportDutyPlan, |
| | | } from "@/api/collaborativeApproval/planTemplate.js"; |
| | | |
| | | // 响应式数据 |
| | | const operationType = ref('add') |
| | | const currentLevel = ref('personal') |
| | | const currentPeriod = ref('week') |
| | | const currentDate = ref(new Date()) |
| | | const planDialogVisible = ref(false) |
| | | const dialogTitle = ref('新增计划') |
| | | const planFormRef = ref() |
| | | const showPlanDetailDialog = ref(false) |
| | | const currentPlanDetail = ref(null) |
| | | // 响应式数据 |
| | | const operationType = ref("add"); |
| | | const currentLevel = ref("personal"); |
| | | const currentPeriod = ref("week"); |
| | | const currentDate = ref(new Date()); |
| | | const planDialogVisible = ref(false); |
| | | const dialogTitle = ref("新增计划"); |
| | | const planFormRef = ref(); |
| | | const showPlanDetailDialog = ref(false); |
| | | const currentPlanDetail = ref(null); |
| | | |
| | | // 表单数据 |
| | | const planForm = reactive({ |
| | | id: '', |
| | | title: '', |
| | | description: '', |
| | | level: 'personal', |
| | | period: 'week', |
| | | startDate: '', |
| | | endDate: '', |
| | | assignee: '', |
| | | priority: 'medium', |
| | | tags: [], |
| | | status: '', |
| | | progress: 0 |
| | | }) |
| | | // 表单数据 |
| | | const planForm = reactive({ |
| | | id: "", |
| | | title: "", |
| | | description: "", |
| | | level: "personal", |
| | | period: "week", |
| | | startDate: "", |
| | | endDate: "", |
| | | assignee: "", |
| | | priority: "medium", |
| | | tags: [], |
| | | status: "", |
| | | progress: 0, |
| | | }); |
| | | |
| | | // 表单验证规则 |
| | | 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 departments = ["产品", "分析", "调研",'技术', '架构', '设计','市场', '推广', '营销']; |
| | | // 概览数据 |
| | | const overviewData = reactive({ |
| | | personal: { total: 0, completion: 0 }, |
| | | group: { total: 0, completion: 0 }, |
| | | department: { total: 0, completion: 0 }, |
| | | company: { total: 0, completion: 0 } |
| | | }) |
| | | // 表单验证规则 |
| | | 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 departments = [ |
| | | "产品", |
| | | "分析", |
| | | "调研", |
| | | "技术", |
| | | "架构", |
| | | "设计", |
| | | "市场", |
| | | "推广", |
| | | "营销", |
| | | ]; |
| | | // 概览数据 |
| | | const overviewData = reactive({ |
| | | personal: { total: 0, completion: 0 }, |
| | | group: { total: 0, completion: 0 }, |
| | | department: { total: 0, completion: 0 }, |
| | | company: { total: 0, completion: 0 }, |
| | | }); |
| | | |
| | | // 计划列表数据 |
| | | const planList = ref([]) |
| | | // 计划列表数据 |
| | | const planList = ref([]); |
| | | |
| | | // 计算属性 |
| | | 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) |
| | | getPlanList() |
| | | // 这里可以根据级别筛选数据 |
| | | } |
| | | |
| | | const handlePeriodChange = (value) => { |
| | | console.log('时间周期变更:', value) |
| | | getPlanList() |
| | | // 这里可以根据周期筛选数据 |
| | | } |
| | | |
| | | const handleDateChange = (value) => { |
| | | console.log('日期变更:', value) |
| | | getPlanList() |
| | | // 这里可以根据日期筛选数据 |
| | | } |
| | | |
| | | const handleAddPlan = () => { |
| | | operationType.value = 'add' |
| | | dialogTitle.value = '新增计划' |
| | | planDialogVisible.value = true |
| | | // 重置表单 |
| | | Object.keys(planForm).forEach(key => { |
| | | planForm[key] = '' |
| | | }) |
| | | planForm.level = 'personal' |
| | | planForm.period = 'week' |
| | | planForm.priority = 'medium' |
| | | planForm.status = 'not_started' |
| | | planForm.progress = 0 |
| | | } |
| | | |
| | | const handleEditPlan = (plan) => { |
| | | operationType.value = 'edit' |
| | | dialogTitle.value = '编辑计划' |
| | | planDialogVisible.value = true |
| | | Object.assign(planForm, plan) |
| | | // // 填充表单数据 |
| | | // Object.keys(planForm).forEach(key => { |
| | | // if (key === 'tags') { |
| | | // planForm[key] = plan[key].join(', ') |
| | | // } else { |
| | | // planForm[key] = plan[key] |
| | | // } |
| | | // }) |
| | | } |
| | | |
| | | const handleViewDetail = (plan) => { |
| | | currentPlanDetail.value = plan |
| | | showPlanDetailDialog.value = true |
| | | // ElMessage.info(`查看计划详情: ${plan.title}`) |
| | | } |
| | | |
| | | const handleMoreAction = async(plan,command) => { |
| | | let ids = []; |
| | | ids.push(plan.id); |
| | | console.log("ids",ids) |
| | | switch (command) { |
| | | case 'share': |
| | | ElMessage.success('计划已共享') |
| | | break |
| | | case 'copy': |
| | | ElMessage.success('计划已复制') |
| | | break |
| | | case 'delete': |
| | | ElMessageBox.confirm('确定要删除这个计划吗?', '提示', { |
| | | confirmButtonText: '确定', |
| | | cancelButtonText: '取消', |
| | | type: 'warning' |
| | | }).then(() => { |
| | | |
| | | delDutyPlan(ids).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划已删除') |
| | | ids.value = []; |
| | | getPlanList() |
| | | } |
| | | }) |
| | | }) |
| | | break |
| | | } |
| | | } |
| | | // |
| | | const handleSavePlan = async () => { |
| | | try { |
| | | await planFormRef.value.validate() |
| | | if (operationType.value === 'add') { |
| | | addDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划保存成功') |
| | | planDialogVisible.value = false |
| | | } |
| | | getPlanList() |
| | | }) |
| | | } else { |
| | | |
| | | updateDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success('计划保存成功') |
| | | planDialogVisible.value = false |
| | | } |
| | | getPlanList() |
| | | }) |
| | | // 计算属性 |
| | | const datePickerType = computed(() => { |
| | | switch (currentPeriod.value) { |
| | | case "week": |
| | | return "week"; |
| | | case "month": |
| | | return "month"; |
| | | case "year": |
| | | return "year"; |
| | | default: |
| | | return "date"; |
| | | } |
| | | } catch (error) { |
| | | console.log('表单验证失败:', error) |
| | | } |
| | | } |
| | | }); |
| | | |
| | | const handleDialogClose = () => { |
| | | planFormRef.value?.resetFields() |
| | | } |
| | | // 方法 |
| | | const handleLevelChange = value => { |
| | | console.log("计划级别变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据级别筛选数据 |
| | | }; |
| | | |
| | | const handleRefresh = () => { |
| | | getPlanList() |
| | | // ElMessage.success('数据已刷新') |
| | | } |
| | | const handlePeriodChange = value => { |
| | | console.log("时间周期变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据周期筛选数据 |
| | | }; |
| | | |
| | | const handleFilter = () => { |
| | | ElMessage.info('打开筛选面板') |
| | | } |
| | | const handleDateChange = value => { |
| | | console.log("日期变更:", value); |
| | | getPlanList(); |
| | | // 这里可以根据日期筛选数据 |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // exportDutyPlan().then(res => { |
| | | |
| | | // }) |
| | | proxy.download("/dutyPlan/export", {}, "计划管理.xlsx"); |
| | | }) |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | const handleAddPlan = () => { |
| | | operationType.value = "add"; |
| | | dialogTitle.value = "新增计划"; |
| | | planDialogVisible.value = true; |
| | | // 重置表单 |
| | | Object.keys(planForm).forEach(key => { |
| | | planForm[key] = ""; |
| | | }); |
| | | }; |
| | | const handleShare = () => { |
| | | ElMessage.success('计划已共享') |
| | | } |
| | | planForm.level = "personal"; |
| | | planForm.period = "week"; |
| | | planForm.priority = "medium"; |
| | | planForm.status = "not_started"; |
| | | planForm.progress = 0; |
| | | }; |
| | | |
| | | const getCurrentLevelText = () => { |
| | | const levelMap = { |
| | | personal: '个人计划', |
| | | group: '小组计划', |
| | | department: '部门计划', |
| | | company: '公司计划' |
| | | } |
| | | return levelMap[currentLevel.value] || '个人计划' |
| | | } |
| | | const handleEditPlan = plan => { |
| | | operationType.value = "edit"; |
| | | dialogTitle.value = "编辑计划"; |
| | | planDialogVisible.value = true; |
| | | Object.assign(planForm, plan); |
| | | // // 填充表单数据 |
| | | // Object.keys(planForm).forEach(key => { |
| | | // if (key === 'tags') { |
| | | // planForm[key] = plan[key].join(', ') |
| | | // } else { |
| | | // planForm[key] = plan[key] |
| | | // } |
| | | // }) |
| | | }; |
| | | |
| | | const getCurrentPeriodText = () => { |
| | | const periodMap = { |
| | | week: '周计划', |
| | | month: '月计划', |
| | | year: '年计划' |
| | | } |
| | | return periodMap[currentPeriod.value] || '周计划' |
| | | } |
| | | const handleViewDetail = plan => { |
| | | currentPlanDetail.value = plan; |
| | | showPlanDetailDialog.value = true; |
| | | // ElMessage.info(`查看计划详情: ${plan.title}`) |
| | | }; |
| | | |
| | | const getPriorityType = (priority) => { |
| | | const typeMap = { |
| | | high: 'danger', |
| | | medium: 'warning', |
| | | low: 'info' |
| | | } |
| | | return typeMap[priority] || 'info' |
| | | } |
| | | const handleMoreAction = async (plan, command) => { |
| | | let ids = []; |
| | | ids.push(plan.id); |
| | | console.log("ids", ids); |
| | | switch (command) { |
| | | case "share": |
| | | ElMessage.success("计划已共享"); |
| | | break; |
| | | case "copy": |
| | | const knowledgeText = ` |
| | | 计划标题:${plan.title} |
| | | 计划描述:${plan.description} |
| | | 计划级别:${getCurrentLevelText(plan.level)} |
| | | 时间周期:${getCurrentPeriodText(plan.period)} |
| | | 开始时间:${plan.startDate} |
| | | 结束时间:${plan.endDate} |
| | | 负责人:${plan.assignee} |
| | | 优先级:${getPriorityText(plan.priority)} |
| | | 标签:${plan.tags.join(", ")} |
| | | 状态:${getStatusText(plan.status)} |
| | | 进度:${plan.progress}% |
| | | `.trim(); |
| | | |
| | | 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' |
| | | } |
| | | //获取数据列表 |
| | | const getPlanList = async () => { |
| | | const params = { |
| | | level: currentLevel.value, |
| | | period: currentPeriod.value, |
| | | queryDate:currentDate.value |
| | | } |
| | | listDutyPlan(params).then(res => { |
| | | if (res.code === 200) { |
| | | planList.value = res.data.records |
| | | // 复制到剪贴板 |
| | | navigator.clipboard |
| | | .writeText(knowledgeText) |
| | | .then(() => { |
| | | ElMessage.success("知识内容已复制到剪贴板"); |
| | | }) |
| | | .catch(() => { |
| | | ElMessage.error("复制失败,请手动复制"); |
| | | }); |
| | | // ElMessage.success('计划已复制') |
| | | break; |
| | | case "delete": |
| | | ElMessageBox.confirm("确定要删除这个计划吗?", "提示", { |
| | | confirmButtonText: "确定", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }).then(() => { |
| | | delDutyPlan(ids).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("计划已删除"); |
| | | ids.value = []; |
| | | getPlanList(); |
| | | } |
| | | }); |
| | | }); |
| | | break; |
| | | } |
| | | }).catch(err => { |
| | | console.log(err) |
| | | }) |
| | | } |
| | | //获取数据 |
| | | const getPlanNum = async () => { |
| | | NumDutyPlan().then(res => { |
| | | if (res.code === 200) { |
| | | // console.log(res.data) |
| | | //讲结果里面的数据根据level 赋值给overviewData |
| | | res.data.forEach(item => { |
| | | overviewData[item.level].total = item.num |
| | | overviewData[item.level].completion = item.completion |
| | | }; |
| | | // |
| | | const handleSavePlan = async () => { |
| | | try { |
| | | await planFormRef.value.validate(); |
| | | if (operationType.value === "add") { |
| | | addDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("计划保存成功"); |
| | | planDialogVisible.value = false; |
| | | } |
| | | getPlanList(); |
| | | }); |
| | | } else { |
| | | updateDutyPlan(planForm).then(res => { |
| | | if (res.code === 200) { |
| | | ElMessage.success("计划保存成功"); |
| | | planDialogVisible.value = false; |
| | | } |
| | | getPlanList(); |
| | | }); |
| | | } |
| | | } catch (error) { |
| | | console.log("表单验证失败:", error); |
| | | } |
| | | }; |
| | | |
| | | const handleDialogClose = () => { |
| | | planFormRef.value?.resetFields(); |
| | | }; |
| | | |
| | | const handleRefresh = () => { |
| | | getPlanList(); |
| | | // ElMessage.success('数据已刷新') |
| | | }; |
| | | |
| | | const handleFilter = () => { |
| | | ElMessage.info("打开筛选面板"); |
| | | }; |
| | | |
| | | const handleExport = () => { |
| | | ElMessageBox.confirm("是否确认导出?", "导出", { |
| | | confirmButtonText: "确认", |
| | | cancelButtonText: "取消", |
| | | type: "warning", |
| | | }) |
| | | .then(() => { |
| | | // exportDutyPlan().then(res => { |
| | | |
| | | // }) |
| | | proxy.download("/dutyPlan/export", {}, "计划管理.xlsx"); |
| | | }) |
| | | |
| | | } |
| | | }).catch(err => { |
| | | console.log(err) |
| | | }) |
| | | } |
| | | .catch(() => { |
| | | proxy.$modal.msg("已取消"); |
| | | }); |
| | | }; |
| | | const handleShare = () => { |
| | | ElMessage.success("计划已共享"); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getPlanList() |
| | | getPlanNum() |
| | | console.log('多级计划模板页面已加载') |
| | | }) |
| | | 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"; |
| | | }; |
| | | //获取数据列表 |
| | | const getPlanList = async () => { |
| | | const params = { |
| | | level: currentLevel.value, |
| | | period: currentPeriod.value, |
| | | queryDate: currentDate.value, |
| | | }; |
| | | listDutyPlan(params) |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | planList.value = res.data.records; |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err); |
| | | }); |
| | | }; |
| | | //获取数据 |
| | | const getPlanNum = async () => { |
| | | NumDutyPlan() |
| | | .then(res => { |
| | | if (res.code === 200) { |
| | | // console.log(res.data) |
| | | //讲结果里面的数据根据level 赋值给overviewData |
| | | res.data.forEach(item => { |
| | | overviewData[item.level].total = item.num; |
| | | overviewData[item.level].completion = item.completion; |
| | | }); |
| | | } |
| | | }) |
| | | .catch(err => { |
| | | console.log(err); |
| | | }); |
| | | }; |
| | | |
| | | onMounted(() => { |
| | | getPlanList(); |
| | | getPlanNum(); |
| | | console.log("多级计划模板页面已加载"); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .app-container { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | } |
| | | .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; |
| | | 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 { |
| | | flex-wrap: wrap; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .right-actions { |
| | | display: flex; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .plan-meta { |
| | | flex-direction: column; |
| | | |
| | | .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 { |
| | | flex-direction: column; |
| | | align-items: flex-start; |
| | | 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> |