j1ack 2 years ago
parent
commit
7ca6209225
  1. BIN
      src/assets/fonts/YouSheBiaoTiHei.ttf
  2. BIN
      src/assets/images/data-view-bg.png
  3. BIN
      src/assets/images/data-view-header-bg.png
  4. BIN
      src/assets/images/data-view-table-header-bg-large.png
  5. BIN
      src/assets/images/data-view-table-header-bg-middle.png
  6. BIN
      src/assets/images/ic_unfullscreen.png
  7. BIN
      src/assets/logo/plain.png
  8. 12
      src/utils/index.js
  9. 96
      src/utils/ruoyi.js
  10. 579
      src/views/data-view/components/data-view-echart.vue
  11. 173
      src/views/data-view/components/data-view-layout.vue
  12. 255
      src/views/data-view/components/data-view-table.vue
  13. 291
      src/views/data-view/data-view.vue

BIN
src/assets/fonts/YouSheBiaoTiHei.ttf

Binary file not shown.

BIN
src/assets/images/data-view-bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 KiB

BIN
src/assets/images/data-view-header-bg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

BIN
src/assets/images/data-view-table-header-bg-large.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
src/assets/images/data-view-table-header-bg-middle.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

BIN
src/assets/images/ic_unfullscreen.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
src/assets/logo/plain.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

12
src/utils/index.js

@ -5,12 +5,12 @@ import { parseTime } from './ruoyi'
*/ */
export function formatDate(cellValue) { export function formatDate(cellValue) {
if (cellValue == null || cellValue == "") return ""; if (cellValue == null || cellValue == "") return "";
var date = new Date(cellValue) var date = new Date(cellValue)
var year = date.getFullYear() var year = date.getFullYear()
var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
} }
@ -330,7 +330,7 @@ export function makeMap(str, expectsLowerCase) {
? val => map[val.toLowerCase()] ? val => map[val.toLowerCase()]
: val => map[val] : val => map[val]
} }
export const exportDefault = 'export default ' export const exportDefault = 'export default '
export const beautifierConf = { export const beautifierConf = {
@ -387,4 +387,4 @@ export function camelCase(str) {
export function isNumberStr(str) { export function isNumberStr(str) {
return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str)
} }

96
src/utils/ruoyi.js

@ -1,21 +1,21 @@
/** /**
* 通用js方法封装处理 * 通用js方法封装处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
* *
* *
* *
* *
* *
* pay_channel_namekey_id, user_id, store_id, order_no, out_trade_no, * pay_channel_namekey_id, user_id, store_id, order_no, out_trade_no,
* transaction_id, tran_amt, pay_amt, pay_type, channel_type, pay_time, order_status, * transaction_id, tran_amt, pay_amt, pay_type, channel_type, pay_time, order_status,
* rate, channel_rate, mch_id, mch_name, notify_url, back_url, store_no, bank_code, * rate, channel_rate, mch_id, mch_name, notify_url, back_url, store_no, bank_code,
* bank_id, card_type, product_name, product_desc, qrcode_img, qrcode_url, user_key, source_type, attach, * bank_id, card_type, product_name, product_desc, qrcode_img, qrcode_url, user_key, source_type, attach,
* version, charset, sign_type, create_time, update_time, order_msg, media_type * version, charset, sign_type, create_time, update_time, order_msg, media_type
* name, mobile,store_settle_time,store_settle, * name, mobile,store_settle_time,store_settle,
real_amt,rate_amt,poundage_rate,poundage_amt, real_amt,rate_amt,poundage_rate,poundage_amt,
store_name,store_type,type_name pay_type_name, store_name,store_type,type_name pay_type_name,
channel_name pay_channel_name channel_name pay_channel_name
* *
*/ */
const baseURL = process.env.VUE_APP_BASE_API const baseURL = process.env.VUE_APP_BASE_API
@ -343,3 +343,81 @@ export async function blobValidate(data) {
return true; return true;
} }
} }
/**
* toFixed 但是不四舍五入
*/
export function toFixedWithOutRounded(num, s) {
const re = new RegExp('^-?\\d+(?:\.\\d{0,' + (s || -1) + '})?');
return num.toString().match(re)[0];
}
/**
* 格式化金额, 保留两位小数
* @param o
* @param o.amount {number | string} 金额
* @param o.precision {number} 精度, 默认 100 ()
* @param o.thousand {boolean} 是否需要千分位, 默认 true
* @param o.decimal {boolean} 是否需要小数点, 默认 true
* @param o.decimalLength {number} 小数点位数, 默认 2
* @param o.decimalSeparator {string} 小数点分隔符, 默认 '.'
* @param o.thousandSeparator {string} 千分位分隔符, 默认 ','
* @return {string | number}
*/
export const amountFormat = (o) => {
if (o.amount === undefined || o.amount === null) {
return o.amount;
}
let amount = o.amount;
// 如果 amount 是字符串, 则转换为数字
if (typeof amount === 'string') {
amount = parseFloat(amount);
}
let precision = o.precision || 100;
let thousand = o.thousand === undefined ? true : o.thousand;
let decimal = o.decimal === undefined ? true : o.decimal;
let decimalLength = o.decimalLength || 2;
let decimalSeparator = o.decimalSeparator || '.';
let thousandSeparator = o.thousandSeparator || ',';
amount = amount / precision;
amount = toFixedWithOutRounded(amount, decimalLength);
if (thousand) {
amount = amount.replace(/(\d)(?=(\d{3})+\.)/g, '$1' + thousandSeparator);
}
if (decimal) {
amount = amount.replace('.', decimalSeparator);
}
return amount;
}
/**
* sortBy
*/
export function sortBy(arr, key) {
return arr.sort(function (a, b) {
return a[key] - b[key];
});
}
/**
* 获取今天星期几
*/
export function getWeek() {
let week = new Date().getDay();
switch (week) {
case 0:
return "星期日";
case 1:
return "星期一";
case 2:
return "星期二";
case 3:
return "星期三";
case 4:
return "星期四";
case 5:
return "星期五";
case 6:
return "星期六";
}
}

