feat: 添加路由权限控制和高级经理数据动态加载
refactor(router): 实现基于用户等级的路由守卫 feat(secondTop): 动态加载高级经理列表数据 fix(SalesTimelineWithTaskList): 修正默认职业和教育显示 perf(CustomerType): 优化图表数据绑定和交互 chore: 更新API基础路径配置
This commit is contained in:
@@ -5,6 +5,7 @@ import SeniorManager from '@/views/senorManger/seniorManager.vue'
|
|||||||
import TopOne from '@/views/topOne/topone.vue'
|
import TopOne from '@/views/topOne/topone.vue'
|
||||||
import Login from '@/views/login/login.vue'
|
import Login from '@/views/login/login.vue'
|
||||||
import SecondTop from '@/views/secondTop/secondTop.vue'
|
import SecondTop from '@/views/secondTop/secondTop.vue'
|
||||||
|
import { useUserStore } from '@/stores/user.js'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
@@ -15,32 +16,38 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
component: Login
|
component: Login,
|
||||||
|
meta: { requiresAuth: false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/sale',
|
path: '/sale',
|
||||||
name: 'Sale',
|
name: 'Sale',
|
||||||
component: Sale
|
component: Sale,
|
||||||
|
meta: { requiresAuth: true, minLevel: 1 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/manager',
|
path: '/manager',
|
||||||
name: 'Manager',
|
name: 'Manager',
|
||||||
component: Manager
|
component: Manager,
|
||||||
|
meta: { requiresAuth: true, minLevel: 2 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/senior-manager',
|
path: '/senior-manager',
|
||||||
name: 'SeniorManager',
|
name: 'SeniorManager',
|
||||||
component: SeniorManager
|
component: SeniorManager,
|
||||||
|
meta: { requiresAuth: true, minLevel: 3 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/second-top',
|
path: '/second-top',
|
||||||
name: 'SecondTop',
|
name: 'SecondTop',
|
||||||
component: SecondTop
|
component: SecondTop,
|
||||||
|
meta: { requiresAuth: true, minLevel: 4 }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/top',
|
path: '/top',
|
||||||
name: 'Top',
|
name: 'Top',
|
||||||
component: TopOne
|
component: TopOne,
|
||||||
|
meta: { requiresAuth: true, minLevel: 5 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -49,4 +56,40 @@ const router = createRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 路由守卫
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 如果路由不需要认证,直接通过
|
||||||
|
if (!to.meta.requiresAuth) {
|
||||||
|
next()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已登录
|
||||||
|
if (!userStore.isLoggedIn || !userStore.userInfo) {
|
||||||
|
next('/login')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户等级权限
|
||||||
|
const userLevel = userStore.userInfo.user_level
|
||||||
|
const requiredLevel = to.meta.minLevel
|
||||||
|
|
||||||
|
// 用户等级必须大于等于所需等级(等级高的可以访问等级低的页面)
|
||||||
|
if (userLevel >= requiredLevel) {
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
// 权限不足,重定向到对应等级的默认页面
|
||||||
|
const defaultRoutes = {
|
||||||
|
1: '/sale',
|
||||||
|
2: '/manager',
|
||||||
|
3: '/senior-manager',
|
||||||
|
4: '/second-top',
|
||||||
|
5: '/top'
|
||||||
|
}
|
||||||
|
next(defaultRoutes[userLevel] || '/sale')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
@@ -5,7 +5,7 @@ import { useUserStore } from '@/stores/user'
|
|||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: 'http://192.168.15.51:8890' || '', // API基础路径,支持完整URL
|
baseURL: 'http://192.168.15.53:8890' || '', // API基础路径,支持完整URL
|
||||||
timeout: 100000, // 请求超时时间
|
timeout: 100000, // 请求超时时间
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json;charset=UTF-8'
|
'Content-Type': 'application/json;charset=UTF-8'
|
||||||
|
|||||||
@@ -56,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="item-footer">
|
<div class="item-footer">
|
||||||
<div class="profession-education">
|
<div class="profession-education">
|
||||||
<span class="profession">{{ item.profession || '公务员' }}</span>
|
<span class="profession">{{ item.profession||'未知' }}</span>
|
||||||
<span class="education">{{ item.education || '高中' }}</span>
|
<span class="education">{{ item.education||'未知' }}</span>
|
||||||
<span class="course-attendance">
|
<span class="course-attendance">
|
||||||
{{ getAttendedLessons(item.class_situation,item.class_num) }}
|
{{ getAttendedLessons(item.class_situation,item.class_num) }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<div class="chart-header">
|
<div class="chart-header">
|
||||||
<h3>客户类型占比</h3>
|
<h3>客户类型占比</h3>
|
||||||
<select v-model="customerTypeCategory" @change="updateChart" class="chart-select">
|
<select v-model="customerTypeCategory" @change="handleCategoryChange" class="chart-select">
|
||||||
<option value="age">年龄</option>
|
|
||||||
<option value="profession">职业</option>
|
<option value="profession">职业</option>
|
||||||
<option value="childGrade">孩子年级</option>
|
<option value="childGrade">孩子年级</option>
|
||||||
<option value="region">地域</option>
|
<option value="region">地域</option>
|
||||||
@@ -16,30 +15,88 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
import { ref, reactive, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
|
// 定义props
|
||||||
|
const props = defineProps({
|
||||||
|
customerData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 定义emit
|
||||||
|
const emit = defineEmits(['categoryChange'])
|
||||||
|
|
||||||
const customerTypeChartRef = ref(null);
|
const customerTypeChartRef = ref(null);
|
||||||
let customerTypeChart = null;
|
let customerTypeChart = null;
|
||||||
|
|
||||||
const customerTypeCategory = ref('age');
|
const customerTypeCategory = ref('profession');
|
||||||
const customerTypeData = reactive({
|
|
||||||
age: [{ value: 120, name: '18-25岁' }, { value: 200, name: '26-35岁' }, { value: 150, name: '36-45岁' }, { value: 80, name: '46-55岁' }, { value: 50, name: '55岁以上' }],
|
// 计算属性:将API数据转换为图表所需格式
|
||||||
profession: [{ value: 180, name: '企业管理者' }, { value: 120, name: '教师' }, { value: 100, name: '医生' }, { value: 90, name: '工程师' }, { value: 110, name: '其他' }],
|
const chartData = computed(() => {
|
||||||
childGrade: [{ value: 80, name: '幼儿园' }, { value: 150, name: '小学' }, { value: 180, name: '初中' }, { value: 120, name: '高中' }, { value: 70, name: '大学' }],
|
if (!props.customerData || !props.customerData.customer_type_distribution) {
|
||||||
region: [{ value: 200, name: '北京' }, { value: 150, name: '上海' }, { value: 120, name: '广州' }, { value: 100, name: '深圳' }, { value: 130, name: '其他' }]
|
return []
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return props.customerData.customer_type_distribution.map(item => ({
|
||||||
|
name: item.category,
|
||||||
|
value: parseFloat(item.ratio.replace('%', '')) || 0
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
if (!customerTypeChart) return;
|
if (!customerTypeChart) return;
|
||||||
const currentData = customerTypeData[customerTypeCategory.value];
|
|
||||||
|
// 使用真实数据
|
||||||
|
const currentData = chartData.value
|
||||||
|
|
||||||
|
if (!currentData || currentData.length === 0) {
|
||||||
|
// 如果没有数据,显示空状态
|
||||||
const option = {
|
const option = {
|
||||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
title: {
|
||||||
|
text: '暂无数据',
|
||||||
|
left: 'center',
|
||||||
|
top: 'middle',
|
||||||
|
textStyle: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
customerTypeChart.setOption(option, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: { type: 'shadow' },
|
||||||
|
formatter: function(params) {
|
||||||
|
return params[0].name + '<br/>' + params[0].seriesName + ': ' + params[0].value + '%'
|
||||||
|
}
|
||||||
|
},
|
||||||
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
|
||||||
xAxis: { type: 'category', data: currentData.map(item => item.name), axisTick: { alignWithLabel: true } },
|
xAxis: {
|
||||||
yAxis: { type: 'value' },
|
type: 'category',
|
||||||
|
data: currentData.map(item => item.name),
|
||||||
|
axisTick: { alignWithLabel: true },
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
rotate: currentData.length > 5 ? 45 : 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}%'
|
||||||
|
}
|
||||||
|
},
|
||||||
series: [{
|
series: [{
|
||||||
name: '客户数量', type: 'bar', barWidth: '60%',
|
name: '占比',
|
||||||
|
type: 'bar',
|
||||||
|
barWidth: '60%',
|
||||||
data: currentData.map(item => item.value),
|
data: currentData.map(item => item.value),
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: (params) => ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'][params.dataIndex % 5]
|
color: (params) => ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'][params.dataIndex % 5]
|
||||||
@@ -57,9 +114,30 @@ const initChart = () => {
|
|||||||
|
|
||||||
const resizeChart = () => customerTypeChart?.resize();
|
const resizeChart = () => customerTypeChart?.resize();
|
||||||
|
|
||||||
|
// 处理类别选择变化
|
||||||
|
const handleCategoryChange = () => {
|
||||||
|
// 映射选择值到API参数
|
||||||
|
const distributionTypeMap = {
|
||||||
|
'profession': 'occupation',
|
||||||
|
'childGrade': 'child_education',
|
||||||
|
'region': 'territory'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通知父组件选择变化
|
||||||
|
emit('categoryChange', distributionTypeMap[customerTypeCategory.value])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听数据变化
|
||||||
|
watch(chartData, () => {
|
||||||
|
updateChart()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initChart();
|
customerTypeChart = echarts.init(customerTypeChartRef.value);
|
||||||
|
updateChart();
|
||||||
window.addEventListener('resize', resizeChart);
|
window.addEventListener('resize', resizeChart);
|
||||||
|
// 初始化时触发一次事件,加载默认数据
|
||||||
|
handleCategoryChange()
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ const props = defineProps({
|
|||||||
groups: {
|
groups: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
seniorManagerData: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,13 +69,21 @@ const emit = defineEmits(['select-group', 'manager-change'])
|
|||||||
const selectedManager = ref('all')
|
const selectedManager = ref('all')
|
||||||
|
|
||||||
// 高级经理列表
|
// 高级经理列表
|
||||||
const seniorManagers = ref([
|
const seniorManagers = computed(() => {
|
||||||
|
if (props.seniorManagerData && props.seniorManagerData.center_advanced_managers) {
|
||||||
|
return props.seniorManagerData.center_advanced_managers.map((name, index) => ({
|
||||||
|
id: `manager_${index}`,
|
||||||
|
name: name
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return [
|
||||||
{ id: 'manager1', name: '张经理' },
|
{ id: 'manager1', name: '张经理' },
|
||||||
{ id: 'manager2', name: '李经理' },
|
{ id: 'manager2', name: '李经理' },
|
||||||
{ id: 'manager3', name: '王经理' },
|
{ id: 'manager3', name: '王经理' },
|
||||||
{ id: 'manager4', name: '刘经理' },
|
{ id: 'manager4', name: '刘经理' },
|
||||||
{ id: 'manager5', name: '陈经理' }
|
{ id: 'manager5', name: '陈经理' }
|
||||||
])
|
]
|
||||||
|
})
|
||||||
|
|
||||||
// 处理经理选择变化
|
// 处理经理选择变化
|
||||||
const handleManagerChange = () => {
|
const handleManagerChange = () => {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="BB-section">
|
<div class="BB-section">
|
||||||
<!--客户类型占比-->
|
<!--客户类型占比-->
|
||||||
<CustomerType />
|
<CustomerType :customer-data="customerTypeDistribution" @category-change="handleCustomerTypeChange" />
|
||||||
<!-- 优秀录音 -->
|
<!-- 优秀录音 -->
|
||||||
<GoodMusic />
|
<GoodMusic />
|
||||||
<!-- 客户问题排行 -->
|
<!-- 客户问题排行 -->
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<!-- Right Section - Group Comparison -->
|
<!-- Right Section - Group Comparison -->
|
||||||
<div class="right-section">
|
<div class="right-section">
|
||||||
<GroupComparison :groups="groups" @select-group="selectGroup" />
|
<GroupComparison :groups="groups" :senior-manager-data="seniorManagerList" @select-group="selectGroup" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -285,18 +285,25 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 客户类型
|
// 客户类型
|
||||||
async function CenterCustomerType() {
|
async function CenterCustomerType(distributionType = 'occupation') {
|
||||||
const params = getRequestParams()
|
const params = getRequestParams()
|
||||||
const hasParams = params.user_name
|
const hasParams = params.user_name
|
||||||
|
// 添加distribution_type参数
|
||||||
|
const requestParams = hasParams ? { ...params, distribution_type: distributionType } : { distribution_type: distributionType }
|
||||||
try {
|
try {
|
||||||
const res = await getCustomerTypeDistribution(hasParams ? params : undefined)
|
const res = await getCustomerTypeDistribution(requestParams)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
customerTypeDistribution.value = res.data
|
customerTypeDistribution.value = res.data
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取中心整体概览失败:', error)
|
console.error('获取客户类型分布失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理客户类型选择变化
|
||||||
|
const handleCustomerTypeChange = (distributionType) => {
|
||||||
|
CenterCustomerType(distributionType)
|
||||||
|
}
|
||||||
// 客户迫切解决的问题
|
// 客户迫切解决的问题
|
||||||
async function CenterUrgentNeedToAddress() {
|
async function CenterUrgentNeedToAddress() {
|
||||||
const params = getRequestParams()
|
const params = getRequestParams()
|
||||||
@@ -305,46 +312,29 @@
|
|||||||
const res = await getUrgentNeedToAddress(hasParams ? params : undefined)
|
const res = await getUrgentNeedToAddress(hasParams ? params : undefined)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
urgentNeedToAddress.value = res.data
|
urgentNeedToAddress.value = res.data
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取中心整体概览失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 综合排名---高级经理列表
|
||||||
|
const seniorManagerList = ref([])
|
||||||
|
async function CenterSeniorManagerList() {
|
||||||
|
const params = getRequestParams()
|
||||||
|
const hasParams = params.user_name
|
||||||
|
try {
|
||||||
|
const res = await getCenterAdvancedManagerList(hasParams ? params : undefined)
|
||||||
|
if (res.code === 200) {
|
||||||
|
seniorManagerList.value = res.data
|
||||||
/**
|
/**
|
||||||
* data
|
* "data": {
|
||||||
:
|
"user_name": "刘瑞",
|
||||||
{user_name: "刘瑞", user_level: 4,…}
|
"user_level": 4,
|
||||||
center_urgent_issue_counts
|
"center_advanced_managers": [
|
||||||
:
|
"陈盼良"
|
||||||
{成绩提升: 1, 少玩手机: 1, 回归学校: 0, 心理健康: 0}
|
]
|
||||||
回归学校
|
}
|
||||||
:
|
|
||||||
0
|
|
||||||
少玩手机
|
|
||||||
:
|
|
||||||
1
|
|
||||||
心理健康
|
|
||||||
:
|
|
||||||
0
|
|
||||||
成绩提升
|
|
||||||
:
|
|
||||||
1
|
|
||||||
center_urgent_issue_ratio
|
|
||||||
:
|
|
||||||
{成绩提升: "50.00%", 少玩手机: "50.00%", 回归学校: "0.00%", 心理健康: "0.00%"}
|
|
||||||
回归学校
|
|
||||||
:
|
|
||||||
"0.00%"
|
|
||||||
少玩手机
|
|
||||||
:
|
|
||||||
"50.00%"
|
|
||||||
心理健康
|
|
||||||
:
|
|
||||||
"0.00%"
|
|
||||||
成绩提升
|
|
||||||
:
|
|
||||||
"50.00%"
|
|
||||||
user_level
|
|
||||||
:
|
|
||||||
4
|
|
||||||
user_name
|
|
||||||
:
|
|
||||||
"刘瑞"
|
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -396,6 +386,7 @@ user_name
|
|||||||
await CenterDepositConversionRate()
|
await CenterDepositConversionRate()
|
||||||
await CenterCustomerType()
|
await CenterCustomerType()
|
||||||
await CenterUrgentNeedToAddress()
|
await CenterUrgentNeedToAddress()
|
||||||
|
await CenterSeniorManagerList()
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user