feat: 实现任务管理功能并优化界面显示
- 添加任务列表获取和状态更新API调用 - 修改任务列表组件显示格式和状态标签 - 优化日期格式化处理逻辑 - 调整任务列表样式和交互效果 - 注释掉部分不需要的API调用
This commit is contained in:
@@ -67,7 +67,7 @@ export const getDetailedDataTable = (params) => {
|
|||||||
|
|
||||||
// 下发任务 /api/v1/level_five/overview/assign_tasks
|
// 下发任务 /api/v1/level_five/overview/assign_tasks
|
||||||
export const assignTasks = (params) => {
|
export const assignTasks = (params) => {
|
||||||
return https.post('/api/v1/level_five/overview/assign_tasks', params)
|
return https.post('http://192.168.15.56:8890/api/v1/level_five/overview/assign_tasks', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,54 @@
|
|||||||
<h2>待处理事项</h2>
|
<h2>待处理事项</h2>
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<select v-model="filterPriority" class="priority-filter">
|
<select v-model="filterPriority" class="priority-filter">
|
||||||
<option value="all">全部优先级</option>
|
<option value="all">全部状态</option>
|
||||||
<option value="urgent">待处理</option>
|
<option value="待处理">待处理</option>
|
||||||
<option value="high">正在处理</option>
|
<option value="正在处理">正在处理</option>
|
||||||
<option value="medium">已完成</option>
|
<option value="已完成">已完成</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="actions-list">
|
||||||
|
<div v-if="filteredActions.length === 0" class="no-tasks">
|
||||||
|
<p>暂无任务</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="task-list">
|
||||||
|
<div
|
||||||
|
v-for="task in filteredActions"
|
||||||
|
:key="task.task_id"
|
||||||
|
class="task-row"
|
||||||
|
>
|
||||||
|
<div class="task-info">
|
||||||
|
<div class="task-row-1">
|
||||||
|
<span class="task-title">{{ task.task_title }}</span>
|
||||||
|
<span class="task-content">{{ task.task_content }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="task-row-2">
|
||||||
|
<span class="task-date">到期时间: {{ formatDueDate(task.expiration_date) }}</span>
|
||||||
|
<span class="task-created">创建时间: {{ task.created_at }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="task-actions">
|
||||||
|
<span class="status-tag" :class="getTaskStatusClass(task.state)">
|
||||||
|
{{ task.state }}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="status-btn"
|
||||||
|
@click="changeTaskStatus(task)"
|
||||||
|
>
|
||||||
|
处理任务
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
selectedGroup: {
|
selectedGroup: {
|
||||||
@@ -40,131 +76,94 @@ const newAction = ref({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 待处理事项数据
|
// 待处理事项数据
|
||||||
const actions = ref([
|
const actions = ref([])
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: '突破组转化率改进计划',
|
|
||||||
description: '针对突破组转化率连续下降问题,制定具体改进措施并跟踪执行',
|
|
||||||
priority: 'urgent',
|
|
||||||
dueDate: '2024-01-15',
|
|
||||||
relatedGroup: '突破组',
|
|
||||||
assignee: '王主管',
|
|
||||||
progress: 30,
|
|
||||||
tags: ['业绩改进', '紧急'],
|
|
||||||
completed: false,
|
|
||||||
createdAt: '2024-01-10'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: '新星组人员补充',
|
|
||||||
description: '新星组当前人员不足,需要招聘2名新销售并安排培训',
|
|
||||||
priority: 'high',
|
|
||||||
dueDate: '2024-01-20',
|
|
||||||
relatedGroup: '新星组',
|
|
||||||
assignee: '赵主管',
|
|
||||||
progress: 60,
|
|
||||||
tags: ['人员管理', '招聘'],
|
|
||||||
completed: false,
|
|
||||||
createdAt: '2024-01-08'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: '月度业绩分析报告',
|
|
||||||
description: '整理各组月度业绩数据,分析趋势并提出下月目标建议',
|
|
||||||
priority: 'medium',
|
|
||||||
dueDate: '2024-01-25',
|
|
||||||
relatedGroup: '',
|
|
||||||
assignee: '中心组长',
|
|
||||||
progress: 80,
|
|
||||||
tags: ['数据分析', '报告'],
|
|
||||||
completed: false,
|
|
||||||
createdAt: '2024-01-05'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: '销售技能培训安排',
|
|
||||||
description: '组织各组销售人员参加客户沟通技巧培训',
|
|
||||||
priority: 'medium',
|
|
||||||
dueDate: '2024-01-30',
|
|
||||||
relatedGroup: '',
|
|
||||||
assignee: '培训部',
|
|
||||||
progress: 20,
|
|
||||||
tags: ['培训', '技能提升'],
|
|
||||||
completed: false,
|
|
||||||
createdAt: '2024-01-12'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: '客户满意度调研',
|
|
||||||
description: '对已成交客户进行满意度调研,收集改进建议',
|
|
||||||
priority: 'low',
|
|
||||||
dueDate: '2024-02-05',
|
|
||||||
relatedGroup: '',
|
|
||||||
assignee: '客服部',
|
|
||||||
progress: 0,
|
|
||||||
tags: ['客户服务', '调研'],
|
|
||||||
completed: false,
|
|
||||||
createdAt: '2024-01-14'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: '精英组激励方案制定',
|
|
||||||
description: '为表现优秀的精英组制定专项激励方案',
|
|
||||||
priority: 'medium',
|
|
||||||
dueDate: '2024-01-18',
|
|
||||||
relatedGroup: '精英组',
|
|
||||||
assignee: '人事部',
|
|
||||||
progress: 100,
|
|
||||||
tags: ['激励', '团队管理'],
|
|
||||||
completed: true,
|
|
||||||
createdAt: '2024-01-01'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
// 筛选后的事项
|
// 筛选后的事项
|
||||||
const filteredActions = computed(() => {
|
const filteredActions = computed(() => {
|
||||||
|
console.log('计算filteredActions - actions.value:', actions.value)
|
||||||
|
console.log('计算filteredActions - filterPriority.value:', filterPriority.value)
|
||||||
|
|
||||||
let filtered = actions.value
|
let filtered = actions.value
|
||||||
|
|
||||||
if (filterPriority.value !== 'all') {
|
if (filterPriority.value !== 'all') {
|
||||||
filtered = filtered.filter(action => action.priority === filterPriority.value)
|
filtered = filtered.filter(task => task.state === filterPriority.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果选中了特定组别,优先显示相关事项
|
console.log('计算filteredActions - filtered结果:', filtered)
|
||||||
if (props.selectedGroup) {
|
return filtered
|
||||||
filtered = filtered.sort((a, b) => {
|
})
|
||||||
const aRelated = a.relatedGroup === props.selectedGroup.name
|
|
||||||
const bRelated = b.relatedGroup === props.selectedGroup.name
|
// 获取任务状态样式类
|
||||||
if (aRelated && !bRelated) return -1
|
const getTaskStatusClass = (state) => {
|
||||||
if (!aRelated && bRelated) return 1
|
switch (state) {
|
||||||
return 0
|
case '待处理':
|
||||||
|
return 'status-pending'
|
||||||
|
case '正在处理':
|
||||||
|
return 'status-processing'
|
||||||
|
case '已完成':
|
||||||
|
return 'status-completed'
|
||||||
|
default:
|
||||||
|
return 'status-default'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化到期日期
|
||||||
|
const formatDueDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '无'
|
||||||
|
// 如果是YYYYMMDD格式,转换为YYYY-MM-DD
|
||||||
|
if (dateStr.length === 8) {
|
||||||
|
const year = dateStr.substring(0, 4)
|
||||||
|
const month = dateStr.substring(4, 6)
|
||||||
|
const day = dateStr.substring(6, 8)
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
|
}
|
||||||
|
return dateStr
|
||||||
|
}
|
||||||
|
// 获取任务列表
|
||||||
|
const getTaskList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post('http://192.168.15.56:8890/api/v1/level_five/overview/view_tasks', {}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
console.log(888888,res)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
actions.value = res.data.data.tasks || res.data.data
|
||||||
|
console.log(777777,actions.value)
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取任务列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 修改任务状态按钮点击事件
|
||||||
|
const changeTaskStatus = (task) => {
|
||||||
|
const statusOptions = ['待处理', '正在处理', '已完成']
|
||||||
|
const currentIndex = statusOptions.indexOf(task.state)
|
||||||
|
const nextIndex = (currentIndex + 1) % statusOptions.length
|
||||||
|
const newState = statusOptions[nextIndex]
|
||||||
|
|
||||||
return filtered.filter(action => !action.completed)
|
updateTaskState(task.task_id, newState)
|
||||||
})
|
}
|
||||||
|
|
||||||
// 已完成数量
|
// 修改任务状态 http://192.168.15.56:8890/api/v1/level_four/overview/update_task_state
|
||||||
const completedCount = computed(() => {
|
const updateTaskState = async (taskId, state) => {
|
||||||
return actions.value.filter(action => action.completed).length
|
try {
|
||||||
})
|
const res = await axios.put('http://192.168.15.56:8890/api/v1/level_four/overview/update_task_state', {
|
||||||
|
task_ids: [taskId],
|
||||||
|
new_state: state
|
||||||
|
}, {
|
||||||
// 格式化截止日期
|
headers: {
|
||||||
const formatDueDate = (dueDate) => {
|
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||||
const date = new Date(dueDate)
|
}
|
||||||
const today = new Date()
|
})
|
||||||
const diffTime = date - today
|
if (res.data.code === 200) {
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
console.log('任务状态更新成功')
|
||||||
|
// 刷新任务列表
|
||||||
if (diffDays < 0) {
|
await getTaskList()
|
||||||
return `逾期${Math.abs(diffDays)}天`
|
}
|
||||||
} else if (diffDays === 0) {
|
} catch (error) {
|
||||||
return '今天到期'
|
console.error('更新任务状态失败:', error)
|
||||||
} else if (diffDays === 1) {
|
|
||||||
return '明天到期'
|
|
||||||
} else {
|
|
||||||
return `${diffDays}天后到期`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,6 +206,9 @@ const addAction = () => {
|
|||||||
|
|
||||||
showAddForm.value = false
|
showAddForm.value = false
|
||||||
}
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
await getTaskList()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -269,57 +271,22 @@ const addAction = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统计概览
|
|
||||||
.actions-summary {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
.summary-item {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
&.urgent {
|
|
||||||
background: #fef2f2;
|
|
||||||
border: 1px solid #fecaca;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.high {
|
|
||||||
background: #fef3c7;
|
|
||||||
border: 1px solid #fed7aa;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.medium {
|
|
||||||
background: #eff6ff;
|
|
||||||
border: 1px solid #bfdbfe;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.completed {
|
|
||||||
background: #f0fdf4;
|
|
||||||
border: 1px solid #bbf7d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-count {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #1f2937;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary-label {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 事项列表
|
// 事项列表
|
||||||
.actions-list {
|
.actions-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.no-tasks {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.action-item {
|
.action-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@@ -334,43 +301,23 @@ const addAction = () => {
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border-color: #d0d0d0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.urgent {
|
&.status-pending {
|
||||||
border-left: 4px solid #ef4444;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.high {
|
|
||||||
border-left: 4px solid #f59e0b;
|
border-left: 4px solid #f59e0b;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.medium {
|
&.status-processing {
|
||||||
border-left: 4px solid #3b82f6;
|
border-left: 4px solid #3b82f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.low {
|
&.status-completed {
|
||||||
border-left: 4px solid #10b981;
|
border-left: 4px solid #10b981;
|
||||||
}
|
opacity: 0.8;
|
||||||
|
|
||||||
&.completed {
|
|
||||||
opacity: 0.6;
|
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.overdue {
|
|
||||||
background: #fef2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-checkbox {
|
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
.checkbox {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-content {
|
.action-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
@@ -385,53 +332,54 @@ const addAction = () => {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
flex: 1;
|
||||||
&.completed {
|
margin-right: 12px;
|
||||||
text-decoration: line-through;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-meta {
|
.action-meta {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
.priority-badge {
|
.status-badge {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 4px;
|
border-radius: 12px;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&.urgent {
|
&.status-pending {
|
||||||
background: #fee2e2;
|
background: #fff3cd;
|
||||||
color: #991b1b;
|
color: #856404;
|
||||||
|
border: 1px solid #ffeaa7;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.high {
|
&.status-processing {
|
||||||
background: #fef3c7;
|
background: #d1ecf1;
|
||||||
color: #92400e;
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.medium {
|
&.status-completed {
|
||||||
background: #dbeafe;
|
background: #d4edda;
|
||||||
color: #1e40af;
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.low {
|
&.status-default {
|
||||||
background: #dcfce7;
|
background: #f8f9fa;
|
||||||
color: #166534;
|
color: #6c757d;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.due-date {
|
.due-date {
|
||||||
font-size: 0.8rem;
|
font-size: 0.75rem;
|
||||||
color: #6b7280;
|
color: #666;
|
||||||
|
background: #f8f9fa;
|
||||||
&.overdue {
|
padding: 2px 6px;
|
||||||
color: #ef4444;
|
border-radius: 4px;
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,108 +392,31 @@ const addAction = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-details {
|
.action-details {
|
||||||
display: grid;
|
margin-top: 12px;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
padding-top: 12px;
|
||||||
gap: 0.75rem;
|
border-top: 1px solid #f0f0f0;
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
.detail-item {
|
.detail-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
gap: 8px;
|
||||||
gap: 0.5rem;
|
font-size: 0.8rem;
|
||||||
|
color: #888;
|
||||||
|
|
||||||
.detail-label {
|
.detail-label {
|
||||||
font-size: 0.8rem;
|
font-weight: 500;
|
||||||
color: #9ca3af;
|
|
||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-value {
|
.detail-value {
|
||||||
font-size: 0.8rem;
|
color: #666;
|
||||||
color: #374151;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-mini {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
.progress-bar-mini {
|
|
||||||
width: 60px;
|
|
||||||
height: 4px;
|
|
||||||
background: #f3f4f6;
|
|
||||||
border-radius: 2px;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
.progress-fill-mini {
|
|
||||||
height: 100%;
|
|
||||||
background: #3b82f6;
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-text-mini {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.action-tags {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
.tag {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
background: #f3f4f6;
|
|
||||||
color: #6b7280;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
.btn-edit, .btn-delete {
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-edit {
|
|
||||||
background: #f3f4f6;
|
|
||||||
color: #374151;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #e5e7eb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-delete {
|
|
||||||
background: #fee2e2;
|
|
||||||
color: #991b1b;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: #fecaca;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 空状态
|
// 空状态
|
||||||
.empty-state {
|
.empty-state {
|
||||||
@@ -698,42 +569,116 @@ const addAction = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动端适配
|
// 任务列表样式
|
||||||
@media (max-width: 768px) {
|
.task-list {
|
||||||
.action-items {
|
.task-row {
|
||||||
padding: 1rem;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
|
||||||
.actions-header {
|
&:hover {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-info {
|
||||||
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 8px;
|
||||||
align-items: flex-start;
|
flex: 1;
|
||||||
|
margin-right: 16px;
|
||||||
|
|
||||||
|
.task-row-1, .task-row-2 {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-summary {
|
.task-row-1 {
|
||||||
grid-template-columns: repeat(4, 1fr);
|
.task-title {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-item {
|
.task-content {
|
||||||
.action-header {
|
color: #6b7280;
|
||||||
|
font-size: 13px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-row-2 {
|
||||||
|
.task-date, .task-created {
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.status-tag {
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&.pending {
|
||||||
|
background-color: #fef3c7;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.in-progress {
|
||||||
|
background-color: #dbeafe;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.completed {
|
||||||
|
background-color: #d1fae5;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-btn {
|
||||||
|
padding: 6px 12px;
|
||||||
|
background-color: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-tasks {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移动端适配
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.task-list {
|
||||||
|
.task-row {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
gap: 0.5rem;
|
gap: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
.action-details {
|
.task-actions {
|
||||||
grid-template-columns: 1fr;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-footer {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
.form-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -716,16 +716,16 @@ const conversionRateVsAverage = ref({})
|
|||||||
await CenterCampPeriodAdmin()
|
await CenterCampPeriodAdmin()
|
||||||
}
|
}
|
||||||
// CenterCampPeriodAdmin中已经调用了recalculateStageDates,这里不需要重复调用
|
// CenterCampPeriodAdmin中已经调用了recalculateStageDates,这里不需要重复调用
|
||||||
await CenterOverallCenterPerformance()
|
// await CenterOverallCenterPerformance()
|
||||||
await CenterTotalGroupCount()
|
// await CenterTotalGroupCount()
|
||||||
await CenterConversionRate()
|
// await CenterConversionRate()
|
||||||
await CenterTotalCallCount()
|
// await CenterTotalCallCount()
|
||||||
await CenterNewCustomer()
|
// await CenterNewCustomer()
|
||||||
await CenterDepositConversionRate()
|
// await CenterDepositConversionRate()
|
||||||
await CenterCustomerType()
|
// await CenterCustomerType()
|
||||||
await CenterUrgentNeedToAddress()
|
// await CenterUrgentNeedToAddress()
|
||||||
await CenterConversionRateVsAverage()
|
// await CenterConversionRateVsAverage()
|
||||||
await CenterSeniorManagerList()
|
// await CenterSeniorManagerList()
|
||||||
|
|
||||||
|
|
||||||
// await CenterGroupList('all') // 初始化加载全部高级经理数据
|
// await CenterGroupList('all') // 初始化加载全部高级经理数据
|
||||||
|
|||||||
@@ -7,12 +7,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-list compact">
|
<div class="task-list compact">
|
||||||
<div v-for="task in tasks.slice(0, 3)" :key="task.id" class="task-item">
|
<div v-for="task in tasks.slice(0, 10)" :key="task.id" class="task-item">
|
||||||
<div class="task-content">
|
<div class="task-content">
|
||||||
<div class="task-title">{{ task.title }}</div>
|
<div class="task-title">{{ task.title }}</div>
|
||||||
<div class="task-meta" style="display: flex; gap: 15px;">
|
<div class="task-meta" style="display: flex; gap: 15px;">
|
||||||
<span class="assignee">分配给: {{ task.assignee }}</span>
|
<span class="deadline">创建时间: {{ formatDate(task.created_at) }}</span>
|
||||||
<span class="deadline">截止: {{ formatDate(task.deadline) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="task-status" :class="task.status">
|
<div class="task-status" :class="task.status">
|
||||||
|
|||||||
@@ -158,8 +158,7 @@ import DataDetail from "./components/DataDetail.vue";
|
|||||||
import CampManagement from "./components/CampManagement.vue";
|
import CampManagement from "./components/CampManagement.vue";
|
||||||
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
||||||
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks
|
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks } from "@/api/top";
|
||||||
} from "@/api/top";
|
|
||||||
|
|
||||||
const rankingPeriod = ref("month");
|
const rankingPeriod = ref("month");
|
||||||
const rankingData = ref([
|
const rankingData = ref([
|
||||||
@@ -187,6 +186,101 @@ const newTask = reactive({
|
|||||||
deadline: "",
|
deadline: "",
|
||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
|
// 获取任务列表
|
||||||
|
const getTaskList = async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post('http://192.168.15.56:8890/api/v1/level_five/overview/view_tasks', {}, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log(888888,res)
|
||||||
|
if (res.data.code === 200) {
|
||||||
|
const apiTasks = res.data.data.tasks || res.data.data
|
||||||
|
// 将API数据格式转换为TaskList组件期望的格式
|
||||||
|
tasks.value = apiTasks.map(task => ({
|
||||||
|
id: task.task_id,
|
||||||
|
title: task.task_title,
|
||||||
|
assignee: task.assignee || '未分配',
|
||||||
|
deadline: task.expiration_date,
|
||||||
|
status: task.state === '待处理' ? 'pending' : task.state === '正在处理' ? 'in-progress' : 'completed',
|
||||||
|
description: task.task_content,
|
||||||
|
created_at: task.created_at
|
||||||
|
}))
|
||||||
|
console.log(777777,tasks.value)
|
||||||
|
/**
|
||||||
|
* tasks
|
||||||
|
:
|
||||||
|
[,…]
|
||||||
|
0
|
||||||
|
:
|
||||||
|
{task_id: "1755748690560728_22d55cc618784537973481228a15956a", task_title: "55", task_content: "222",…}
|
||||||
|
created_at
|
||||||
|
:
|
||||||
|
"2025-08-21 11:58:10"
|
||||||
|
expiration_date
|
||||||
|
:
|
||||||
|
"20250808"
|
||||||
|
state
|
||||||
|
:
|
||||||
|
"待处理"
|
||||||
|
task_content
|
||||||
|
:
|
||||||
|
"222"
|
||||||
|
task_id
|
||||||
|
:
|
||||||
|
"1755748690560728_22d55cc618784537973481228a15956a"
|
||||||
|
task_title
|
||||||
|
:
|
||||||
|
"55"
|
||||||
|
1
|
||||||
|
:
|
||||||
|
{task_id: "1755745331126891_650206e5b6d345699de3e3e406a2600e", task_title: "测试任务",…}
|
||||||
|
created_at
|
||||||
|
:
|
||||||
|
"2025-08-21 11:02:11"
|
||||||
|
expiration_date
|
||||||
|
:
|
||||||
|
"121221"
|
||||||
|
state
|
||||||
|
:
|
||||||
|
"待处理"
|
||||||
|
task_content
|
||||||
|
:
|
||||||
|
"测试任务"
|
||||||
|
task_id
|
||||||
|
:
|
||||||
|
"1755745331126891_650206e5b6d345699de3e3e406a2600e"
|
||||||
|
task_title
|
||||||
|
:
|
||||||
|
"测试任务"
|
||||||
|
2
|
||||||
|
:
|
||||||
|
{task_id: "1755745330094989_528dd87dc13a4a5bb33c9c272fb1a482", task_title: "测试任务",…}
|
||||||
|
created_at
|
||||||
|
:
|
||||||
|
"2025-08-21 11:02:10"
|
||||||
|
expiration_date
|
||||||
|
:
|
||||||
|
"121221"
|
||||||
|
state
|
||||||
|
:
|
||||||
|
"已完成"
|
||||||
|
task_content
|
||||||
|
:
|
||||||
|
"测试任务"
|
||||||
|
task_id
|
||||||
|
:
|
||||||
|
"1755745330094989_528dd87dc13a4a5bb33c9c272fb1a482"
|
||||||
|
task_title
|
||||||
|
:
|
||||||
|
"测试任务"
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取任务列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
// 下拉框人员
|
// 下拉框人员
|
||||||
const assigneeOptions = ref([]);
|
const assigneeOptions = ref([]);
|
||||||
async function name() {
|
async function name() {
|
||||||
@@ -203,34 +297,6 @@ async function name() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取下属人员列表失败:', error);
|
console.error('获取下属人员列表失败:', error);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* "data": [
|
|
||||||
{
|
|
||||||
"name": "程琦",
|
|
||||||
"wechat_id": "1688856301330784"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "潘加俊",
|
|
||||||
"wechat_id": "1688855836721980"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "伍晶晶",
|
|
||||||
"wechat_id": "1688854476805987"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "张三丰",
|
|
||||||
"wechat_id": "1212345648513"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "朱一航",
|
|
||||||
"wechat_id": "1212345648513"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "王卓琳",
|
|
||||||
"wechat_id": "1212345648513"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const filteredTableData = computed(() => {
|
const filteredTableData = computed(() => {
|
||||||
@@ -329,7 +395,17 @@ const formatTime = (timestamp) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
return new Date(dateString).toLocaleDateString("zh-CN");
|
if (!dateString) return '';
|
||||||
|
// 处理 "2025-08-21 11:58:10" 格式的时间字符串
|
||||||
|
try {
|
||||||
|
const date = new Date(dateString.replace(' ', 'T'));
|
||||||
|
if (isNaN(date.getTime())) {
|
||||||
|
return dateString; // 如果解析失败,返回原字符串
|
||||||
|
}
|
||||||
|
return date.toLocaleDateString("zh-CN");
|
||||||
|
} catch (error) {
|
||||||
|
return dateString;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDuration = (minutes) => {
|
const formatDuration = (minutes) => {
|
||||||
@@ -643,6 +719,7 @@ onMounted(async() => {
|
|||||||
// 页面初始化逻辑
|
// 页面初始化逻辑
|
||||||
await getRealTimeProgress()
|
await getRealTimeProgress()
|
||||||
await getTotalDeals()
|
await getTotalDeals()
|
||||||
|
await getTaskList()
|
||||||
await getConversionComparison('month')
|
await getConversionComparison('month')
|
||||||
await getCompanySalesRank('red')
|
await getCompanySalesRank('red')
|
||||||
await getCustomerTypeRatio('child_education')
|
await getCustomerTypeRatio('child_education')
|
||||||
|
|||||||
Reference in New Issue
Block a user