【招生用户端】 新增# 工作台页面

main
kaeery 2025-03-03 21:06:32 +08:00
parent bb64589dfd
commit a8cc9b437b
9 changed files with 398 additions and 87 deletions

View File

@ -52,4 +52,6 @@
--el-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, 0.12);
--el-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, 0.08), 0px 12px 32px rgba(0, 0, 0, 0.12), 0px 8px 16px -8px rgba(0, 0, 0, 0.16);
--color-purple: #800080;
--color-primary: #0e66fb;
--color-gray: #d5d5d5;
}

View File

@ -1,10 +1,10 @@
<template>
<div class="flex flex-col gap-[16px] bg-white p-[20px]">
<div class="flex-row-between-center">
<span>{{ title }}</span>
<div class="flex flex-col gap-[16px] bg-white h-full">
<div class="flex-row-between-center px-[20px] pt-[20px]">
<span class="text-[20px] font-bold">{{ title }}</span>
<slot name="right" />
</div>
<div>
<div class="flex-1 px-[20px] pb-[20px]">
<slot />
</div>
</div>

View File

@ -0,0 +1,51 @@
<template>
<div class="flex-1 w-full">
<card title="线索转化情况统计">
<v-charts style="height: 350px" :option="option" :autoresize="true" />
</card>
</div>
</template>
<script setup lang="ts">
import card from './card.vue'
import vCharts from 'vue-echarts'
const data = [
{ value: 1048, name: '有意向' },
{ value: 735, name: '待领取' },
{ value: 580, name: '转化中' },
{ value: 484, name: '已添加' },
{ value: 300, name: '异常待处理' },
{ value: 300, name: '已成交' },
{ value: 300, name: '已战败' }
]
const option = ref({
color: ['#73DDFF', '#73ACFF', '#FDD56A', '#FDB36A', '#FD866A', '#9E87FF', '#58D5FF'],
tooltip: {
trigger: 'item'
},
legend: {
orient: 'vertical',
top: 'center',
right: 0
},
series: [
{
name: '',
type: 'pie',
radius: ['25%', '55%'],
center: ['45%', '50%'],
avoidLabelOverlap: false,
label: {
show: true,
position: 'outside',
formatter: params => {
return `${params.name}${params.percent}%`
}
},
data
}
]
})
</script>
<style scoped></style>

View File

@ -0,0 +1,86 @@
<template>
<div class="flex-1 w-full chart-card">
<card title="线索转客户统计">
<v-charts style="height: 350px" :autoresize="true" :option="option" />
</card>
</div>
</template>
<script setup lang="ts">
import card from './card.vue'
import vCharts from 'vue-echarts'
const data = [
{ name: '湛江团队', clueNumber: 52, client: 52, rate: 100 },
{ name: '广州团队', clueNumber: 8, client: 6, rate: 15 }
]
function createBarSeries(data, name, field) {
return {
name,
type: 'bar',
data: data.map(item => item[field])
}
}
function createLineSeries(data, name) {
return {
name,
type: 'line',
tooltip: {
valueFormatter: function (value) {
return (value as number) + '%'
}
},
data: data.map(item => item.client),
yAxisIndex: 1
}
}
const xAxisData = () => data.map(item => item.name)
const series = [
createBarSeries(data, '线索数', 'clueNumber'),
createBarSeries(data, '线索转客户数', 'client'),
createLineSeries(data, '线索转客户率')
]
const option = ref({
color: ['#0E66FB', '#FAC858', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {},
legend: {},
xAxis: [
{
type: 'category',
data: xAxisData()
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
{
type: 'value',
axisLabel: {
formatter: '{value} %'
}
}
],
series
})
</script>
<style scoped lang="scss">
@media (max-width: 1000px) {
.chart-card {
flex: 1 0 100%;
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<div class="flex-1 w-full chart-card">
<card title="成交客户统计">
<v-charts style="height: 350px" :autoresize="true" :option="option" />
</card>
</div>
</template>
<script setup lang="ts">
import card from './card.vue'
import vCharts from 'vue-echarts'
const data = [
{ name: '湛江团队', clueNumber: 52, client: 52, rate: 100 },
{ name: '广州团队', clueNumber: 8, client: 6, rate: 15 }
]
function createBarSeries(data, name, field) {
return {
name,
type: 'bar',
data: data.map(item => item[field])
}
}
const xAxisData = () => data.map(item => item.name)
const series = [createBarSeries(data, '客户数', 'clueNumber'), createBarSeries(data, '成交客户数', 'client')]
const option = ref({
color: ['#0E66FB', '#96B2D9'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {},
legend: {},
xAxis: [
{
type: 'category',
data: xAxisData()
}
],
yAxis: [
{
type: 'value',
axisLabel: {
formatter: '{value}'
}
}
],
series
})
</script>
<style scoped lang="scss">
@media (max-width: 1000px) {
.chart-card {
flex: 1 0 100%;
}
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div class="flex-1 w-full">
<card title="成交客户增长趋势">
<v-charts style="height: 350px" :option="option" :autoresize="true" />
</card>
</div>
</template>
<script setup lang="ts">
import vCharts from 'vue-echarts'
import card from './card.vue'
const data = [
{ label: '2025-02-24', value: 320 },
{ label: '2025-02-25', value: 132 },
{ label: '2025-02-26', value: 201 },
{ label: '2025-02-27', value: 334 },
{ label: '2025-02-28', value: 190 },
{ label: '2025-03-01', value: 130 },
{ label: '2025-03-02', value: 220 }
]
const xAxisData = data.map(item => item.label)
const seriesData = data.map(item => item.value)
const option = ref({
color: ['#37A2FF'],
tooltip: {
trigger: 'axis'
},
legend: {},
toolbox: {},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: xAxisData,
axisLabel: {
interval: 0
}
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '成交客户数量',
type: 'line',
smooth: true,
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#9E87FFb3'
},
{
offset: 1,
color: '#9E87FF03'
}
]
}
},
data: seriesData
}
]
})
</script>
<style scoped></style>

