Commit 0c0ce9db authored by 沈翠玲's avatar 沈翠玲

数据大屏

parent 2970522e
...@@ -18,4 +18,17 @@ export const getReturn = (params) => { ...@@ -18,4 +18,17 @@ export const getReturn = (params) => {
// 回款金额 总金额 // 回款金额 总金额
export const getLoanKanBanTotal = (params) => { export const getLoanKanBanTotal = (params) => {
return request.get('Loan/getLoanKanBanTotal', params); return request.get('Loan/getLoanKanBanTotal', params);
};
// 查询30天工作量的接口
export const getTractRecordThirtyDayTotal = (params) => {
return request.get('TrackRecord/getTractRecordThirtyDayTotal', params);
};
// 统计当前每个小时端的作业量
export const getTractRecordDayHoursTotal = (params) => {
return request.get('TrackRecord/getTractRecordDayHoursTotal', params);
};
// 跟进结果:4大类跟进结果统计
export const getFourFollowTypeTotal = (params) => {
return request.get('TrackRecord/getFourFollowTypeTotal', params);
}; };
\ No newline at end of file
import * as echarts from "echarts/core"; import * as echarts from "echarts/core";
import { BarChart, LineChart, LinesChart, PieChart, ScatterChart, RadarChart, GaugeChart, MapChart } from "echarts/charts"; import { BarChart, LineChart, LinesChart, PieChart, ScatterChart, RadarChart, GaugeChart, MapChart,CustomChart } from "echarts/charts";
import { import {
TitleComponent, TitleComponent,
TooltipComponent, TooltipComponent,
...@@ -64,6 +64,7 @@ echarts.use([ ...@@ -64,6 +64,7 @@ echarts.use([
ToolboxComponent, ToolboxComponent,
DataZoomComponent, DataZoomComponent,
BarChart, BarChart,
CustomChart,
LineChart, LineChart,
MapChart, MapChart,
LinesChart, LinesChart,
......
<template> <template>
<div class="echarts"> <div class="echarts">
<ECharts :option="option" :resize="false" /> <ECharts :option="option" :resize="false" :onClick="onClick"/>
</div> </div>
</template> </template>
<script setup> <script setup>
import ECharts from "@/components/ECharts/index.vue"; import ECharts from "@/components/ECharts/index.vue";
import { ranking1, ranking2, ranking3, ranking4 } from "../assets/ranking-icon"; import { ranking1, ranking2, ranking3, ranking4 } from "../assets/ranking-icon";
import { useRouter } from 'vue-router';
import echarts from "@/components/ECharts/config"; import echarts from "@/components/ECharts/config";
import { getReturn } from '@/api/dataScreen'; import { getReturn } from '@/api/dataScreen';
const router = useRouter();
import { computed } from "vue"; import { computed } from "vue";
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
const list = ref([]); const salvProName =ref([]);
const salvProValue =ref([]);
var colorList = ['#73DDFF', '#73ACFF', '#3074c7', '#415b98', '#709cf5', '#9ac0fa', '#58D5FF'] const dataZoom =ref({
getReturn().then(res => { startValue: 0,
console.log('reee', res) endValue: 5
if(res.result) { });
list.value = res.result.map(v => ({name: v.name, value: v.totalReturn})) const props = defineProps({
isAdmin: Boolean,
tenantId: [String, Number, Object]
});
const onClick = (e) => {
if (props.isAdmin) {
const item = salvProValue.value[e.dataIndex]
const url = router.resolve({
path: '/data-screen',
query: { tenantId: item.tenantId },
});
console.log(url.href); // 输出为#/test-url
// 打开新窗口
window.open(url.href);
} }
}
})
const option = computed(() => { const option = computed(() => {
return { return {
grid: {
left: '2%',
right: '2%',
bottom: '2%',
top: '2%',
containLabel: true
},
tooltip: { tooltip: {
trigger: 'item' trigger: 'axis',
axisPointer: {
type: 'none'
},
formatter: function(params) {
return params[0].name + ' : ' + params[0].value + '元'
}
}, },
series: [{ dataZoom: [
type: 'pie', {
center: ['50%', '50%'], type: 'slider',
radius: ['34%', '60%'], orient: 'vertical',
clockwise: true, show: false,
avoidLabelOverlap: true, ...dataZoom.value
hoverOffset: 15, },
itemStyle: { ],
normal: { xAxis: {
color: function(params) { show: false,
return colorList[params.dataIndex] type: 'value'
} },
yAxis: [{
type: 'category',
inverse: true,
axisLabel: {
show: false,
textStyle: {
color: '#fff',
fontSize: '14'
},
},
splitLine: {
show: false
},
axisTick: {
show: false
},
axisLine: {
show: false
},
data: salvProName.value
}, {
type: 'category',
inverse: true,
axisTick: 'none',
axisLine: 'none',
show: true,
axisLabel: {
textStyle: {
color: '#ffffff',
fontSize: '14'
},
formatter: function (a, b) {
return a + '元'
} }
}, },
label: { data:salvProValue.value
color: '#fff', }],
fontSize: 14, series: [{
show: true, name: '值',
position: 'outside', type: 'bar',
formatter: (params) => { zlevel: 1,
return params.name + '\n¥' + params.value + '\n' + params.percent + '%' itemStyle: {
normal: {
// barBorderRadius: 30,
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [{
offset: 0,
color: 'rgb(57,89,255,1)'
}, {
offset: 1,
color: 'rgb(46,200,207,1)'
}]),
},
}, },
label: {
normal: {
color: '#fff',
show: true,
position: [0, '-24px'],
textStyle: {
fontSize: 16
},
formatter: function (a, b) {
return a.name
}
}
},
barWidth: 50,
data: salvProValue.value
}, },
labelLine: { {
normal: { name: '背景',
length: 14, type: 'bar',
length2: 8, barWidth: 50,
lineStyle: { barGap: '-100%',
width: 1, data: salvProValue.value.map(v => (salvProValue.value.length > 0? salvProValue.value[0] : 100)),
color: '#fff' itemStyle: {
normal: {
color: '#142166',
// barBorderRadius: 30,
} }
} },
}, },
data: list.value, ]
}]
} }
}) })
const param = {}
if (!props.isAdmin) {
param['tenant'] = props.tenantId
}
getReturn(param).then(res => {
if(res.result) {
const arr = res.result
arr.sort((a,b) => b.totalReturn - a.totalReturn)
salvProName.value = arr.map(v => v.name)
salvProValue.value = arr.map(v => ({name: v.name,value: v.totalReturn, tenantId: v.tenantId}))
if (salvProName.value.length > 6) {
const timer = setInterval(() => {
dataZoom.value.startValue++;
if (dataZoom.value.endValue++ >= salvProName.value.length - 1) {
dataZoom.value.startValue = 0;
dataZoom.value.endValue = 5;
}
// chart?.setOption(option);
}, 3000);
}
}
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.echarts { .echarts {
......
...@@ -2,21 +2,93 @@ ...@@ -2,21 +2,93 @@
<!-- 中国地图 --> <!-- 中国地图 -->
<div class="map-ball"></div> <div class="map-ball"></div>
<div id="mapChart" class="echarts"> <div id="mapChart" class="echarts">
<ECharts :option="option" ref="EChartsRef" :resize="false" /> <ECharts :option="option" ref="EChartsRef" :resize="false" :onClick="onClick"/>
<template v-if="tenantPos && tenantPos.length > 0">
<tooltip v-for="(item, index) in tenantPos" :key="index" :style="{left: item.left + 'px', top: item.top + 'px'}" :item="item"/>
</template>
</div> </div>
</template> </template>
<script setup> <script setup>
import echarts from "@/components/ECharts/config"; import echarts from "@/components/ECharts/config";
import { computed } from "vue"; import { computed } from "vue";
import { onMounted } from 'vue';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import ECharts from "@/components/ECharts/index.vue"; import ECharts from "@/components/ECharts/index.vue";
import mapJson from "../assets/china.json"; import mapJson from "../assets/china.json";
import { getMapByTenant } from '@/api/dataScreen'; import { getMapByTenant } from '@/api/dataScreen';
import tooltip from './tooltip.vue'
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
const mapData = ref([]); const mapData = ref([]);
const EChartsRef = ref(); const EChartsRef = ref();
const tenantPos = ref([]);
onMounted(() => {
document.body.addEventListener('click', (e) => {
tenantPos.value = []
})
})
const onClick = (e) => {
e.event.event.stopPropagation();
tenantPos.value = []
if (e.data.tenants && e.data.tenants.length > 0) {
let width = 200
let height = 100
e.data.tenants.forEach((ele, index) => {
let offsetX = e.event.offsetX
let offsetY = e.event.offsetY
if (index === 0) {
offsetX = offsetX
offsetY = offsetY - height
}
if (index === 1) {
offsetX = offsetX - (width)
offsetY = offsetY - (height * 2)
}
if (index === 2) {
offsetX = offsetX - (width)
offsetY = offsetY + height
}
if (index === 3) {
offsetX = offsetX
offsetY = offsetY - (height * 2)
}
if (index === 4) {
offsetX = offsetX - (width)
offsetY = offsetY
}
if (index === 5) {
offsetX = offsetX + width
offsetY = offsetY - (height * 2)
}
if (index === 6) {
offsetX = offsetX + width
offsetY = offsetY - height
}
if (index === 7) {
offsetX = offsetX + width
offsetY = offsetY + height
}
if (index === 8) {
offsetX = offsetX
offsetY = offsetY + (height)
}
if (index === 9) {
offsetX = offsetX + width
offsetY = offsetY
}
if (index === 10) {
offsetX = offsetX - width
offsetY = offsetY - height
}
tenantPos.value.push({
left: offsetX,
top: offsetY,
...ele
})
})
}
}
echarts.registerMap("china", mapJson); echarts.registerMap("china", mapJson);
getMapByTenant().then(res => { getMapByTenant().then(res => {
mapData.value = [] mapData.value = []
...@@ -27,16 +99,15 @@ getMapByTenant().then(res => { ...@@ -27,16 +99,15 @@ getMapByTenant().then(res => {
mapData.value.push({ mapData.value.push({
name: element.province, name: element.province,
value: element.commissionAmount, value: element.commissionAmount,
tenants: [{tenantName: element.tenantName, borrowNum: element.borrowNum, commissionAmount: element.commissionAmount, caseNum: element.caseNum}], tenants: [{tenantName: element.tenantName,tenantId: element.tenantId, borrowNum: element.borrowNum, commissionAmount: element.commissionAmount, caseNum: element.caseNum}],
}) })
} else { } else {
const item = mapData.value[findindex] const item = mapData.value[findindex]
item.value = Decimal(item.value || 0).add(Decimal(element.commissionAmount || 0)); item.value = Decimal(item.value || 0).add(Decimal(element.commissionAmount || 0)).toNumber();
item.tenants.push({tenantName: element.tenantName, borrowNum: element.borrowNum, commissionAmount: element.commissionAmount, caseNum: element.caseNum}) item.tenants.push({tenantName: element.tenantName, tenantId: element.tenantId, borrowNum: element.borrowNum, commissionAmount: element.commissionAmount, caseNum: element.caseNum})
} }
}); });
} }
console.log('mapData.value', mapData.value)
EChartsRef.value.draw() EChartsRef.value.draw()
}).catch(() => { }).catch(() => {
mapData.value = [] mapData.value = []
...@@ -95,16 +166,15 @@ const option = computed(() => { ...@@ -95,16 +166,15 @@ const option = computed(() => {
tooltip: { tooltip: {
show: true, show: true,
formatter: function(params) { formatter: function(params) {
console.log('params', params)
let html = '' let html = ''
html = html + params.name + '<br />案件总金额:' + (isNaN(params.value) ? 0 : params.value) + '元' html = html + params.name + '<br />案件总金额:' + (isNaN(params.value) ? 0 : params.value) + '元'
if (params.data.tenants) { // if (params.data.tenants) {
params.data.tenants.forEach(ele => { // params.data.tenants.forEach(ele => {
html = html + '<br /><br />' + ele.tenantName + '<br />案件金额:' + ele.commissionAmount // html = html + '<br /><br />' + ele.tenantName + '<br />案件金额:' + ele.commissionAmount
html = html + '<br />' + '案件数:' + ele.caseNum // html = html + '<br />' + '案件数:' + ele.caseNum
html = html + '<br />' + '案件人数:' + ele.borrowNum // html = html + '<br />' + '案件人数:' + ele.borrowNum
}) // })
} // }
return html return html
} }
}, },
...@@ -196,6 +266,7 @@ const option = computed(() => { ...@@ -196,6 +266,7 @@ const option = computed(() => {
.echarts { .echarts {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative;
} }
.map-ball { .map-ball {
position: absolute; position: absolute;
......
...@@ -12,17 +12,16 @@ import { getReturnRateByPlatform } from '@/api/dataScreen'; ...@@ -12,17 +12,16 @@ import { getReturnRateByPlatform } from '@/api/dataScreen';
import { computed } from "vue"; import { computed } from "vue";
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
var salvProName =ref([]); const salvProName =ref([]);
var salvProValue =ref([]); const salvProValue =ref([]);
getReturnRateByPlatform().then(res => { const dataZoom =ref({
if (res.result) { startValue: 0,
const arr = res.result endValue: 5
arr.sort((a,b) => a.rate - b.rate) });
salvProName.value = arr.map(v => v.loanPlatform) const props = defineProps({
salvProValue.value = arr.map(v => v.rate?.toFixed(2)) isAdmin: Boolean,
} tenantId: [String, Number, Object]
}) });
const option = computed(() => { const option = computed(() => {
return { return {
...@@ -39,9 +38,17 @@ const option = computed(() => { ...@@ -39,9 +38,17 @@ const option = computed(() => {
type: 'none' type: 'none'
}, },
formatter: function(params) { formatter: function(params) {
return params[0].name + ' : ' + params[0].value return params[0].name + ' : ' + params[0].value + '%'
} }
}, },
dataZoom: [
{
type: 'slider',
orient: 'vertical',
show: false,
...dataZoom.value
},
],
xAxis: { xAxis: {
show: false, show: false,
type: 'value' type: 'value'
...@@ -77,6 +84,9 @@ const option = computed(() => { ...@@ -77,6 +84,9 @@ const option = computed(() => {
color: '#ffffff', color: '#ffffff',
fontSize: '14' fontSize: '14'
}, },
formatter: function (a, b) {
return a + '%'
}
}, },
data:salvProValue.value data:salvProValue.value
}], }],
...@@ -120,7 +130,7 @@ const option = computed(() => { ...@@ -120,7 +130,7 @@ const option = computed(() => {
data: salvProValue.value.map(v => 100), data: salvProValue.value.map(v => 100),
itemStyle: { itemStyle: {
normal: { normal: {
color: '#05091e', color: '#142166',
// barBorderRadius: 30, // barBorderRadius: 30,
} }
}, },
...@@ -128,11 +138,33 @@ const option = computed(() => { ...@@ -128,11 +138,33 @@ const option = computed(() => {
] ]
} }
}) })
const param = {}
if (!props.isAdmin) {
param['tenant'] = props.tenantId
}
getReturnRateByPlatform(param).then(res => {
if (res.result) {
const arr = res.result
arr.sort((a,b) => b.rate - a.rate)
salvProName.value = arr.map(v => v.loanPlatform)
salvProValue.value = arr.map(v => v.rate?.toFixed(2))
if (salvProName.value.length > 6) {
const timer = setInterval(() => {
dataZoom.value.startValue++;
if (dataZoom.value.endValue++ >= salvProName.value.length - 1) {
dataZoom.value.startValue = 0;
dataZoom.value.endValue = 5;
}
// chart?.setOption(option);
}, 3000);
}
}
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.echarts { .echarts {
width: 100%; width: 100%;
height: calc(100% - 56px); height: calc(100%);
} }
.echarts-header { .echarts-header {
box-sizing: border-box; box-sizing: border-box;
......
...@@ -7,148 +7,244 @@ ...@@ -7,148 +7,244 @@
<script setup > <script setup >
import ECharts from "@/components/ECharts/index.vue"; import ECharts from "@/components/ECharts/index.vue";
import { useDict } from '@/hooks/useDict';
import echarts from "@/components/ECharts/config"; import echarts from "@/components/ECharts/config";
import { reactive, ref, computed } from 'vue';
import { getFourFollowTypeTotal } from '@/api/dataScreen';
const salvPro =ref([]);
const { FollowStatus } = useDict("FollowStatus");
const props = defineProps({
tenantId: [String, Number, Object]
});
// 绘制左侧面
const CubeLeft = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0
},
buildPath: function(ctx, shape) {
// 会canvas的应该都能看得懂,shape是从custom传入的
const xAxisPoint = shape.xAxisPoint
console.log(shape)
const c0 = [shape.x + 17, shape.y]
const c1 = [shape.x - 30, shape.y - 6]
const c2 = [xAxisPoint[0] - 30, xAxisPoint[1] - 13]
const c3 = [xAxisPoint[0] + 17, xAxisPoint[1]]
ctx.moveTo(c0[0], c0[1]).lineTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).closePath()
}
})
// 绘制右侧面
const CubeRight = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0
},
buildPath: function(ctx, shape) {
const xAxisPoint = shape.xAxisPoint
const c1 = [shape.x + 17, shape.y]
const c2 = [xAxisPoint[0] + 17, xAxisPoint[1]]
const c3 = [xAxisPoint[0] + 35, xAxisPoint[1] - 15]
const c4 = [shape.x + 35, shape.y - 15]
ctx.moveTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).lineTo(c4[0], c4[1]).closePath()
}
})
// 绘制顶面
const CubeTop = echarts.graphic.extendShape({
shape: {
x: 0,
y: 0
},
buildPath: function(ctx, shape) {
const c1 = [shape.x + 17, shape.y]
const c2 = [shape.x + 35, shape.y - 15] //右点
const c3 = [shape.x - 12, shape.y - 20]
const c4 = [shape.x - 30, shape.y - 6]
ctx.moveTo(c1[0], c1[1]).lineTo(c2[0], c2[1]).lineTo(c3[0], c3[1]).lineTo(c4[0], c4[1]).closePath()
}
})
// 注册三个面图形
echarts.graphic.registerShape('CubeLeft', CubeLeft)
echarts.graphic.registerShape('CubeRight', CubeRight)
echarts.graphic.registerShape('CubeTop', CubeTop)
const param = {
tenant: props.tenantId
}
getFourFollowTypeTotal(param).then(res => {
if(res.result) {
console.log('res.result', res.result)
salvPro.value = res.result.map(v => ({name: v.followType, value: v.tractNum, flowStatusTotalList: v.flowStatusTotalList}))
}
})
/**
*
* 作者: GhostCat
* 博客: https://gcat.cc
* 描述: 双折线图
*
*/
let xLabel = ['西南调解中心', '雄安调解中心', '成都调解中心', '大同调解中心']
let goToSchool = ["40", "60", "22", "85"]
let goOutSchool = ["20", "50", "12", "65"]
const option = { const option = computed(() => {
return {
tooltip: { tooltip: {
trigger: 'axis' trigger: 'axis',
}, axisPointer: {
legend: { type: 'shadow'
show: false },
formatter: function(params, ticket, callback) {
const item = params[1]
let html = item.name + ' : ' + item.value + '<br/>'
if (item.data && item.data.flowStatusTotalList.length > 0) {
html += '以下是明细:<br/>'
item.data.flowStatusTotalList.forEach((item, index) => {
html += (item.followStatus
? FollowStatus.value?.find((v) => v.value === item.followStatus)?.label
: '') + item.tractNum + '个<br/>'
})
html +='</div>'
}
return html;
}
}, },
grid: { grid: {
top: '20%', left: '5%',
left: '10%', right: '5%',
right: '10%', top: '15%',
bottom: '15%', bottom: '5%',
containLabel: true containLabel: true
}, },
xAxis: [{ xAxis: {
type: 'category', type: 'category',
boundaryGap: false, data: salvPro.value.map(v => v.name),
axisLine: { //坐标轴轴线相关设置。数学上的x轴 axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: '#233653' color: 'white'
},
},
axisLabel: { //坐标轴刻度标签的相关设置
textStyle: {
color: '#7ec7ff',
padding: 16,
fontSize: 14
},
formatter: function(data) {
return data
} }
}, },
splitLine: {
show: true,
lineStyle: {
color: '#192a44'
},
},
axisTick: { axisTick: {
show: false, show: false,
}, length: 9,
data: xLabel alignWithLabel: true,
}],
yAxis: [{
name: '回款率',
nameTextStyle: {
color: "#7ec7ff",
fontSize: 16,
padding: 10
},
min: 0,
max: 100,
splitLine: {
show: true,
lineStyle: { lineStyle: {
color: '#192a44' color: '#7DFFFD'
}, }
}, },
axisLabel: {
fontSize: 20
}
},
yAxis: {
type: 'value',
axisLine: { axisLine: {
show: true, show: true,
lineStyle: { lineStyle: {
color: "#233653" color: 'white'
} }
}, },
axisLabel: { min: 0,
show: true, splitLine: {
textStyle: { show: false
color: '#7ec7ff',
padding: 16
},
formatter: function(value) {
if (value === 0) {
return value
}
return value
}
}, },
axisTick: { axisTick: {
show: false, show: false
},
axisLabel: {
fontSize: 16
}, },
}], boundaryGap: ['20%', '20%']
},
series: [{ series: [{
name: '回款率', type: 'custom',
type: 'line', renderItem: (params, api) => {
symbol: 'circle', // 默认是空心圆(中间是白色的),改成实心圆 const location = api.coord([api.value(0), api.value(1)])
showAllSymbol: true, return {
symbolSize: 0, type: 'group',
smooth: true, children: [{
lineStyle: { type: 'CubeLeft',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1],
xAxisPoint: api.coord([api.value(0), 0])
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#1BC9F1'
},
{
offset: 1,
color: '#1C7084'
}
])
}
}, {
type: 'CubeRight',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1],
xAxisPoint: api.coord([api.value(0), 0])
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#1C7287'
},
{
offset: 1,
color: '#1BC3EB'
}
])
}
}, {
type: 'CubeTop',
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: location[0],
y: location[1],
xAxisPoint: api.coord([api.value(0), 0])
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: '#06728A'
},
{
offset: 1,
color: '#06728A'
}
])
}
}]
}
},
data: salvPro.value
}, {
type: 'bar',
label: {
normal: { normal: {
width: 5, show: true,
color: "rgba(10,219,250,1)", // 线条颜色 position: 'top',
}, formatter: (e) => {
borderColor: 'rgba(0,0,0,.4)', return e.value
},
fontSize: 16,
color: '#fff',
offset: [2, -25]
}
}, },
barWidth: 170,
itemStyle: { itemStyle: {
color: "rgba(10,219,250,1)", color: 'transparent'
borderColor: "#646ace",
borderWidth: 2
}, },
tooltip: { tooltip: {
show: true
},
areaStyle: { //区域填充样式
normal: {
//线性渐变,前4个参数分别是x0,y0,x2,y2(范围0~1);相当于图形包围盒中的百分比。如果最后一个参数是‘true’,则该四个值是绝对像素位置。
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: "rgba(10,219,250,.3)"
},
{
offset: 1,
color: "rgba(10,219,250,0)"
}
], false),
shadowColor: 'rgba(10,219,250,.5)', //阴影颜色
shadowBlur: 20 //shadowBlur设图形阴影的模糊大小。配合shadowColor,shadowOffsetX/Y, 设置图形的阴影效果。
}
}, },
data: goToSchool data: salvPro.value
}] }]
}; }
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
...@@ -6,47 +6,94 @@ ...@@ -6,47 +6,94 @@
</template> </template>
<script setup > <script setup >
import { reactive, ref, watch, computed } from 'vue';
import { useDict } from '@/hooks/useDict';
import dayjs from "dayjs"; import dayjs from "dayjs";
import { getTractRecordThirtyDayTotal, getTractRecordDayHoursTotal } from '@/api/dataScreen';
import echarts from "@/components/ECharts/config";
import ECharts from "@/components/ECharts/index.vue"; import ECharts from "@/components/ECharts/index.vue";
const { FollowStatus } = useDict("FollowStatus");
const props = defineProps({ const props = defineProps({
dayActiveName: String dayActiveName: String,
tenant: [String, Number]
}); });
const initDate = () => { const datas = ref([]);
const dateList = []; const dates = ref([]);
let startDate = dayjs();
const endDate = startDate.add(30, "day"); const query = () => {
while (startDate.isBefore(endDate)) { if (props.dayActiveName === '月'){
const month = startDate.format("MM"); const param = {}
const day = startDate.format("DD"); if (props.tenant !== '全部') {
dateList.push(`${month}/${day}`); param['tenantId'] = props.tenant
startDate = startDate.add(1, "day"); }
getTractRecordThirtyDayTotal(param).then(res => {
if (res.result) {
res.result.reverse()
datas.value = res.result.map(v => ({value: v.num, name: v.time, flowStatusTotalList: v.flowStatusTotalList}))
dates.value = res.result.map(v => v.time)
}
}).catch(() => {
datas.value = []
dates.value = []
})
} else {
const param = {}
if (props.tenant !== '全部') {
param['tenantId'] = props.tenant
}
getTractRecordDayHoursTotal(param).then(res => {
if (res.result) {
datas.value = res.result.map(v => ({value: v.num, name: v.time, flowStatusTotalList: v.flowStatusTotalList}))
dates.value = res.result.map(v => v.time)
}
}).catch(() => {
datas.value = []
dates.value = []
})
}
}
query()
watch(
() => props.dayActiveName,
(newValue, oldValue) => {
query();
}
);
watch(
() => props.tenant,
(newValue, oldValue) => {
query();
} }
return dateList; );
};
const data = {
unit: ["每天拨打记录总额"],
data: new Array(31).fill("").map(val => {
val = Math.floor(Math.random() * (11000 - 9000 + 1)) + 9000;
return val;
})
};
const option = { const option = computed(() => {
return {
tooltip: { tooltip: {
trigger: "axis", trigger: "axis",
confine: true, confine: true,
hideDelay: 200, // 浮层隐藏的延迟
enterable: true,
formatter: params => { formatter: params => {
let tipData = params[0]; let tipData = params[0];
let html = `<div class="line-chart-bg"> let html = `<div style="height: auto;max-height: 170px;overflow-y: auto;">${tipData.name}${props.dayActiveName === '日'?':00':''} <i >${tipData.value}</i> 个<br/>`;
<span style="">${tipData.name} <i >${tipData.value}</i> 人次拨打</span> if (tipData.data && tipData.data.flowStatusTotalList.length > 0) {
</div>`; tipData.data.flowStatusTotalList.forEach((item, index) => {
html += (item.followStatus
? FollowStatus.value?.find((v) => v.value === item.followStatus)?.label
: '') + item.tractNum + '个<br/>'
})
html +='</div>'
}
return html; return html;
}, },
backgroundColor: "transparent", // backgroundColor: "transparent",
borderColor: "transparent", // borderColor: "transparent",
axisPointer: { lineStyle: { type: "dashed" }, snap: true }, axisPointer: { lineStyle: { type: "dashed" }, snap: true },
extraCssText: "box-shadow: none;padding:0" // extraCssText: "box-shadow: none;padding:0"
}, },
grid: { grid: {
top: "15%", top: "15%",
...@@ -69,23 +116,22 @@ const option = { ...@@ -69,23 +116,22 @@ const option = {
} }
}, },
axisLabel: { axisLabel: {
color: "#7ec7ff", color: "#fff",
padding: 0, padding: 0,
fontSize: 12, fontSize: 14,
formatter: function (data) { formatter: function (data) {
return data; return data;
} }
}, },
splitLine: { show: false, lineStyle: { color: "#192a44" } }, splitLine: { show: false, lineStyle: { color: "#192a44" } },
axisTick: { show: false }, axisTick: { show: false },
data: initDate() data: dates.value
} }
], ],
yAxis: data.unit.map((_val, index) => { yAxis: {
return { name: "作业量",
name: "(每天拨打记录总额)",
nameTextStyle: { nameTextStyle: {
color: "#7ec7ff", color: "#fff",
fontSize: 12, fontSize: 12,
padding: [0, 30, -4, 30] padding: [0, 30, -4, 30]
}, },
...@@ -93,18 +139,19 @@ const option = { ...@@ -93,18 +139,19 @@ const option = {
splitLine: { splitLine: {
show: false, show: false,
lineStyle: { lineStyle: {
color: "#192a44" color: "#fff"
} }
}, },
axisLine: { axisLine: {
show: index === 0 ? true : false, show: true,
lineStyle: { lineStyle: {
color: "#233653" color: "#fff"
} }
}, },
axisLabel: { axisLabel: {
show: true, show: true,
color: "#7ec7ff", color: "#fff",
fontSize: 14,
padding: 0, padding: 0,
formatter: function (value) { formatter: function (value) {
if (Number(value) >= 10000) { if (Number(value) >= 10000) {
...@@ -116,10 +163,8 @@ const option = { ...@@ -116,10 +163,8 @@ const option = {
axisTick: { axisTick: {
show: false show: false
} }
}; },
}), series: [{
series: data.data.map(() => {
return {
name: "", name: "",
type: "line", type: "line",
symbol: "circle", symbol: "circle",
...@@ -127,12 +172,12 @@ const option = { ...@@ -127,12 +172,12 @@ const option = {
smooth: true, smooth: true,
lineStyle: { lineStyle: {
width: 1, width: 1,
color: "#707070", color: "#fff",
borderColor: "#707070" borderColor: "#707070"
}, },
itemStyle: { itemStyle: {
color: "#F5B348", color: "rgb(57,89,255,1)",
shadowColor: "rgba(245, 179, 72, 0.3)", shadowColor: "rgb(46,200,207,1)",
shadowBlur: 3 shadowBlur: 3
}, },
emphasis: { emphasis: {
...@@ -146,19 +191,17 @@ const option = { ...@@ -146,19 +191,17 @@ const option = {
x2: 0, x2: 0,
y2: 1, y2: 1,
colorStops: [ colorStops: [
{ offset: 0, color: "#846B38" }, { offset: 0, color: "#a7cafd" },
{ offset: 0.5, color: "#403E47" }, { offset: 1, color: "#05e8fe" }
{ offset: 1, color: "#11144E" }
], ],
global: false global: false
}, },
shadowColor: "rgba(255, 199, 37, 0)", shadowColor: "rgba(255, 199, 37, 0)",
shadowBlur: 20 shadowBlur: 20
}, },
data: data.data data: datas.value
}; }]}
}) })
};
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
......
<template>
<div class="tooltip-wrap" @click.stop="jump(item)">
<div class="city">{{item.tenantName}}</div>
<div class="info">案件金额:{{item.commissionAmount}}</div>
<div class="info">案件数:{{item.caseNum}}</div>
<div class="info">案件人数:{{item.borrowNum}}</div>
</div>
</template>
<script setup >
import { useRouter } from 'vue-router';
const router = useRouter();
const props = defineProps({
item: [Object]
});
const jump = (item) => {
const url = router.resolve({
path: '/data-screen',
query: { tenantId: item.tenantId },
});
console.log(url.href); // 输出为#/test-url
// 打开新窗口
window.open(url.href);
}
</script>
<style lang="scss" scoped>
.tooltip-wrap {
position: absolute;
z-index: 99999;
width: 200px;
cursor: pointer;
height: 100px;
background: #6a31eb;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
overflow: hidden;
color: white;
padding: 5px;
transition: transform 0.3s;
&:hover{
transform: scale(1.05);
}
.city {
font-size: 14px;
font-weight: bold;
color: #fff; /* 突出城市的颜色 */
}
.info {
font-size: 14px;
margin-top: 2px;
}
}
</style>
\ No newline at end of file
.dataScreen-container { .dataScreen-container {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #140f59; background: #2353a8;
.dataScreen-content { .dataScreen-content {
position: fixed; position: fixed;
top: 50%; top: 50%;
...@@ -139,6 +139,7 @@ ...@@ -139,6 +139,7 @@
} }
.number-value { .number-value {
font-size: 44px; font-size: 44px;
font-weight: 600;
} }
} }
.dataScreen-cb { .dataScreen-cb {
...@@ -146,12 +147,12 @@ ...@@ -146,12 +147,12 @@
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
height: 252px; height: 252px;
background: #100c476b; background: #0574e83d;
padding-top: 54px; padding-top: 54px;
.tabs { .tabs {
border-radius: 5px; border-radius: 5px;
position: absolute; position: absolute;
border: 1px solid #0350e9; border: 1px solid #40daff;
right: 3px; right: 3px;
top: 3px; top: 3px;
color: #fff; color: #fff;
...@@ -159,13 +160,26 @@ ...@@ -159,13 +160,26 @@
overflow: hidden; overflow: hidden;
.tab-item { .tab-item {
padding: 2px 20px; padding: 2px 20px;
border: 1px solid #0350e9; border: 1px solid #40daff;
&.active{ &.active{
background: #0350e9; background: #40daff;
} }
} }
} }
} }
.my-select {
color: #fff;
width: 250px;
.el-select {
--el-border-color: #40daff;
--el-color-primary: #40daff;
--el-border-color-hover: #40daff;
--el-fill-color-blank: transparent;
--el-text-color-regular: #fff;
.el-select__wrapper {
}
}
}
.dataScreen-map-title { .dataScreen-map-title {
position: absolute; position: absolute;
top: 10px; top: 10px;
...@@ -219,7 +233,7 @@ ...@@ -219,7 +233,7 @@
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
width: 500px; width: 500px;
background: #100c476b; background: #0574e83d;
height: 100%; height: 100%;
margin-right: 40px; margin-right: 40px;
.dataScreen-top, .dataScreen-top,
...@@ -262,7 +276,7 @@ ...@@ -262,7 +276,7 @@
justify-content: space-between; justify-content: space-between;
width: 500px; width: 500px;
height: 100%; height: 100%;
background: #100c476b; background: #0574e83d;
.dataScreen-top, .dataScreen-top,
.dataScreen-center, .dataScreen-center,
.dataScreen-bottom { .dataScreen-bottom {
......
...@@ -16,19 +16,19 @@ ...@@ -16,19 +16,19 @@
</div> </div>
<div class="dataScreen-Number flex justify-between"> <div class="dataScreen-Number flex justify-between">
<div class="number-box"> <div class="number-box">
<p class="number-value text-white">{{staticNumber.caseNum}}</p> <p class="number-value text-white">{{numberFormat(staticNumber.caseNum)}}</p>
<p class="number-title text-lg">案件总数</p> <p class="number-title text-lg">案件总数</p>
</div> </div>
<div class="number-box"> <div class="number-box">
<p class="number-value text-white">{{staticNumber.borrowerNum}}</p> <p class="number-value text-white">{{numberFormat(staticNumber.borrowerNum)}}</p>
<p class="number-title text-lg">案件人数</p> <p class="number-title text-lg">案件人数</p>
</div> </div>
<div class="number-box"> <div class="number-box">
<p class="number-value text-white">{{staticNumber.amount}}</p> <p class="number-value text-white">{{numberFormat(staticNumber.amount)}}</p>
<p class="number-title text-lg">案件总额</p> <p class="number-title text-lg">案件总额</p>
</div> </div>
<div class="number-box"> <div class="number-box">
<p class="number-value text-white">{{staticNumber.sumRepayTotal}}</p> <p class="number-value text-white">{{numberFormat(staticNumber.sumRepayTotal)}}</p>
<p class="number-title text-lg">回款金额</p> <p class="number-title text-lg">回款金额</p>
</div> </div>
<div class="number-box"> <div class="number-box">
...@@ -43,15 +43,19 @@ ...@@ -43,15 +43,19 @@
产品回款率 产品回款率
</div> </div>
<div class="dataScreen-main-chart"> <div class="dataScreen-main-chart">
<HotPlateChart /> <HotPlateChart :isAdmin="isAdmin" :tenantId="tenantId"/>
</div> </div>
</div> </div>
</div> </div>
<div class="dataScreen-ct"> <div class="dataScreen-ct">
<div class="dataScreen-map"> <div class="dataScreen-map" v-if="isAdmin">
<div class="dataScreen-map-title">调解中心分布点</div> <div class="dataScreen-map-title">调解中心分布点</div>
<ChinaMapChart /> <ChinaMapChart />
</div> </div>
<div class="dataScreen-map" v-else>
<div class="dataScreen-map-title">当日跟进分析</div>
<MaleFemaleRatioChart :tenantId="tenantId"/>
</div>
</div> </div>
<div class="dataScreen-rg"> <div class="dataScreen-rg">
...@@ -60,7 +64,7 @@ ...@@ -60,7 +64,7 @@
调解中心回款金额 调解中心回款金额
</div> </div>
<div class="dataScreen-main-chart"> <div class="dataScreen-main-chart">
<AnnualUseChart /> <AnnualUseChart :isAdmin="isAdmin" :tenantId="tenantId"/>
</div> </div>
</div> </div>
</div> </div>
...@@ -69,12 +73,25 @@ ...@@ -69,12 +73,25 @@
<div class="dataScreen-main-title" style="width: auto;"> <div class="dataScreen-main-title" style="width: auto;">
作业量趋势分析 作业量趋势分析
</div> </div>
<div class=" absolute top-1 right-40 flex my-select items-center" v-if="isAdmin">
<div style="width: 95px;margin-right: 5px;">
调解中心
</div>
<el-select v-model="tenant">
<el-option
v-for="item in Tenantlist"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</div>
<div class="tabs flex"> <div class="tabs flex">
<div class="tab-item" @click="dayActiveName = '日'" :class="{active: dayActiveName === '日'}">日</div> <div class="tab-item" @click="dayActiveName = '日'" :class="{active: dayActiveName === '日'}">日</div>
<div class="tab-item" @click="dayActiveName = '月'" :class="{active: dayActiveName === '月'}">月</div> <div class="tab-item" @click="dayActiveName = '月'" :class="{active: dayActiveName === '月'}">月</div>
</div> </div>
<div class="dataScreen-main-chart"> <div class="dataScreen-main-chart" style="height: 100%;">
<OverNext30Chart :dayActiveName="dayActiveName" /> <OverNext30Chart :dayActiveName="dayActiveName" :tenant="tenant" />
</div> </div>
</div> </div>
</div> </div>
...@@ -84,6 +101,10 @@ ...@@ -84,6 +101,10 @@
<script setup name="dataScreen"> <script setup name="dataScreen">
import { ref, onMounted, onBeforeUnmount } from "vue"; import { ref, onMounted, onBeforeUnmount } from "vue";
import { HOME_URL } from "@/config"; import { HOME_URL } from "@/config";
import { getTenantPage } from '@/api/tenant';
import { useRoute } from 'vue-router';
import Decimal from 'decimal.js';
import { computed } from "vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import AgeRatioChart from "./components/AgeRatioChart.vue"; import AgeRatioChart from "./components/AgeRatioChart.vue";
import AnnualUseChart from "./components/AnnualUseChart.vue"; import AnnualUseChart from "./components/AnnualUseChart.vue";
...@@ -92,15 +113,63 @@ import HotPlateChart from "./components/HotPlateChart.vue"; ...@@ -92,15 +113,63 @@ import HotPlateChart from "./components/HotPlateChart.vue";
import MaleFemaleRatioChart from "./components/MaleFemaleRatioChart.vue"; import MaleFemaleRatioChart from "./components/MaleFemaleRatioChart.vue";
import OverNext30Chart from "./components/OverNext30Chart.vue"; import OverNext30Chart from "./components/OverNext30Chart.vue";
import PlatformSourceChart from "./components/PlatformSourceChart.vue"; import PlatformSourceChart from "./components/PlatformSourceChart.vue";
import { useUserStore } from '@/stores/modules/user';
import RealTimeAccessChart from "./components/RealTimeAccessChart.vue"; import RealTimeAccessChart from "./components/RealTimeAccessChart.vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
const userStore = useUserStore();
const route = useRoute();
import { getLoanKanBanTotal } from '@/api/dataScreen'; import { getLoanKanBanTotal } from '@/api/dataScreen';
import { useAuthStore } from '@/stores/modules/auth';
const { authButtonListGet } = useAuthStore(); // 获取用户权限列表
const isAdmin = ref(false);
const tenant = ref('全部');
const tenantId = ref();
// userStore.tenant.id
console.log('route.query', userStore.userInfo)
console.log('authButtonListGet', authButtonListGet.includes('admin_screen'))
if (authButtonListGet.includes('admin_screen') && !route.query.tenantId) {
isAdmin.value = true
} else {
isAdmin.value = false
}
if (authButtonListGet.includes('admin_screen') && window.location.search) {
const searchParams = new URLSearchParams(window.location.search)
const id = searchParams.get('tenantId')
console.log('tenantId', id)
tenant.value = id
tenantId.value = id
} else if (userStore.userInfo?.tenants && userStore.userInfo?.tenants.length > 0){
let id = ''
if (userStore.userInfo?.tenants.length < 2) {
id = userStore.userInfo?.tenants[0].id;
} else {
id = userStore.tenant.id;
}
tenant.value = id
console.log('tenant.value', tenant.value)
tenantId.value = id
}
const router = useRouter(); const router = useRouter();
const dataScreenRef = ref(null); const dataScreenRef = ref(null);
const staticNumber = ref({}); const staticNumber = ref({});
const dayActiveName = ref('日'); const dayActiveName = ref('日');
const Tenantlist = ref([]);
const numberFormat = (number) => {
if (!number || isNaN(Number(number))) {
console.log('number', number)
return '--'
}
const num = Number(number)
if (num > 100000000) {
return Decimal(num).div(Decimal(100000000)).toNumber().toFixed(2) + '亿';
} else if (num> 10000) {
return Decimal(num).div(Decimal(10000)).toNumber().toFixed(2) + '万';
}else if (num> 1000) {
return Decimal(num).div(Decimal(1000)).toNumber().toFixed(2) + '千';
}
return num
}
onMounted(() => { onMounted(() => {
if (dataScreenRef.value) { if (dataScreenRef.value) {
dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`; dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`;
...@@ -116,7 +185,11 @@ const resize = () => { ...@@ -116,7 +185,11 @@ const resize = () => {
dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`; dataScreenRef.value.style.transform = `scale(${getScale()}) translate(-50%, -50%)`;
} }
}; };
getLoanKanBanTotal().then(res=> { const params = {}
if (!isAdmin.value) {
params['tenant'] = tenantId.value
}
getLoanKanBanTotal(params).then(res=> {
if (res.success) { if (res.success) {
staticNumber.value = res.result staticNumber.value = res.result
} }
...@@ -127,7 +200,12 @@ const getScale = (width = 1920, height = 1080) => { ...@@ -127,7 +200,12 @@ const getScale = (width = 1920, height = 1080) => {
let wh = window.innerHeight / height; let wh = window.innerHeight / height;
return ww < wh ? ww : wh; return ww < wh ? ww : wh;
}; };
getTenantPage({ current: 1, size: 9999999, status: 'enable' }).then(res => {
if (res.success) {
Tenantlist.value = res.result.content
Tenantlist.value.unshift({name: '全部', id: '全部'})
}
})
// 获取当前时间 // 获取当前时间
let timer = null; let timer = null;
let time = ref(dayjs().format("YYYY年MM月DD HH:mm:ss")); let time = ref(dayjs().format("YYYY年MM月DD HH:mm:ss"));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment