2024-12-20 19:28:44 +08:00
|
|
|
|
<template>
|
2025-02-10 15:00:46 +08:00
|
|
|
|
<view class="container">
|
|
|
|
|
|
<u-navbar
|
|
|
|
|
|
class="u-navbar"
|
|
|
|
|
|
title="日计划管理"
|
|
|
|
|
|
placeholder
|
|
|
|
|
|
@leftClick="leftClick"
|
|
|
|
|
|
leftIconColor="#fff"
|
|
|
|
|
|
bgColor="#00337A"
|
|
|
|
|
|
:titleStyle="{ color: '#FFF', fontSize: '32rpx' }"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<!-- Header -->
|
|
|
|
|
|
<view class="header">
|
|
|
|
|
|
<view class="date-section">
|
|
|
|
|
|
<picker mode="date" :value="currentDate" @change="onDateChange">
|
|
|
|
|
|
<view class="date-picker">
|
|
|
|
|
|
<image src="@/static/realName/attendance.png" class="calendar-icon"></image>
|
|
|
|
|
|
<text>{{ currentDate }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</picker>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="progress-section">
|
|
|
|
|
|
<text class="reminder-text">请于每日18:00点前提交明日计划</text>
|
|
|
|
|
|
<button class="primary-btn" @click="developDailyPlan">制定日计划</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="project-title">
|
|
|
|
|
|
<text>{{ proName }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="completion-rate">
|
|
|
|
|
|
<text>作业计划完成率:{{ totalComplete }}%</text>
|
|
|
|
|
|
<text>月完成计划完成率:{{ monthComplete }}%</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<view class="task-list">
|
|
|
|
|
|
<block v-for="(task, index) in taskList" :key="index">
|
|
|
|
|
|
<view class="task-item">
|
|
|
|
|
|
<view class="task-header">
|
|
|
|
|
|
<text class="timestamp">{{ task.day }}</text>
|
|
|
|
|
|
<view :class="['status', getStatusClass(task)]">{{ getTaskStatusText(task) }}</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="metrics-container">
|
|
|
|
|
|
<view class="metric-row">
|
|
|
|
|
|
<text class="metric-item">总计划数量:{{ task.totalNum }}</text>
|
|
|
|
|
|
<text class="metric-item">月计划数量:{{ task.completeNumMonth }}</text>
|
|
|
|
|
|
<text class="metric-item">计划数量:{{ task.planCompleteNumDay }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="metric-row">
|
|
|
|
|
|
<text class="metric-item">累计完成作业量:{{ task.completeNumDay }}</text>
|
|
|
|
|
|
<text class="metric-item">月累计完成作业量:{{ task.completeNumAllMonth }}</text>
|
|
|
|
|
|
<text class="metric-item">作业人数:{{ task.planWorkNum }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="actions">
|
|
|
|
|
|
<button class="action-btn" @click="detailsPlan(task)">详情</button>
|
|
|
|
|
|
<button class="action-btn" @click="uploadPlan(task)" v-if="task.path == null || task.path === ''">
|
|
|
|
|
|
完成电子上传
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button class="action-btn" @click="editPlan(task)" v-if="task.path == null || task.path === ''">
|
|
|
|
|
|
完成情况修改
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</block>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
2024-12-20 19:28:44 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
2024-12-30 21:18:13 +08:00
|
|
|
|
<script>
|
2025-02-10 15:00:46 +08:00
|
|
|
|
// 定义一个不依赖于Vue实例的日期格式化函数
|
|
|
|
|
|
function formatDate(date) {
|
|
|
|
|
|
const year = date.getFullYear()
|
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
|
|
|
|
return `${year}-${month}-${day}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
import config from '@/config'
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
data() {
|
|
|
|
|
|
return {
|
|
|
|
|
|
currentDate: formatDate(new Date()),
|
|
|
|
|
|
proId: uni.getStorageSync('realNameUser').proId,
|
|
|
|
|
|
proName: '',
|
|
|
|
|
|
taskList: [],
|
|
|
|
|
|
totalComplete: '0.00',
|
|
|
|
|
|
monthComplete: '0.00',
|
|
|
|
|
|
id: '',
|
|
|
|
|
|
dayId: ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
created() {
|
|
|
|
|
|
// 如果需要在组件创建后再次设置currentDate为最新的当前日期,可以在这里做
|
|
|
|
|
|
this.currentDate = formatDate(new Date())
|
|
|
|
|
|
this.getListData()
|
|
|
|
|
|
},
|
|
|
|
|
|
onLoad() {
|
|
|
|
|
|
// this.getListData()
|
|
|
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
|
|
|
onDateChange(e) {
|
|
|
|
|
|
this.currentDate = e.detail.value
|
|
|
|
|
|
this.getListData()
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
developDailyPlan() {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
//制定日计划
|
|
|
|
|
|
url: `/pages/realName/workbench/dailyPlanManagement/develop?proId=${encodeURIComponent(
|
|
|
|
|
|
this.proId
|
|
|
|
|
|
)}&proName=${encodeURIComponent(this.proName)}&id=${encodeURIComponent(this.id)}`
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
editPlan() {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
//修改日计划
|
|
|
|
|
|
url: `/pages/realName/workbench/dailyPlanManagement/edit?dayId=${encodeURIComponent(
|
|
|
|
|
|
this.dayId
|
|
|
|
|
|
)}&id=${encodeURIComponent(this.id)}&proName=${encodeURIComponent(this.proName)}`
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
uploadPlan() {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
//完成情况上传
|
|
|
|
|
|
url: `/pages/realName/workbench/dailyPlanManagement/upload?dayId=${encodeURIComponent(
|
|
|
|
|
|
this.dayId
|
|
|
|
|
|
)}&id=${encodeURIComponent(this.id)}&proName=${encodeURIComponent(this.proName)}`
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
detailsPlan() {
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
//日计划详情
|
|
|
|
|
|
url: `/pages/realName/workbench/dailyPlanManagement/details?dayId=${encodeURIComponent(
|
|
|
|
|
|
this.dayId
|
|
|
|
|
|
)}&id=${encodeURIComponent(this.id)}&proName=${encodeURIComponent(this.proName)}`
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 返回
|
|
|
|
|
|
leftClick() {
|
|
|
|
|
|
console.log('返回')
|
|
|
|
|
|
uni.navigateBack({
|
|
|
|
|
|
delta: 1 // 返回
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
getListData() {
|
|
|
|
|
|
let param = {
|
|
|
|
|
|
proId: this.proId,
|
|
|
|
|
|
date: this.currentDate
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('param查询参数', param)
|
|
|
|
|
|
uni.request({
|
|
|
|
|
|
url: config.realAppUrl + '/AppPlan/selectDayPlan',
|
|
|
|
|
|
method: 'post',
|
|
|
|
|
|
data: JSON.stringify(param),
|
|
|
|
|
|
header: {
|
|
|
|
|
|
'content-type': 'application/json',
|
|
|
|
|
|
Authorization: uni.getStorageSync('realNameToken')
|
|
|
|
|
|
},
|
|
|
|
|
|
success: res => {
|
|
|
|
|
|
console.log('日计划管理', res)
|
|
|
|
|
|
if (res.data.code == 200) {
|
|
|
|
|
|
this.taskList = res.data.data
|
|
|
|
|
|
const firstProName = res.data.data.length > 0 ? res.data.data[0].proName : null
|
|
|
|
|
|
this.proName = firstProName
|
|
|
|
|
|
this.totalComplete = this.overallCompletionRate()
|
|
|
|
|
|
this.monthComplete = this.monthlyCompletionRate()
|
|
|
|
|
|
this.id = res.data.data[0].id
|
|
|
|
|
|
this.dayId = res.data.data[0].dayId
|
|
|
|
|
|
} else {
|
|
|
|
|
|
uni.$u.toast(res.data.msg)
|
|
|
|
|
|
this.taskList = ''
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: err => {
|
|
|
|
|
|
console.log(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
getStatusClass(task) {
|
|
|
|
|
|
var planCompleteNumDay = task.planCompleteNumDay
|
|
|
|
|
|
var completeNumDay = task.completeNumDay
|
|
|
|
|
|
var status = ''
|
|
|
|
|
|
if (planCompleteNumDay > completeNumDay) {
|
|
|
|
|
|
status = '进行中'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
status = '已结束'
|
|
|
|
|
|
}
|
|
|
|
|
|
switch (status) {
|
|
|
|
|
|
case '进行中':
|
|
|
|
|
|
return 'in-progress'
|
|
|
|
|
|
case '已结束':
|
|
|
|
|
|
return 'completed'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return 'not-started'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
getTaskStatusText(task) {
|
|
|
|
|
|
var planCompleteNumDay = task.planCompleteNumDay
|
|
|
|
|
|
var completeNumDay = task.completeNumDay
|
|
|
|
|
|
var status = ''
|
|
|
|
|
|
if (planCompleteNumDay > completeNumDay) {
|
|
|
|
|
|
status = '进行中'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
status = '已结束'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (status) {
|
|
|
|
|
|
case '未开始':
|
|
|
|
|
|
return '未开始'
|
|
|
|
|
|
case '进行中':
|
|
|
|
|
|
return '进行中'
|
|
|
|
|
|
case '已结束':
|
|
|
|
|
|
return '已结束'
|
|
|
|
|
|
default:
|
|
|
|
|
|
return '未开始'
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 作业计划完成率 = (累计完成作业量 / 总计划数量) * 100%
|
|
|
|
|
|
overallCompletionRate() {
|
|
|
|
|
|
let totalPlan = 0
|
|
|
|
|
|
let completedWork = 0
|
|
|
|
|
|
|
|
|
|
|
|
this.taskList.forEach(task => {
|
|
|
|
|
|
if (task.completeNumDay && task.planCompleteNumDay) {
|
|
|
|
|
|
totalPlan += parseFloat(task.planCompleteNumDay)
|
|
|
|
|
|
completedWork += parseFloat(task.completeNumDay)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return totalPlan > 0 ? ((completedWork / totalPlan) * 100).toFixed(2) : '0.00'
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 月完成计划完成率 = (月累计完成作业量 / 总计划数量) * 100%
|
|
|
|
|
|
monthlyCompletionRate() {
|
|
|
|
|
|
let totalPlan = 0
|
|
|
|
|
|
let completedWorkInMonth = 0
|
|
|
|
|
|
|
|
|
|
|
|
this.taskList.forEach(task => {
|
|
|
|
|
|
if (task.completeNumAllMonth && task.completeNumMonth) {
|
|
|
|
|
|
totalPlan += parseFloat(task.completeNumAllMonth)
|
|
|
|
|
|
completedWorkInMonth += parseFloat(task.completeNumMonth)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
return totalPlan > 0 ? ((completedWorkInMonth / totalPlan) * 100).toFixed(2) : '0.00'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2024-12-30 21:18:13 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
2024-12-20 19:28:44 +08:00
|
|
|
|
<style>
|
2025-02-10 15:00:46 +08:00
|
|
|
|
.container {
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.date-section {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.date-picker {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.calendar-icon {
|
|
|
|
|
|
width: 32rpx;
|
|
|
|
|
|
height: 32rpx;
|
|
|
|
|
|
margin-right: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress-section {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.reminder-text {
|
|
|
|
|
|
color: #ff4d4f;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.primary-btn {
|
|
|
|
|
|
background-color: #0099ff;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
padding: 0rpx 20rpx;
|
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.project-title {
|
|
|
|
|
|
font-size: 32rpx;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.completion-rate {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-item {
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
padding: 20rpx;
|
|
|
|
|
|
border-radius: 10rpx;
|
|
|
|
|
|
margin-bottom: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.task-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.timestamp {
|
|
|
|
|
|
color: #999;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.status {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
padding: 4rpx 20rpx;
|
|
|
|
|
|
border-radius: 4rpx;
|
|
|
|
|
|
font-size: 24rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.not-started {
|
|
|
|
|
|
background-color: #e6f7ff;
|
|
|
|
|
|
color: #1890ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.in-progress {
|
|
|
|
|
|
background-color: #fff7e6;
|
|
|
|
|
|
color: #fa8c16;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.completed {
|
|
|
|
|
|
background-color: #f6ffed;
|
|
|
|
|
|
color: #52c41a;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.metrics-container {
|
|
|
|
|
|
margin: 20rpx 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.metric-row {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-start;
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
margin-bottom: 10rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.metric-item {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
text-align: left;
|
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.actions {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
gap: 20rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn {
|
|
|
|
|
|
font-size: 26rpx;
|
|
|
|
|
|
padding: 0rpx 22rpx;
|
|
|
|
|
|
border: 1px solid #ddd;
|
|
|
|
|
|
border-radius: 20rpx;
|
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.action-btn.danger {
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
background-color: #ff4d4f;
|
|
|
|
|
|
border-color: #ff4d4f;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|