feat: 实现任务管理功能并优化界面显示

- 添加任务列表获取和状态更新API调用
- 修改任务列表组件显示格式和状态标签
- 优化日期格式化处理逻辑
- 调整任务列表样式和交互效果
- 注释掉部分不需要的API调用
This commit is contained in:
2025-08-21 12:57:50 +08:00
parent 8780a94f82
commit 3b1c1c03f3
5 changed files with 396 additions and 375 deletions

View File

@@ -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)
} }

View File

@@ -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
if (!aRelated && bRelated) return 1
return 0
}) })
// 获取任务状态样式类
const getTaskStatusClass = (state) => {
switch (state) {
case '待处理':
return 'status-pending'
case '正在处理':
return 'status-processing'
case '已完成':
return 'status-completed'
default:
return 'status-default'
}
} }
return filtered.filter(action => !action.completed) // 格式化到期日期
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]
// 已完成数量 updateTaskState(task.task_id, newState)
const completedCount = computed(() => { }
return actions.value.filter(action => action.completed).length
// 修改任务状态 http://192.168.15.56:8890/api/v1/level_four/overview/update_task_state
const updateTaskState = async (taskId, state) => {
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: {
'Authorization': 'Bearer ' + localStorage.getItem('token')
}
}) })
if (res.data.code === 200) {
console.log('任务状态更新成功')
// 刷新任务列表
// 格式化截止日期 await getTaskList()
const formatDueDate = (dueDate) => { }
const date = new Date(dueDate) } catch (error) {
const today = new Date() console.error('更新任务状态失败:', error)
const diffTime = date - today
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
if (diffDays < 0) {
return `逾期${Math.abs(diffDays)}`
} else if (diffDays === 0) {
return '今天到期'
} 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 = () => {
} }
} }
// 任务列表样式
.task-list {
.task-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e5e7eb;
&:hover {
background-color: #f9fafb;
}
.task-info {
display: flex;
flex-direction: column;
gap: 8px;
flex: 1;
margin-right: 16px;
.task-row-1, .task-row-2 {
display: flex;
gap: 12px;
align-items: center;
}
.task-row-1 {
.task-title {
font-weight: 600;
color: #111827;
font-size: 14px;
margin-right: 8px;
}
.task-content {
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) { @media (max-width: 768px) {
.action-items { .task-list {
padding: 1rem; .task-row {
.actions-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
}
.actions-summary {
grid-template-columns: repeat(4, 1fr);
}
.action-item {
.action-header {
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;
} }
} }
} }

View File

@@ -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') // 初始化加载全部高级经理数据

View File

@@ -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">

View File

@@ -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')