feat(topOne): 添加各中心营期阶段组件并优化API调用
- 新增PeriodStage组件展示各中心营期阶段信息 - 移除任务管理相关代码,替换为营期阶段展示 - 修改API端点路径,优化优秀录音文件接口调用 - 调整TeamAlerts组件样式,减小最大高度
This commit is contained in:
@@ -63,7 +63,7 @@ const aggregatedAlerts = computed(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
max-height: 300px;
|
||||
max-height: 270px;
|
||||
overflow-y: auto;
|
||||
|
||||
// 自定义滚动条样式
|
||||
|
||||
287
my-vue-app/src/views/topOne/components/PeriodStage.vue
Normal file
287
my-vue-app/src/views/topOne/components/PeriodStage.vue
Normal file
@@ -0,0 +1,287 @@
|
||||
<template>
|
||||
<div class="period-stage-container">
|
||||
<div class="period-stage-header">
|
||||
<h3>各中心营期阶段</h3>
|
||||
<div class="stage-legend">
|
||||
<span class="legend-item">
|
||||
<span class="legend-dot active"></span>
|
||||
进行中
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-dot completed"></span>
|
||||
已完成
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<span class="legend-dot pending"></span>
|
||||
未开始
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="period-stage-content">
|
||||
<div class="center-list">
|
||||
<div
|
||||
v-for="(center, index) in periodStageData"
|
||||
:key="index"
|
||||
class="center-item"
|
||||
>
|
||||
<div class="center-info">
|
||||
<div class="center-name">{{ center.center_name }}</div>
|
||||
<div class="center-leader">负责人:{{ center.center_leader_name }}</div>
|
||||
</div>
|
||||
<div class="stage-badge" :class="getStageClass(center.period_stage)">
|
||||
{{ center.period_stage }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { getPeriodStage } from '@/api/top'
|
||||
|
||||
// 营期阶段数据
|
||||
const periodStageData = ref([])
|
||||
|
||||
// 获取营期阶段数据
|
||||
const fetchPeriodStageData = async () => {
|
||||
try {
|
||||
console.log('开始获取营期阶段数据...')
|
||||
const res = await getPeriodStage()
|
||||
console.log('API响应:', res)
|
||||
if (res && res.data) {
|
||||
periodStageData.value = res.data.period_stage
|
||||
console.log('数据设置成功:', periodStageData.value)
|
||||
} else {
|
||||
console.log('API响应无数据,使用默认数据')
|
||||
setDefaultData()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取营期阶段数据失败:', error)
|
||||
setDefaultData()
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认数据
|
||||
const setDefaultData = () => {
|
||||
periodStageData.value = [
|
||||
{
|
||||
center_name: '一中心',
|
||||
center_leader_name: '张三丰',
|
||||
period_stage: '接数据'
|
||||
},
|
||||
{
|
||||
center_name: '二中心',
|
||||
center_leader_name: '朱一航',
|
||||
period_stage: '未知阶段'
|
||||
},
|
||||
{
|
||||
center_name: '三中心',
|
||||
center_leader_name: '程琦',
|
||||
period_stage: '课1'
|
||||
}
|
||||
]
|
||||
console.log('已设置默认数据:', periodStageData.value)
|
||||
}
|
||||
|
||||
// 获取阶段状态样式类
|
||||
const getStageClass = (stage) => {
|
||||
const stageMap = {
|
||||
'接数据': 'stage-data',
|
||||
'课1': 'stage-course1',
|
||||
'课2': 'stage-course2',
|
||||
'课3': 'stage-course3',
|
||||
'课4': 'stage-course4',
|
||||
'休息': 'stage-rest',
|
||||
'未知阶段': 'stage-unknown'
|
||||
}
|
||||
return stageMap[stage] || 'stage-default'
|
||||
}
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchPeriodStageData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.period-stage-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.period-stage-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.period-stage-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.stage-legend {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.legend-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.legend-dot.active {
|
||||
background-color: #1890ff;
|
||||
}
|
||||
|
||||
.legend-dot.completed {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
.legend-dot.pending {
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.period-stage-content {
|
||||
margin-top: 16px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.center-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.center-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 6px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.center-item:hover {
|
||||
background: #f1f5f9;
|
||||
border-color: #cbd5e1;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.center-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.center-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.center-leader {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.stage-badge {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.stage-data {
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.stage-course1 {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.stage-course2 {
|
||||
background: #fef3c7;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
.stage-course3 {
|
||||
background: #fed7d7;
|
||||
color: #c53030;
|
||||
}
|
||||
|
||||
.stage-course4 {
|
||||
background: #e9d5ff;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.stage-rest {
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.stage-unknown {
|
||||
background: #fecaca;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.stage-default {
|
||||
background: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.period-stage-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.period-stage-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stage-legend {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stage-item {
|
||||
padding-left: 32px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stage-timeline::before {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.stage-dot {
|
||||
left: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,19 +7,14 @@
|
||||
<UserDropdown />
|
||||
</div>
|
||||
|
||||
<!-- 第一行:核心业绩指标、销售实时进度、下发任务 -->
|
||||
<!-- 第一行:核心业绩指标、销售实时进度 -->
|
||||
<div class="dashboard-row row-1">
|
||||
<!-- 核心业绩指标 -->
|
||||
<kpi-metrics :kpi-data="totalDeals" :format-number="formatNumber" />
|
||||
<!-- 销售实时进度 -->
|
||||
<sales-progress :sales-data="realTimeProgress" />
|
||||
<!-- 下发任务 -->
|
||||
<task-list
|
||||
:tasks="tasks"
|
||||
:format-date="formatDate"
|
||||
:get-task-status-text="getTaskStatusText"
|
||||
@show-task-modal="showTaskModal = true"
|
||||
/>
|
||||
<!-- 各中心营期阶段 -->
|
||||
<period-stage />
|
||||
</div>
|
||||
<!-- 第二行 -->
|
||||
<div class="dashboard-row row-3">
|
||||
@@ -69,59 +64,7 @@
|
||||
@filter-change="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
<!-- 新建任务模态框 -->
|
||||
<div
|
||||
v-if="showTaskModal"
|
||||
class="modal-overlay"
|
||||
@click="showTaskModal = false"
|
||||
>
|
||||
<div class="modal-content" @click.stop>
|
||||
<div class="modal-header">
|
||||
<h3>新建任务</h3>
|
||||
<button class="close-btn" @click="showTaskModal = false">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label>任务标题</label>
|
||||
<input
|
||||
v-model="newTask.title"
|
||||
type="text"
|
||||
placeholder="请输入任务标题"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>分配给</label>
|
||||
<select v-model="newTask.assignee">
|
||||
<option value="">请选择员工</option>
|
||||
<option
|
||||
v-for="employee in assigneeOptions"
|
||||
:key="employee.wechat_id"
|
||||
:value="employee.wechat_id"
|
||||
>
|
||||
{{ employee.name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>截止日期</label>
|
||||
<input v-model="newTask.deadline" type="date" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>任务描述</label>
|
||||
<textarea
|
||||
v-model="newTask.description"
|
||||
placeholder="请输入任务描述"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="cancel-btn" @click="showTaskModal = false">
|
||||
取消
|
||||
</button>
|
||||
<button class="confirm-btn" @click="createTask">创建任务</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -144,7 +87,6 @@ import axios from "axios";
|
||||
import UserDropdown from "@/components/UserDropdown.vue";
|
||||
import KpiMetrics from "./components/KpiMetrics.vue";
|
||||
import SalesProgress from "./components/SalesProgress.vue";
|
||||
import TaskList from "./components/TaskList.vue";
|
||||
import FunnelChart from "./components/FunnelChart.vue";
|
||||
import CustomerProfile from "./components/CustomerProfile.vue";
|
||||
import CustomerType from "./components/CustomerType.vue";
|
||||
@@ -157,8 +99,10 @@ import QualityCalls from "./components/QualityCalls.vue";
|
||||
import DataDetail from "./components/DataDetail.vue";
|
||||
import CampManagement from "./components/CampManagement.vue";
|
||||
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
||||
import PeriodStage from "./components/PeriodStage.vue";
|
||||
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks } from "@/api/top";
|
||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,getExcellentRecordFile } from "@/api/top";
|
||||
import { useUserStore } from "@/stores/user.js";
|
||||
|
||||
const rankingPeriod = ref("month");
|
||||
const rankingData = ref([
|
||||
@@ -173,131 +117,8 @@ const sortField = ref("dealRate");
|
||||
const sortOrder = ref("desc");
|
||||
const selectedPerson = ref(null);
|
||||
|
||||
const tasks = ref([]);
|
||||
const userStore = useUserStore();
|
||||
|
||||
const employees = ref([
|
||||
{ id: 1, name: "张三" }
|
||||
]);
|
||||
|
||||
const showTaskModal = ref(false);
|
||||
const newTask = reactive({
|
||||
title: "",
|
||||
assignee: "",
|
||||
deadline: "",
|
||||
description: "",
|
||||
});
|
||||
// 获取任务列表
|
||||
const getTaskList = async () => {
|
||||
try {
|
||||
const res = await axios.post('http://192.168.15.60: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() {
|
||||
try {
|
||||
console.log('开始获取下属人员列表...');
|
||||
const res = await axios.get('http://192.168.15.60:8890/api/v1/level_five/overview/get_subordinates',{
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||||
}
|
||||
});
|
||||
assigneeOptions.value = res.data.data;
|
||||
|
||||
console.log('assigneeOptions设置后:', assigneeOptions.value);
|
||||
} catch (error) {
|
||||
console.error('获取下属人员列表失败:', error);
|
||||
}
|
||||
}
|
||||
// 计算属性
|
||||
const filteredTableData = computed(() => {
|
||||
let filtered = tableData.value;
|
||||
@@ -418,14 +239,7 @@ const selectPerson = (person) => {
|
||||
selectedPerson.value = person;
|
||||
};
|
||||
|
||||
const getTaskStatusText = (status) => {
|
||||
const statusMap = {
|
||||
pending: "待处理",
|
||||
"in-progress": "进行中",
|
||||
completed: "已完成",
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
|
||||
const playCall = (callId) => {
|
||||
console.log("播放通话录音:", callId);
|
||||
@@ -435,51 +249,7 @@ const downloadCall = (callId) => {
|
||||
console.log("下载通话录音:", callId);
|
||||
};
|
||||
|
||||
const createTask = async () => {
|
||||
if (!newTask.title || !newTask.assignee || !newTask.deadline) {
|
||||
alert("请填写完整信息");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构造API请求参数
|
||||
const params = {
|
||||
task_title: newTask.title,
|
||||
task_assignee: [newTask.assignee], // 转换为数组格式
|
||||
expiration_date: newTask.deadline.replace(/-/g, ''), // 移除日期中的横线
|
||||
task_content: newTask.description || newTask.title
|
||||
};
|
||||
|
||||
// 调用API
|
||||
const response = await assignTasks(params);
|
||||
console.log('任务创建成功:', response);
|
||||
|
||||
// 创建本地任务对象用于显示
|
||||
const task = {
|
||||
id: Date.now(),
|
||||
title: newTask.title,
|
||||
assignee: newTask.assignee,
|
||||
deadline: newTask.deadline,
|
||||
status: "pending",
|
||||
};
|
||||
|
||||
tasks.value.unshift(task);
|
||||
|
||||
// 重置表单
|
||||
Object.assign(newTask, {
|
||||
title: "",
|
||||
assignee: "",
|
||||
deadline: "",
|
||||
description: "",
|
||||
});
|
||||
|
||||
showTaskModal.value = false;
|
||||
alert('任务创建成功!');
|
||||
} catch (error) {
|
||||
console.error('创建任务失败:', error);
|
||||
alert('创建任务失败,请重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 核心数据
|
||||
const totalDeals = ref({});
|
||||
@@ -505,6 +275,9 @@ async function getTotalDeals() {
|
||||
}
|
||||
// 实时进度
|
||||
const realTimeProgress = ref({});
|
||||
|
||||
|
||||
|
||||
async function getRealTimeProgress() {
|
||||
try {
|
||||
const res = await getCompanyRealTimeProgress()
|
||||
@@ -706,7 +479,6 @@ async function getDetailData(params) {
|
||||
console.error("获取详细数据表格失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 处理筛选器变化
|
||||
@@ -714,19 +486,31 @@ const handleFilterChange = (filterParams) => {
|
||||
console.log('筛选器变化:', filterParams)
|
||||
getDetailData(filterParams)
|
||||
}
|
||||
|
||||
// 优秀录音
|
||||
const excellentRecord = ref({});
|
||||
async function CenterExcellentRecord() {
|
||||
const params={
|
||||
user_level:userStore.userInfo.user_level.toString(),
|
||||
user_name:userStore.userInfo.username
|
||||
}
|
||||
try {
|
||||
const res = await getExcellentRecordFile(params)
|
||||
excellentRecord.value = res.data
|
||||
} catch (error) {
|
||||
console.error("获取优秀录音失败:", error);
|
||||
}
|
||||
}
|
||||
onMounted(async() => {
|
||||
// 页面初始化逻辑
|
||||
await getRealTimeProgress()
|
||||
await getTotalDeals()
|
||||
await getTaskList()
|
||||
await getConversionComparison('month')
|
||||
await getCompanySalesRank('red')
|
||||
await getCustomerTypeRatio('child_education')
|
||||
await getCustomerUrgency()
|
||||
await CusotomGetLevelTree()
|
||||
await getDetailData()
|
||||
await name() // 获取下属人员列表
|
||||
await CenterExcellentRecord()
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user