579
src/views/data-view/components/data-view-echart.vue

@ -0,0 +1,579 @@
<template>
<div
ref="charts"
style="min-width: 12vw;max-height:220px;height:220px;"
/>
</template>
<script>
import * as echarts from 'echarts'
import { debounce } from '@/utils'
export default {
props: {
config: Object
},
watch: {
config: {
handler: function(val) {
this.$nextTick(() => {
this.init()
})
},
immediate: true,
deep: true,
}
},
data() {
return {
chart: null
}
},
computed: {
option() {
let percent = 0;
if (this.config.total === 0) {
percent = 0
} else {
percent = ((this.config.value / this.config.total) * 100).toFixed(2)
}
const center = ['50%', '55%']
const self = this
return {
grid: {
left: '0',
top: '0',
right: '0',
bottom: '0',
containLabel: false
},
// backgroundColor: '#0E1327',
backgroundColor: 'transparent',
tooltip: {
show: false
},
angleAxis: {
show: false,
max: 100 * 360 / 270, //-45225270360
type: 'value',
startAngle: 225, //
splitLine: {
show: false
}
},
barMaxWidth: '11%', //
radiusAxis: {
show: false,
type: 'category'
},
//
polar: {
center: center,
radius: '100%'
},
series: [
// ()
{
name: '内部透明起始结束的圆边',
type: 'gauge',
center: center,
radius: '80%',
splitNumber: 10,
axisLine: {
lineStyle: {
color: [
// ///
[0.3, new echarts.graphic.LinearGradient(
1, 0, 0, 0,
[{
offset: 0.4,
color: this.config.opacityBorder[0]
}, {
offset: 1,
color: this.config.opacityBorder[1]
}]
)],
[0.7, new echarts.graphic.LinearGradient(
0, 0, 1, 0,
[{
offset: 0,
color: this.config.opacityBorder[0]
}, {
offset: 1,
color: this.config.opacityBorder[0]
}]
)],
[1, new echarts.graphic.LinearGradient(
0, 0, 1, 0,
[{
offset: 0.4,
color: this.config.opacityBorder[0]
}, {
offset: 1,
color: this.config.opacityBorder[1]
}]
)]
],
width: 2
}
},
z: 4,
axisLabel: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
//
detail: {
show: true,
offsetCenter: [0, '95%'],
color: '#fff',
formatter(params) {
return `{t1|${self.config.valueLabel}}\n{t2|${self.config.totalLabel}}`
},
rich: {
t1: {
fontFamily: 'PingFang TC',
fontSize: '5%',
color: '#C6CEEC',
lineHeight: 18
},
t2: {
fontFamily: 'PingFang TC',
fontSize: '10%',
color: '#FFFFFF',
lineHeight: 18
}
}
},
label: {
show: false
},
pointer: {
show: false
},
title: {
//
show: false
},
data: [
{
name: percent + '%',
value: percent
}
]
},
//
{
name: 'border',
type: 'pie',
clockWise: false,
radius: '70%',
center: center,
animation: false,
z: 0,
data: [{
value: 0,
color: 'transparent',
label: {
show: false
},
labelLine: {
show: false
},
emphasis: {
disabled: true
},
select: {
disabled: true
},
tooltip: {
show: false
},
itemStyle: {
color: '#071422',
shadowColor: this.config.shadowColor,
shadowBlur: 25,
shadowOffsetX: 0,
shadowOffsetY: -25
}
}]
},
// ()
{
name: '内部透明起始结束的圆边',
type: 'gauge',
center: center,
radius: '70%',
axisLine: {
lineStyle: {
color: [
// ///
[0.4, new echarts.graphic.LinearGradient(
1, 0, 0, 0,
[{
offset: 0,
color: this.config.opacityBorder[0]
}, {
offset: 1,
color: this.config.opacityBorder[1]
}]
)],
[0.6, new echarts.graphic.LinearGradient(
1, 0, 0, 0,
[{
offset: 0,
color: this.config.opacityBorder[0]
}, {
offset: 1,
color: this.config.opacityBorder[0]
}]
)]
],
width: 2
}
},
z: 4,
axisLabel: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
detail: {
show: false
},
label: {
show: false
},
pointer: {
show: false
},
title: {
//
show: false
},
data: [
{
name: percent + '%',
value: percent
}
]
},
//
{
name: '外部刻度',
type: 'gauge',
center: center,
radius: '75%',
min: 0, //
max: 100, //
splitNumber: 10, //
startAngle: 225,
endAngle: -45,
axisLine: {
show: false,
lineStyle: {
width: 1,
color: [
[1, 'rgba(0,0,0,0)']
]
}
},
//线
axisLabel: {
show: false
}, //
axisTick: {
show: true,
splitNumber: 4,
lineStyle: {
//
color: '#6DCBFF',
width: 1
},
length: 1
},
//
splitLine: {
show: true,
length: 2,
lineStyle: {
//
color: '#6DCBFF'
}
}, //线
detail: {
show: false
},
pointer: {
show: false
}
},
// ,
{
type: 'bar',
data: [
{
value: percent,
itemStyle: {
//offset0%100%
color: {
type: 'linear',
x: 0,
y: 1,
x2: 0,
y2: 0,
colorStops: [
{
offset: 0,
color: this.config.progressBar[0]
}, {
offset: 1,
color: this.config.progressBar[1]
}
]
}
}
}
],
barGap: '-100%', //,
coordinateSystem: 'polar',
roundCap: true, // v4.5.0
//zindex
z: 2
},
// ,
{
type: 'bar',
data: [
{
value: 100,
itemStyle: {
color: this.config.progressBarBG
}
}
],
barGap: '-100%',
coordinateSystem: 'polar',
roundCap: true,
z: 1
},
{
name: '内部透明起始结束的圆边',
type: 'gauge',
center: center,
radius: '40%',
splitNumber: 10,
axisLine: {
lineStyle: {
color: [
[0, new echarts.graphic.LinearGradient(
0, 1, 0, 0,
[{
offset: 0,
color: this.config.circle2Color[0]
}, {
offset: 1,
color: this.config.circle2Color[1]
}]
)],
[0.2, new echarts.graphic.LinearGradient(
0, 1, 0, 0,
[{
offset: 0,
color: this.config.circle2Color[0]
}, {
offset: 1,
color: this.config.circle2Color[1]
}]
)],
[0.8, new echarts.graphic.LinearGradient(
0, 0, 1, 0,
[{
offset: 0,
color: this.config.circle2Color[1]
}, {
offset: 1,
color: this.config.circle2Color[1]
}]
)],
[1, new echarts.graphic.LinearGradient(
0, 0, 0, 1,
[{
offset: 0,
color: this.config.circle2Color[1]
}, {
offset: 1,
color: this.config.circle2Color[0]
}]
)]
],
width: 1
}
},
z: 4,
axisLabel: {
show: false
},
axisTick: {
show: false
},
splitLine: {
show: false
},
detail: {
show: false
},
label: {
show: false
},
pointer: {
show: false
},
title: {
//
show: false
},
data: [
{
name: percent + '%',
value: percent
}
]
},
//
{
name: '外边框',
type: 'pie',
clockWise: false,
hoverAnimation: false,
radius: ['35%', '35%'],//
center: center,//
tooltip: {
show: false
},
label: {
show: false
},
data: [
{
value: 10,
itemStyle: {
normal: {
borderWidth: 1,//
borderColor: this.config.circle2Color[1]//
}
}
}
]
},
//
{
type: 'pie',
radius: '35%',
center: center,
z: 1,
itemStyle: {
normal: {
color: new echarts.graphic.RadialGradient(.5, .5, .8, [
{
offset: 0,
color: this.config.innerCircle[0]
},
{
offset: .5,
color: this.config.innerCircle[1]
},
{
offset: 1,
color: this.config.innerCircle[2]
}
],
false
),
label: {
show: false
},
labelLine: {
show: false
}
}
},
hoverAnimation: false,
label: {
normal: {
show: true,
position: 'center',
color: '#4c4a4a',
formatter: `{t1|${percent}%}\n{t2|${this.config.value}}\n{t3|${this.config.unit}}`,
rich: {
t1: {
fontFamily: 'PingFang TC',
fontSize: '5%',
color: '#C6CEEC',
lineHeight: 14
},
t2: {
fontFamily: 'PingFang TC',
fontSize: '10%',
color: '#FFFFFF',
lineHeight: 14,
fontWeight: 600
},
t3: {
fontFamily: 'PingFang TC',
fontSize: '5%',
color: '#C6CEEC',
lineHeight: 14
}
}
},
emphasis: {//
show: true
}
},
tooltip: {
show: false
},
data: [100]
}
]
}
}
},
mounted() {
window.addEventListener('resize', debounce(() => {
this.chart?.resize()
}, 200))
},
methods: {
init() {
if (!this.chart) {
this.chart = echarts.init(this.$refs.charts)
}
this.chart.setOption(this.option)
}
}
}
</script>
<style scoped>
</style>

