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 Login from '@/views/login/login.vue'
|
||||
import SecondTop from '@/views/secondTop/secondTop.vue'
|
||||
import { useUserStore } from '@/stores/user.js'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -15,32 +16,38 @@ const routes = [
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
component: Login,
|
||||
meta: { requiresAuth: false }
|
||||
},
|
||||
{
|
||||
path: '/sale',
|
||||
name: 'Sale',
|
||||
component: Sale
|
||||
component: Sale,
|
||||
meta: { requiresAuth: true, minLevel: 1 }
|
||||
},
|
||||
{
|
||||
path: '/manager',
|
||||
name: 'Manager',
|
||||
component: Manager
|
||||
component: Manager,
|
||||
meta: { requiresAuth: true, minLevel: 2 }
|
||||
},
|
||||
{
|
||||
path: '/senior-manager',
|
||||
name: 'SeniorManager',
|
||||
component: SeniorManager
|
||||
component: SeniorManager,
|
||||
meta: { requiresAuth: true, minLevel: 3 }
|
||||
},
|
||||
{
|
||||
{
|
||||
path: '/second-top',
|
||||
name: 'SecondTop',
|
||||
component: SecondTop
|
||||
component: SecondTop,
|
||||
meta: { requiresAuth: true, minLevel: 4 }
|
||||
},
|
||||
{
|
||||
{
|
||||
path: '/top',
|
||||
name: 'Top',
|
||||
component: TopOne
|
||||
component: TopOne,
|
||||
meta: { requiresAuth: true, minLevel: 5 }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -49,4 +56,40 @@ const router = createRouter({
|
||||
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
|
||||
@@ -5,7 +5,7 @@ import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: 'http://192.168.15.51:8890' || '', // API基础路径,支持完整URL
|
||||
baseURL: 'http://192.168.15.53:8890' || '', // API基础路径,支持完整URL
|
||||
timeout: 100000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
</div>
|
||||
<div class="item-footer">
|
||||
<div class="profession-education">
|
||||
<span class="profession">{{ item.profession || '公务员' }}</span>
|
||||
<span class="education">{{ item.education || '高中' }}</span>
|
||||
<span class="profession">{{ item.profession||'未知' }}</span>
|
||||
<span class="education">{{ item.education||'未知' }}</span>
|
||||
<span class="course-attendance">
|
||||
{{ getAttendedLessons(item.class_situation,item.class_num) }}
|
||||
</span>
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3>客户类型占比</h3>
|
||||
<select v-model="customerTypeCategory" @change="updateChart" class="chart-select">
|
||||
<option value="age">年龄</option>
|
||||
<select v-model="customerTypeCategory" @change="handleCategoryChange" class="chart-select">
|
||||
<option value="profession">职业</option>
|
||||
<option value="childGrade">孩子年级</option>
|
||||
<option value="region">地域</option>
|
||||
@@ -16,30 +15,88 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
customerData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emit
|
||||
const emit = defineEmits(['categoryChange'])
|
||||
|
||||
const customerTypeChartRef = ref(null);
|
||||
let customerTypeChart = null;
|
||||
|
||||
const customerTypeCategory = ref('age');
|
||||
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岁以上' }],
|
||||
profession: [{ value: 180, name: '企业管理者' }, { value: 120, name: '教师' }, { value: 100, name: '医生' }, { value: 90, name: '工程师' }, { value: 110, name: '其他' }],
|
||||
childGrade: [{ value: 80, name: '幼儿园' }, { value: 150, name: '小学' }, { value: 180, name: '初中' }, { value: 120, name: '高中' }, { value: 70, name: '大学' }],
|
||||
region: [{ value: 200, name: '北京' }, { value: 150, name: '上海' }, { value: 120, name: '广州' }, { value: 100, name: '深圳' }, { value: 130, name: '其他' }]
|
||||
});
|
||||
const customerTypeCategory = ref('profession');
|
||||
|
||||
// 计算属性:将API数据转换为图表所需格式
|
||||
const chartData = computed(() => {
|
||||
if (!props.customerData || !props.customerData.customer_type_distribution) {
|
||||
return []
|
||||
}
|
||||
|
||||
return props.customerData.customer_type_distribution.map(item => ({
|
||||
name: item.category,
|
||||
value: parseFloat(item.ratio.replace('%', '')) || 0
|
||||
}))
|
||||
})
|
||||
|
||||
const updateChart = () => {
|
||||
if (!customerTypeChart) return;
|
||||
const currentData = customerTypeData[customerTypeCategory.value];
|
||||
|
||||
// 使用真实数据
|
||||
const currentData = chartData.value
|
||||
|
||||
if (!currentData || currentData.length === 0) {
|
||||
// 如果没有数据,显示空状态
|
||||
const option = {
|
||||
title: {
|
||||
text: '暂无数据',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
customerTypeChart.setOption(option, true)
|
||||
return
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||
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 },
|
||||
xAxis: { type: 'category', data: currentData.map(item => item.name), axisTick: { alignWithLabel: true } },
|
||||
yAxis: { type: 'value' },
|
||||
xAxis: {
|
||||
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: [{
|
||||
name: '客户数量', type: 'bar', barWidth: '60%',
|
||||
name: '占比',
|
||||
type: 'bar',
|
||||
barWidth: '60%',
|
||||
data: currentData.map(item => item.value),
|
||||
itemStyle: {
|
||||
color: (params) => ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de'][params.dataIndex % 5]
|
||||
@@ -57,9 +114,30 @@ const initChart = () => {
|
||||
|
||||
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(() => {
|
||||
initChart();
|
||||
customerTypeChart = echarts.init(customerTypeChartRef.value);
|
||||
updateChart();
|
||||
window.addEventListener('resize', resizeChart);
|
||||
// 初始化时触发一次事件,加载默认数据
|
||||
handleCategoryChange()
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
@@ -56,6 +56,10 @@ const props = defineProps({
|
||||
groups: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
seniorManagerData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -65,13 +69,21 @@ const emit = defineEmits(['select-group', 'manager-change'])
|
||||
const selectedManager = ref('all')
|
||||
|
||||
// 高级经理列表
|
||||
const seniorManagers = ref([
|
||||
{ id: 'manager1', name: '张经理' },
|
||||
{ id: 'manager2', name: '李经理' },
|
||||
{ id: 'manager3', name: '王经理' },
|
||||
{ id: 'manager4', name: '刘经理' },
|
||||
{ id: 'manager5', name: '陈经理' }
|
||||
])
|
||||
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: 'manager2', name: '李经理' },
|
||||
{ id: 'manager3', name: '王经理' },
|
||||
{ id: 'manager4', name: '刘经理' },
|
||||
{ id: 'manager5', name: '陈经理' }
|
||||
]
|
||||
})
|
||||
|
||||
// 处理经理选择变化
|
||||
const handleManagerChange = () => {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<div class="BB-section">
|
||||
<!--客户类型占比-->
|
||||
<CustomerType />
|
||||
<CustomerType :customer-data="customerTypeDistribution" @category-change="handleCustomerTypeChange" />
|
||||
<!-- 优秀录音 -->
|
||||
<GoodMusic />
|
||||
<!-- 客户问题排行 -->
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<!-- Right Section - Group Comparison -->
|
||||
<div class="right-section">
|
||||
<GroupComparison :groups="groups" @select-group="selectGroup" />
|
||||
<GroupComparison :groups="groups" :senior-manager-data="seniorManagerList" @select-group="selectGroup" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -285,18 +285,25 @@
|
||||
}
|
||||
}
|
||||
// 客户类型
|
||||
async function CenterCustomerType() {
|
||||
async function CenterCustomerType(distributionType = 'occupation') {
|
||||
const params = getRequestParams()
|
||||
const hasParams = params.user_name
|
||||
// 添加distribution_type参数
|
||||
const requestParams = hasParams ? { ...params, distribution_type: distributionType } : { distribution_type: distributionType }
|
||||
try {
|
||||
const res = await getCustomerTypeDistribution(hasParams ? params : undefined)
|
||||
const res = await getCustomerTypeDistribution(requestParams)
|
||||
if (res.code === 200) {
|
||||
customerTypeDistribution.value = res.data
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取中心整体概览失败:', error)
|
||||
console.error('获取客户类型分布失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理客户类型选择变化
|
||||
const handleCustomerTypeChange = (distributionType) => {
|
||||
CenterCustomerType(distributionType)
|
||||
}
|
||||
// 客户迫切解决的问题
|
||||
async function CenterUrgentNeedToAddress() {
|
||||
const params = getRequestParams()
|
||||
@@ -305,46 +312,29 @@
|
||||
const res = await getUrgentNeedToAddress(hasParams ? params : undefined)
|
||||
if (res.code === 200) {
|
||||
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
|
||||
:
|
||||
{user_name: "刘瑞", user_level: 4,…}
|
||||
center_urgent_issue_counts
|
||||
:
|
||||
{成绩提升: 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
|
||||
:
|
||||
"刘瑞"
|
||||
* "data": {
|
||||
"user_name": "刘瑞",
|
||||
"user_level": 4,
|
||||
"center_advanced_managers": [
|
||||
"陈盼良"
|
||||
]
|
||||
}
|
||||
*/
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -396,6 +386,7 @@ user_name
|
||||
await CenterDepositConversionRate()
|
||||
await CenterCustomerType()
|
||||
await CenterUrgentNeedToAddress()
|
||||
await CenterSeniorManagerList()
|
||||
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user