View File

@ -1,10 +1,33 @@
<template>
<card title="数据简报">
<div>333</div>
<div class="data-overview">
<div
class="flex-1 bg-[#F7F8FA] flex flex-col gap-[12px] p-[20px] rounded-[8px]"
v-for="(item, index) in dataOverview"
:key="`unique-${index}`"
>
<span>{{ item.label }}</span>
<span class="text-[20px]">{{ item.value }}</span>
</div>
</div>
</card>
</template>
<script setup lang="ts">
import card from './card.vue'
const dataOverview = ref([
{ label: '新增跟进记录(个)', value: 60 },
{ label: '新增客户(个)', value: 60 },
{ label: '成交客户(个)', value: 60 },
{ label: '转化中客户(个)', value: 60 },
{ label: '异常待处理(个)', value: 60 },
{ label: '战败客户(个)', value: 60 }
])
</script>
<style scoped></style>
<style scoped lang="scss">
.data-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
</style>

View File

@ -0,0 +1,69 @@
<template>
<div class="rank flex-1">
<card title="TOP5">
<template #right>
<div class="positions">
<span class="default" :class="{ active: item.id == postId }" v-for="item in positions" :key="item.id" @click="postId = item.id">
{{ item.name }}
</span>
</div>
</template>
<el-table :data="tableData">
<el-table-column prop="index" label="排名" width="70" />
<el-table-column prop="name" label="负责人" width="180" />
<el-table-column prop="totalNumber" label="总数" />
</el-table>
</card>
</div>
</template>
<script setup lang="ts">
import card from './card.vue'
const positions = ref([
{ name: '电销', id: 5 },
{ name: '招生', id: 6 }
])
const postId = ref(5)
const tableData = ref([
{
index: 1,
name: 'John Brown',
totalNumber: 32
},
{
index: 2,
name: 'John Brown',
totalNumber: 32
},
{
index: 3,
name: 'John Brown',
totalNumber: 32
}
])
</script>
<style scoped lang="scss">
.rank {
min-height: 350px;
.positions {
display: flex;
cursor: pointer;
span {
padding: 5px 10px;
border: 1px solid var(--color-gray);
&:first-child {
border-right-color: transparent;
}
&:last-child {
border-left-color: transparent;
}
&.active {
color: var(--color-primary);
border-color: var(--color-primary);
border-right-color: var(--color-primary);
}
}
}
}
</style>

View File

@ -1,91 +1,33 @@
<template>
<div class="workbench">
<div class="workbench flex flex-col gap-[16px]">
<data-overview />
<div class="mb-4 function">2</div>
<div class="lg:flex">
<el-card class="flex-1 !border-none md:mr-4 mb-4" shadow="never">
<template #header>
<span>销售额趋势图近15天</span>
</template>
<div>
<v-charts style="height: 350px" :option="workbenchData.businessOption" :autoresize="true" />
<div class="flex gap-[16px] flex-wrap">
<conversion-process-chart />
<converted-chart />
</div>
</el-card>
<el-card class="flex-1 !border-none md:mr-4 mb-4" shadow="never">
<template #header>
<span>用户访问量近15天</span>
</template>
<div>
<v-charts style="height: 350px" :option="workbenchData.visitorOption" :autoresize="true" />
</div>
</el-card>
<div class="flex gap-[16px] flex-wrap">
<rank />
<clue-status-pie />
<!-- <converted-line-chart /> -->
</div>
</div>
</template>
<script lang="ts" setup>
import dataOverview from './components/data-overview.vue'
import vCharts from 'vue-echarts'
//
const workbenchData: any = reactive({
businessOption: {
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
itemStyle: {
//
color: 'red'
},
tooltip: {
trigger: 'axis'
},
series: [
{
name: '销售量',
data: [],
type: 'line',
smooth: true
}
]
},
visitorOption: {
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
itemStyle: {
//
color: 'red'
},
tooltip: {
trigger: 'axis'
},
series: [
{
name: '访问量',
data: [],
type: 'line',
smooth: true
}
]
}
})
//
const getData = async () => {}
onMounted(() => {
getData()
})
import conversionProcessChart from './components/conversion-process-chart.vue'
import convertedChart from './components/converted-chart.vue'
import rank from './components/rank.vue'
import clueStatusPie from './components/clue-status-pie.vue'
// import convertedLineChart from './components/converted-line-chart.vue'
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.wokrbench {
&-center {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
}
}
</style>