173
src/views/data-view/components/data-view-layout.vue

@ -0,0 +1,173 @@
<template>
<div
class="data-view"
:style="{backgroundImage: `url(${DataViewBG})`}"
>
<!-- 头部部分 -->
<div
class="data-view-header"
:style="{backgroundImage: `url(${DataViewHeaderBg})`}"
>
<div class="identifier">
<img style="width: 23px; height: 23px;" :src="icon" alt="">
<span style="padding-left: 10px;">{{identifier}}</span>
</div>
<h1 class="YouSheBiaoTiHei data-view-header-title">{{ title }}</h1>
<div class="data-view-header-time">
<img @click="$emit('leave')" style="width: 26px; height: 26px; cursor: pointer;" :src="ICUNFullscreen" alt="">
<div style="width: 26px;"/>
<span>{{date}}</span>
</div>
</div>
<!-- 内容 -->
<div class="data-view-body">
<!-- 左边的表格 -->
<div class="left-table">
<slot name="left-table"/>
</div>
<!-- 中间的内容 -->
<div class="middle">
<!-- 中间的图表 -->
<div class="middle-charts">
<slot name="middle-charts"></slot>
</div>
<!-- 中间的表格 -->
<div class="middle-table">
<slot name="middle-table"/>
</div>
</div>
<!-- 右边的表格 -->
<div class="right-table">
<slot name="right-table"/>
</div>
</div>
</div>
</template>
<script>
import DataViewBG from '@/assets/images/data-view-bg.png'
import DataViewHeaderBg from '@/assets/images/data-view-header-bg.png'
import ICUNFullscreen from '@/assets/images/ic_unfullscreen.png'
import { getWeek, parseTime } from '@/utils/ruoyi'
export default {
components: { },
props: {
//
identifier: {
type: String
},
//
icon: {
type: String
},
//
title: {
type: String
}
},
data() {
return {
ICUNFullscreen,
DataViewBG,
DataViewHeaderBg,
date: parseTime(new Date(), `{y}-{m}-{d} {h}:{i}:{s}`) + " " + getWeek()
}
},
mounted() {
//
setInterval(() => {
this.date = parseTime(new Date(), `{y}-{m}-{d} {h}:{i}:{s}`) + " " + getWeek()
}, 1000)
}
}
</script>
<style scoped>
.data-view {
height: 100vh;
width: 100vw;
background-size: cover;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
.data-view-header {
display: flex;
justify-content: space-between;
background-size: contain;
background-repeat: no-repeat;
height: 6vw;
padding: 0 20px 0 20px;
.identifier {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 24px;
line-height: 25px;
color: #FFFFFF;
display: flex;
flex-direction: row;
margin-top: 14px;
}
.data-view-header-time {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 18px;
line-height: 25px;
color: #FFFFFF;
display: flex;
flex-direction: row;
margin-top: 14px;
}
.data-view-header-title {
color: #D4DCFF;
font-size: 2.5vw;
}
}
.data-view-body {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-top: 40px;
padding: 0 20px 0 20px;
}
.left-table {
width: 23.5%;
}
.middle {
width: 50%;
.middle-charts {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.middle-table {
margin-top: 0.5vh;
}
}
.right-table {
width: 23.5%;
}
}
</style>

255
src/views/data-view/components/data-view-table.vue

@ -0,0 +1,255 @@
<template>
<div
class="data-view-table"
:class="['data-view-table' + '--' + size]"
>
<div
class="data-view-table-header"
:style="{ backgroundImage: `url(${DataViewTableHeaderBG})` }"
>
<span class="YouSheBiaoTiHei header-title">{{ title }}</span>
</div>
<table class="data-view-table-body">
<thead>
<tr class="data-view-table-body-header">
<th
class="data-view-table-body-header-item"
v-for="(column, index) of columns"
:key="column.key"
:style="{width: columnSizes[index] + '%'}"
>
{{ column.label }}
</th>
</tr>
</thead>
<tbody ref="tbody" @mouseout="mouseleave" @mouseover="mouseenter">
<tr
v-for="(item, index) in data"
:key="index"
class="data-view-table-body-item"
:class="[
index % 2 === 0 ? 'data-view-table-body-item--odd' : 'data-view-table-body-item--even',
item.balanceWarning === 1 && 'data-view-table-body-item--warning'
]"
>
<td
v-for="(column, index) in columns"
:key="column.key"
:style="{width: columnSizes[index] + '%'}"
>
{{ item[column.key] }}
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import DataViewTableHeaderBGLarge from '@/assets/images/data-view-table-header-bg-large.png'
import DataViewTableHeaderBGMiddle from '@/assets/images/data-view-table-header-bg-middle.png'
export default {
components: {},
props: {
title: {
type: String
},
data: {
type: Array
},
columns: {
type: Array
},
columnSizes: {
type: Array
},
size: String
},
data() {
return {
DataViewTableHeaderBGLarge,
DataViewTableHeaderBGMiddle,
timer: null,
}
},
computed: {
DataViewTableHeaderBG() {
if (this.size === 'large') {
return DataViewTableHeaderBGLarge
} else if (this.size === 'middle') {
return DataViewTableHeaderBGMiddle
}
}
},
watch: {
data: {
handler(newValue, oldValue) {
this.$nextTick(() => {
this.scroll()
})
},
immediate: true
}
},
methods: {
scrollReset() {
this.$refs.tbody.scroll({top: 0, behavior: "smooth"})
},
mouseleave() {
this.scroll()
},
mouseenter() {
clearTimeout(this.timer)
},
scroll() {
const tbody = this.$refs.tbody
if ((tbody.clientHeight + tbody.scrollTop) < tbody.scrollHeight) {
clearInterval(this.timer)
this.timer = setInterval(() => {
const remainScrollTop = tbody.scrollHeight - (tbody.clientHeight + tbody.scrollTop)
tbody.scrollTop += 1
if (tbody.scrollTop >= remainScrollTop) {
clearInterval(this.timer)
}
}, 100)
}
}
}
}
</script>
<style scoped lang="scss">
.data-view-table {
.data-view-table-header {
background-size: cover;
background-repeat: no-repeat;
height: 51px;
align-items: center;
padding-left: 28px;
display: flex;
flex-direction: row;
.header-title {
font-size: 22px;
/* identical to box height */
text-transform: uppercase;
color: #FFFFFF;
}
}
table {
/*设置相邻单元格的边框间的距离*/
border-spacing: 0;
/*表格设置合并边框模型*/
border-collapse: collapse;
text-align: center;
}
table tbody {
display: block;
overflow-y: scroll;
}
table tbody::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
table tbody {
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
overflow-x: hidden;
overflow-y: auto;
}
.data-view-table-body {
display: flex;
flex-direction: column;
tr, td {
padding: 8px 0px 8px 0px;
width: 100%;
}
.data-view-table-body-header {
display: flex;
flex-direction: row;
background: rgba(14, 75, 255, 0.14);
th {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
/* identical to box height */
text-align: center;
color: #5887FF;
}
}
tbody {
width: 100%;
}
.data-view-table-body-item {
width: 100%;
display: flex;
flex-direction: row;
}
.data-view-table-body-item--odd {
}
.data-view-table-body-item--even {
background: rgba(14, 75, 255, 0.14);
}
.data-view-table-body-item--warning {
background: rgba(255, 14, 14, 0.14) !important;
td {
color: #FF3939;
}
}
}
}
.data-view-table--large {
table tbody {
height: 40vh;
}
}
.data-view-table--middle {
table tbody {
height: 65vh;
}
}
.data-view-table-body-header {
}
.data-view-table-body-item td {
font-family: 'PingFang SC';
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
/* identical to box height */
text-align: center;
color: #C6CEEC;
}
</style>

