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
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>
<div class="header-controls">
<select v-model="filterPriority" class="priority-filter">
<option value="all">全部优先级</option>
<option value="urgent">待处理</option>
<option value="high">正在处理</option>
<option value="medium">已完成</option>
<option value="all">全部状态</option>
<option value="待处理">待处理</option>
<option value="正在处理">正在处理</option>
<option value="已完成">已完成</option>
</select>
</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>
</template>
<script setup>
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
const props = defineProps({
selectedGroup: {
@@ -40,131 +76,94 @@ const newAction = 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 actions = ref([])
// 筛选后的事项
const filteredActions = computed(() => {
console.log('计算filteredActions - actions.value:', actions.value)
console.log('计算filteredActions - filterPriority.value:', filterPriority.value)
let filtered = actions.value
if (filterPriority.value !== 'all') {
filtered = filtered.filter(action => action.priority === filterPriority.value)
filtered = filtered.filter(task => task.state === filterPriority.value)
}
// 如果选中了特定组别,优先显示相关事项
if (props.selectedGroup) {
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
console.log('计算filteredActions - filtered结果:', filtered)
return filtered
})
// 获取任务状态样式类
const getTaskStatusClass = (state) => {
switch (state) {
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)
}
// 已完成数量
const completedCount = computed(() => {
return actions.value.filter(action => action.completed).length
})
// 格式化截止日期
const formatDueDate = (dueDate) => {
const date = new Date(dueDate)
const today = new Date()
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}天后到期`
// 修改任务状态 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()
}
} catch (error) {
console.error('更新任务状态失败:', error)
}
}
@@ -207,6 +206,9 @@ const addAction = () => {
showAddForm.value = false
}
onMounted(async () => {
await getTaskList()
})
</script>
<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 {
flex: 1;
overflow-y: auto;
.no-tasks {
text-align: center;
padding: 40px 20px;
color: #999;
p {
margin: 0;
font-size: 0.9rem;
}
}
.action-item {
display: flex;
padding: 1rem;
@@ -334,43 +301,23 @@ const addAction = () => {
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-color: #d0d0d0;
}
&.urgent {
border-left: 4px solid #ef4444;
}
&.high {
&.status-pending {
border-left: 4px solid #f59e0b;
}
&.medium {
&.status-processing {
border-left: 4px solid #3b82f6;
}
&.low {
&.status-completed {
border-left: 4px solid #10b981;
}
&.completed {
opacity: 0.6;
opacity: 0.8;
background: #f9fafb;
}
&.overdue {
background: #fef2f2;
}
.action-checkbox {
margin-right: 1rem;
.checkbox {
width: 18px;
height: 18px;
cursor: pointer;
}
}
.action-content {
flex: 1;
@@ -385,53 +332,54 @@ const addAction = () => {
font-weight: 600;
color: #1f2937;
margin: 0;
&.completed {
text-decoration: line-through;
color: #9ca3af;
}
flex: 1;
margin-right: 12px;
}
.action-meta {
display: flex;
gap: 0.5rem;
align-items: center;
flex-shrink: 0;
.priority-badge {
.status-badge {
padding: 0.25rem 0.5rem;
border-radius: 4px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
&.urgent {
background: #fee2e2;
color: #991b1b;
&.status-pending {
background: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
&.high {
background: #fef3c7;
color: #92400e;
&.status-processing {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
&.medium {
background: #dbeafe;
color: #1e40af;
&.status-completed {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
&.low {
background: #dcfce7;
color: #166534;
&.status-default {
background: #f8f9fa;
color: #6c757d;
border: 1px solid #dee2e6;
}
}
.due-date {
font-size: 0.8rem;
color: #6b7280;
&.overdue {
color: #ef4444;
font-weight: 600;
}
font-size: 0.75rem;
color: #666;
background: #f8f9fa;
padding: 2px 6px;
border-radius: 4px;
}
}
}
@@ -444,102 +392,23 @@ const addAction = () => {
}
.action-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 0.75rem;
margin-bottom: 1rem;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
.detail-item {
display: flex;
align-items: center;
gap: 0.5rem;
gap: 8px;
font-size: 0.8rem;
color: #888;
.detail-label {
font-size: 0.8rem;
color: #9ca3af;
font-weight: 500;
min-width: 60px;
}
.detail-value {
font-size: 0.8rem;
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;
}
color: #666;
}
}
}
@@ -547,6 +416,8 @@ const addAction = () => {
}
}
// 空状态
.empty-state {
display: flex;
@@ -698,42 +569,116 @@ const addAction = () => {
}
}
// 移动端适配
@media (max-width: 768px) {
.action-items {
padding: 1rem;
// 任务列表样式
.task-list {
.task-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #e5e7eb;
.actions-header {
flex-direction: column;
gap: 1rem;
align-items: flex-start;
&:hover {
background-color: #f9fafb;
}
.actions-summary {
grid-template-columns: repeat(4, 1fr);
}
.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;
}
}
}
.action-item {
.action-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
.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;
}
}
.action-details {
grid-template-columns: 1fr;
}
.action-footer {
flex-direction: column;
align-items: flex-start;
gap: 0.75rem;
.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;
}
}
}
.modal-content {
.form-row {
grid-template-columns: 1fr;
}
.no-tasks {
text-align: center;
padding: 40px 20px;
color: #6b7280;
}
// 移动端适配
@media (max-width: 768px) {
.task-list {
.task-row {
flex-direction: column;
align-items: flex-start;
gap: 12px;
.task-actions {
align-self: flex-end;
}
}
}
}

View File

@@ -716,16 +716,16 @@ const conversionRateVsAverage = ref({})
await CenterCampPeriodAdmin()
}
// CenterCampPeriodAdmin中已经调用了recalculateStageDates这里不需要重复调用
await CenterOverallCenterPerformance()
await CenterTotalGroupCount()
await CenterConversionRate()
await CenterTotalCallCount()
await CenterNewCustomer()
await CenterDepositConversionRate()
await CenterCustomerType()
await CenterUrgentNeedToAddress()
await CenterConversionRateVsAverage()
await CenterSeniorManagerList()
// await CenterOverallCenterPerformance()
// await CenterTotalGroupCount()
// await CenterConversionRate()
// await CenterTotalCallCount()
// await CenterNewCustomer()
// await CenterDepositConversionRate()
// await CenterCustomerType()
// await CenterUrgentNeedToAddress()
// await CenterConversionRateVsAverage()
// await CenterSeniorManagerList()
// await CenterGroupList('all') // 初始化加载全部高级经理数据

View File

@@ -7,12 +7,11 @@
</button>
</div>
<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-title">{{ task.title }}</div>
<div class="task-meta" style="display: flex; gap: 15px;">
<span class="assignee">分配给: {{ task.assignee }}</span>
<span class="deadline">截止: {{ formatDate(task.deadline) }}</span>
<span class="deadline">创建时间: {{ formatDate(task.created_at) }}</span>
</div>
</div>
<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 DetailedDataTable from "./components/DetailedDataTable.vue";
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks
} from "@/api/top";
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks } from "@/api/top";
const rankingPeriod = ref("month");
const rankingData = ref([
@@ -187,6 +186,101 @@ const newTask = reactive({
deadline: "",
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([]);
async function name() {
@@ -203,34 +297,6 @@ async function name() {
} catch (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(() => {
@@ -329,7 +395,17 @@ const formatTime = (timestamp) => {
};
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) => {
@@ -643,6 +719,7 @@ onMounted(async() => {
// 页面初始化逻辑
await getRealTimeProgress()
await getTotalDeals()
await getTaskList()
await getConversionComparison('month')
await getCompanySalesRank('red')
await getCustomerTypeRatio('child_education')