291
src/views/data-view/data-view.vue

@ -0,0 +1,291 @@
<template>
<data-view-layout
title="卡卡数据可视化"
identifier="KK- MANAGE"
:icon="icon"
@leave="handleLeave"
ref="layout"
>
<template #left-table>
<data-view-table
title="商户数据"
:columnSizes="[20, 20, 20, 20, 20]"
:columns="[
{label: '商户名称', key: 'username'},
{label: '余额', key: 'balance'},
{label: '剩余押金额度', key: 'residueBalance'},
{label: '成功总金额', key: 'collectionSuccessPrice'},
{label: '成功率', key: 'percentage'},
]"
:data="data1"
size="middle"
ref="table1"
/>
</template>
<template #middle-charts>
<data-view-echart :config="chart1"/>
<data-view-echart :config="chart2"/>
<data-view-echart :config="chart3"/>
<data-view-echart :config="chart4"/>
</template>
<template #middle-table>
<data-view-table
title="在线卡数据"
:columnSizes="[12, 12, 12, 12, 20, 20, 12]"
:columns="[
{label: '银行名称/卡号', key: 'bankName'},
{label: '收款人名称', key: 'cardHolder'},
{label: '卡商名称', key: 'username'},
{label: '已收款', key: 'todayIncomeReceived'},
{label: '剩余收款额度', key: 'todayRemainingAmount'},
{label: '出款笔数/金额', key: 'todayOutNumber'},
{label: '剩余卡余额', key: 'remainingAmount'},
]"
:data="data2"
size="large"
ref="table2"
/>
</template>
<template #right-table>
<data-view-table
title="在线卡商数据"
:columnSizes="[25, 25, 25, 25]"
:columns="[
{label: '卡商名称', key: 'username'},
{label: '押金/剩余额度', key: 'dayMargin'},
{label: '日总跑量', key: 'toDayTotal'},
{label: '押金周转率', key: 'turnoverRate'},
]"
:data="data3"
size="middle"
ref="table3"
/>
</template>
</data-view-layout>
</template>
<script>
import DataViewBG from '@/assets/images/data-view-bg.png'
import DataViewEchart from './components/data-view-echart.vue'
import DataViewLayout from './components/data-view-layout.vue'
import icon from '@/assets/logo/plain.png'
import DataViewTable from './components/data-view-table.vue'
import { amountFormat, sortBy } from '@/utils/ruoyi'
import screenfull from 'screenfull';
export default {
components: { DataViewTable, DataViewLayout, DataViewEchart },
data() {
return {
DataViewBG,
icon,
//
data1: [],
// 线
data2: [],
// 线
data3: [],
// -/
chart1: {
value: 0,
total: 0,
unit: "单",
// (),
progressBar: ["#4494FF", '#B04BFF'],
//
progressBarBG: '#091560',
// , ,
circle2Color: ['#168FFF00', '#168FFF'],
// ,
innerCircle: ['#082863', '#082863', '#8000FF68'],
// , ,
opacityBorder: ['rgba(0, 0, 0, 0)', 'rgba(45, 103, 252, 1)'],
//
shadowColor: '#2D68FF',
//
scaleColor: '#6DCBFF',
//
valueLabel: '代收成功单数',
//
totalLabel: '总订单:0',
},
chart2: {
value: 0,
total: 0,
unit: "元",
progressBar: ["#4494FF", '#B04BFF'],
progressBarBG: '#091560',
circle2Color: ['#168FFF00', '#168FFF'],
innerCircle: ['#082863', '#082863', '#8000FF68'],
opacityBorder: ['rgba(0, 0, 0, 0)', 'rgba(45, 103, 252, 1)'],
shadowColor: '#2D68FF',
scaleColor: '#6DCBFF',
valueLabel: '代收成功金额',
totalLabel: '总金额:0',
},
chart3: {
value: 0,
total: 0,
unit: "单",
progressBar: ["#4494FF", '#EFA83E'],
progressBarBG: '#0A2F28',
circle2Color: ['#30BE7900', '#30BE79'],
innerCircle: ['#2EB87642', '#2EB87642', '#2AA56C'],
opacityBorder: ['rgba(0, 0, 0, 0)', '#30C17B'],
shadowColor: '#30C17B',
scaleColor: '#6DCBFF',
valueLabel: '代付成功单数',
totalLabel: '总金额:0',
},
chart4: {
value: 0,
total: 0,
unit: "元",
progressBar: ["#4494FF", '#EFA83E'],
progressBarBG: '#0A2F28',
circle2Color: ['#30BE7900', '#30BE79'],
innerCircle: ['#2EB87642', '#2EB87642', '#2AA56C'],
opacityBorder: ['rgba(0, 0, 0, 0)', '#30C17B'],
shadowColor: '#30C17B',
scaleColor: '#6DCBFF',
valueLabel: '代付成功金额',
totalLabel: '总金额:0',
},
ws: null,
isScreenFull: false,
}
},
mounted() {
this.connectWS()
},
methods: {
connectWS() {
this.ws = new WebSocket(`ws://192.168.31.129:18997/websocket`)
this.ws.onopen = () => {
this.ws.send(JSON.stringify({ 'type': 'add_push' }))
}
this.ws.onmessage = (event) => {
if (!event.data) {
return
}
let data
try {
data = JSON.parse(event.data)
} catch (e) {
return
}
if (!data || data.type !== 'push') {
return
}
const card = JSON.parse(data.value.card)
const carddealer = JSON.parse(data.value.carddealer)
const statisticsPush = JSON.parse(data.value.statisticsPush)
// payTotalQty
// paySuccessQty
// paySuccessAmount
// payTotalAmount
// transferTotalQty
// transferSuccessQty
// transferSuccessAmount
// transferTotalAmount
console.log("statisticsPush", statisticsPush)
this.chart1.value = statisticsPush.paySuccessQty
this.chart1.total = statisticsPush.payTotalQty
this.chart1.totalLabel = '总订单:' + statisticsPush.payTotalQty
this.chart2.value = this.NumberDiv(statisticsPush.paySuccessAmount, 100)
this.chart2.total = this.NumberDiv(statisticsPush.payTotalAmount, 100)
this.chart2.totalLabel = '总金额:' + this.NumberDiv(statisticsPush.payTotalAmount, 100)
this.chart3.value = statisticsPush.transferSuccessQty
this.chart3.total = statisticsPush.transferTotalQty
this.chart3.totalLabel = '总订单:' + statisticsPush.transferTotalQty
this.chart4.value = this.NumberDiv(statisticsPush.transferSuccessAmount, 100)
this.chart4.total = this.NumberDiv(statisticsPush.transferTotalAmount, 100)
this.chart4.totalLabel = '总金额:' + this.NumberDiv(statisticsPush.transferTotalAmount, 100)
const pushMerchant = JSON.parse(data.value.pushMerchant)
//
this.data1 = pushMerchant.map(i => {
return {
username: i.username,
balance: amountFormat({ amount: i.balance }) || "0",
residueBalance: amountFormat({ amount: i.residueBalance }) || "0",
collectionSuccessPrice: amountFormat({ amount: i.collectionSuccessPrice }) || "0",
percentage: (i.percentage * 100).toFixed(2) + "%",
balanceWarning: i.balanceWarning,
}
})
this.data1 = sortBy(this.data1, 'balanceWarning')
// 线
this.data2 = card.map(i => {
return {
bankName: i.bankName + '/' + "**** " + i.cardNumber.slice(-4),
cardHolder: i.cardHolder,
username: i.username,
todayIncomeReceived: amountFormat({ amount: i.todayIncomeReceived }) || "0",
todayRemainingAmount: amountFormat({ amount: i.todayRemainingAmount }) || "0",
todayOutNumber: i.todayOutNumber + '/' + amountFormat({ amount: i.todayOutReceived }) || "0",
remainingAmount: amountFormat({ amount: i.remainingAmount }) || "0",
balanceWarning: i.balanceWarning,
}
})
this.data2 = sortBy(this.data2, 'balanceWarning')
// 线
this.data3 = carddealer.map(i => {
return {
username: i.username,
dayMargin: (amountFormat({ amount: i.dayMargin }) || "0") + (amountFormat({ amount: i.margin }) || "0"),
toDayTotal: amountFormat({ amount: i.toDayTotal }) || "0",
turnoverRate: (i.turnoverRate * 100).toFixed(2) + "%",
balanceWarning: i.balanceWarning,
}
})
this.data3 = sortBy(this.data3, 'balanceWarning')
this.$refs.table1.scrollReset();
this.$refs.table2.scrollReset();
this.$refs.table3.scrollReset();
}
},
/**
* 退出全屏
*/
handleLeave() {
if (this.isScreenFull) {
screenfull.exit()
} else {
screenfull.request(this.$refs.layout.$el)
}
this.isScreenFull = !this.isScreenFull;
},
disconnect() {
this.ws?.send({ 'type': 'del_push' })
}
},
beforeDestroy() {
this.disconnect()
}
}
</script>
<style>
@font-face {
font-family: "YouSheBiaoTiHei";
src: url('../../assets/fonts/YouSheBiaoTiHei.ttf');
font-weight: normal;
font-style: normal;
}
.YouSheBiaoTiHei {
font-family: "YouSheBiaoTiHei", serif;
font-style: normal;
font-weight: 400;
}
</style>
Loading…
Cancel
Save