Commit 1379c461 authored by 沈翠玲's avatar 沈翠玲

首页更新

parent f6aec18f
...@@ -32,3 +32,6 @@ export const getTractRecordDayHoursTotal = (params) => { ...@@ -32,3 +32,6 @@ export const getTractRecordDayHoursTotal = (params) => {
export const getFourFollowTypeTotal = (params) => { export const getFourFollowTypeTotal = (params) => {
return request.get('TrackRecord/getFourFollowTypeTotal', params); return request.get('TrackRecord/getFourFollowTypeTotal', params);
}; };
export const getRepayRecordTotal = (params) => {
return request.get('repayRecord/getRepayRecordTotal', params);
};
<template>
<!-- 未来30天访问量趋势预测图 -->
<div class="region" style="margin-right: 10px; width: calc(50% - 10px)">
<div class="echarts">
<ECharts :option="option" :resize="false" />
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch, computed, onBeforeUnmount } from 'vue';
import { useDict } from '@/hooks/useDict';
import dayjs from 'dayjs';
import { getRepayRecordTotal } from '@/api/dataScreen';
import echarts from '@/components/ECharts/config';
import ECharts from '@/components/ECharts/index.vue';
const { FollowStatus } = useDict('FollowStatus');
const load = ref(false);
const props = defineProps({
dayActiveName: String,
tenant: [String, Number],
});
const datas = ref([]);
const dates = ref([]);
const query = () => {
const param = {};
if (props.tenant) {
param['tenant'] = props.tenant;
}
load.value = true;
getRepayRecordTotal(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);
}
load.value = false;
})
.catch(() => {
datas.value = [];
dates.value = [];
});
};
query();
watch(
() => props.tenant,
(newValue, oldValue) => {
query();
}
);
const option = computed(() => {
return {
grid: {
left: '0',
right: '4%',
bottom: '1%',
top: '10px',
containLabel: true
},
yAxis: {
type: 'value',
axisLabel: {
show: true,
color: '#999',
fontSize: 13
},
splitLine: {
show: true,
lineStyle: {
color: '#EFF1F3',
width: 1,
type: 'dashed'
}
},
axisLine: {
show: true,
lineStyle: {
color: '#EFF1F3',
width: 1
}
}
},
xAxis: {
type: 'category',
data: [1, 2, 3, 4, 5, 6, 7, 8, 9],
boundaryGap: [0, 0.01],
splitLine: {
show: false
},
axisLine: {
show: true,
lineStyle: {
color: '#EFF1F3',
width: 1
}
},
axisLabel: {
show: true,
color: '#999',
fontSize: 13
}
},
series: [
{
data: [160, 100, 150, 80, 190, 100, 175, 120, 160],
type: 'bar',
barMaxWidth: 36,
itemStyle: {
borderRadius: [6, 6, 6, 6],
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: '#9eb7ff'
},
{
offset: 1,
color: '#5d87ff'
}
])
}
}
]
};
});
</script>
<style lang="scss" scoped>
.echarts {
width: 100%;
height: 100%;
:deep(.line-chart-bg) {
box-sizing: border-box;
display: flex;
align-items: center;
width: 180px;
height: 60px;
padding-left: 20px;
background: url('../images/line-bg.png') no-repeat;
background-size: 100% 100%;
span {
font-size: 12px;
color: rgb(255 255 255 / 80%);
i {
font-style: normal;
color: #f5b348;
}
}
}
}
</style>
\ No newline at end of file
<template>
<ul class="card-list" :style="{ marginTop: '10px' }">
<li class="art-custom-card" v-for="(item, index) in dataList" :key="index">
<span class="des subtitle">{{ item.des }}</span>
<CountTo class="number box-title" :endVal="item.num" :duration="1000" separator="" :decimals="2" :suffix="item.suffix"></CountTo>
<el-icon class="iconfont-sys"> <component :is="item.icon"/></el-icon>
</li>
</ul>
</template>
<script setup>
import { CountTo } from 'vue3-count-to'
import { getLoanKanBanTotal } from '@/api/dataScreen';
import { reactive, ref } from 'vue';
import { PictureRounded, Money, User, Star } from '@element-plus/icons-vue';
const props = defineProps({
tenant: [String, Number],
});
const numberFormat = (number) => {
if (!number || isNaN(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;
};
const dataList = reactive([
{
des: '案件总数',
icon: 'PictureRounded',
startVal: 0,
duration: 1000,
num: 0
},
{
des: '案件人数',
icon: 'User',
startVal: 0,
duration: 1000,
num: 0
},
{
des: '案件总额',
icon: 'Money',
startVal: 0,
duration: 1000,
num: 0
},
{
des: '回款金额',
icon: 'Money',
startVal: 0,
duration: 1000,
num: 0
},
{
des: '回款率',
icon: 'Star',
startVal: 0,
suffix: '%',
duration: 1000,
num: 0
}
])
const query = () => {
const params = {}
if (props.tenant) {
params['tenant'] = props.tenant
}
getLoanKanBanTotal(params).then((res) => {
if (res.success) {
dataList[0].num = res.result.caseNum
dataList[1].num = res.result.borrowerNum
dataList[2].num = res.result.amount
dataList[3].num = res.result.sumRepayTotal
dataList[4].num = res.result.rate
}
});
}
query();
</script>
<style lang="scss" scoped>
.card-list {
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
width: calc(100% + 20px);
margin-top: 0 !important;
margin-left: calc(0px - 20px);
background-color: transparent !important;
li {
position: relative;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
width: calc(20% - 20px);
height: 140px;
border: 1px solid rgba(219, 223, 233, .6) !important;
padding: 0 18px;
margin: 0 0 0 20px;
background: #ffffff;
$icon-size: 52px;
.iconfont-sys {
position: absolute;
top: 0;
right: 20px;
bottom: 0;
width: $icon-size;
height: $icon-size;
margin: auto;
overflow: hidden;
font-size: 22px;
line-height: $icon-size;
color: var(--el-color-primary) !important;
text-align: center;
background-color: var(--el-color-primary-light-9);
border-radius: 12px;
}
.des {
display: block;
height: 14px;
font-size: 14px;
line-height: 14px;
}
.number {
display: block;
margin-top: 10px;
font-size: 28px;
font-weight: 400;
}
}
}
.dark {
.card-list {
li {
.iconfont-sys {
background-color: #232323 !important;
}
}
}
}
</style>
\ No newline at end of file
<template>
<!-- 未来30天访问量趋势预测图 -->
<div class="region">
<div class="card-header">
<div class="title flex justify-between w-full">
<h4 class="box-title">作业量</h4>
<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>
</div>
</div>
<div class="echarts" style="height: calc(100% - 30px);">
<ECharts :option="option" :resize="false" />
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch, computed, onBeforeUnmount } from 'vue';
import { useDict } from '@/hooks/useDict';
import dayjs from 'dayjs';
import { getTractRecordThirtyDayTotal, getTractRecordDayHoursTotal } from '@/api/dataScreen';
import echarts from '@/components/ECharts/config';
import ECharts from '@/components/ECharts/index.vue';
const { FollowStatus } = useDict('FollowStatus');
const load = ref(false);
const dayActiveName = ref('日');
const props = defineProps({
tenant: [String, Number, Object],
});
const datas = ref([]);
const dates = ref([]);
const query = () => {
if (dayActiveName.value === '月') {
const param = {};
if (props.tenant) {
param['tenantId'] = props.tenant;
}
load.value = true;
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);
}
load.value = false;
})
.catch(() => {
datas.value = [];
dates.value = [];
});
} else {
const param = {};
if (props.tenant) {
param['tenant'] = props.tenant;
}
load.value = true;
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);
}
load.value = false;
})
.catch(() => {
datas.value = [];
dates.value = [];
});
}
};
query();
watch(
() => dayActiveName.value,
(newValue, oldValue) => {
query();
}
);
watch(
() => props.tenant,
(newValue, oldValue) => {
query();
}
);
const option = computed(() => {
return {
tooltip: {
trigger: 'axis',
confine: true,
hideDelay: 200, // 浮层隐藏的延迟
enterable: true,
formatter: (params) => {
let tipData = params[0];
let html = `<div style="height: auto;max-height: 170px;overflow-y: auto;">${tipData.name}${props.dayActiveName === '日' ? ':00' : ''} <i >${tipData.value}</i> 个<br/>`;
if (tipData.data && tipData.data.flowStatusTotalList.length > 0) {
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;
},
// backgroundColor: "transparent",
// borderColor: "transparent",
axisPointer: { lineStyle: { type: 'dashed' }, snap: true },
// extraCssText: "box-shadow: none;padding:0"
},
grid: {
left: '2.2%',
right: '3%',
bottom: '1%',
top: '10px',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
axisLabel: {
show: true,
color: '#999',
margin: 20,
// interval: 0,
fontSize: 13,
formatter: (v) => {
// console.log('v0', v)
if (v.length > 4) {
return v.slice(v.indexOf('.') + 1, v.length)
}
return v
}
},
axisLine: {
show: true,
lineStyle: {
color: '#E8E8E8',
width: 1
}
},
axisTick: {
show: false,
},
data: dates.value,
},
],
yAxis: {
name: '作业量',
axisLine: {
show: true,
lineStyle: {
color: '#E8E8E8',
width: 1
}
},
splitLine: {
show: true,
lineStyle: {
color: '#e8e8e8',
width: 1,
type: 'dashed'
}
},
axisLabel: {
show: true,
color: '#999',
fontSize: 13,
padding: 0,
formatter: function (value) {
if (Number(value) >= 10000) {
value = Number(value) / 10000 + 'w';
}
return value;
},
}
},
series: [
{
name: '',
type: 'line',
symbol: 'none',
smooth: true,
lineStyle: {
width: 2.6
},
color: '#5D87FF',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(93, 135, 255, .2)'
},
{
offset: 1,
color: 'rgba(93, 135, 255, .01)'
}
])
},
data: datas.value,
},
],
};
});
</script>
<style lang="scss" scoped>
.card-header {
padding: 0 18px !important;
}
.echarts {
width: 100%;
height: 100%;
:deep(.line-chart-bg) {
box-sizing: border-box;
display: flex;
align-items: center;
width: 180px;
height: 60px;
padding-left: 20px;
background: url('../images/line-bg.png') no-repeat;
background-size: 100% 100%;
span {
font-size: 12px;
color: rgb(255 255 255 / 80%);
i {
font-style: normal;
color: #f5b348;
}
}
}
}
.tabs {
border-radius: 5px;
border: 1px solid #6591f7;
color: #000;
cursor: pointer;
overflow: hidden;
.tab-item {
padding: 2px 20px;
border: 1px solid #6591f7;
&.active {
color: #fff;
background: #6591f7;
}
}
}
</style>
\ No newline at end of file
...@@ -4,9 +4,92 @@ ...@@ -4,9 +4,92 @@
justify-content: center; justify-content: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #f5f7fa;
.home-bg { .home-bg {
width: 70%; width: 70%;
max-width: 1200px; max-width: 1200px;
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
.console {
width: 100%;
height: 100%;
padding-bottom: 15px;
:deep(.card-header) {
display: flex;
justify-content: space-between;
padding: 20px 25px 5px 0;
.title {
h4 {
font-size: 18px;
font-weight: 500;
color: #252f4a;
}
p {
margin-top: 3px;
font-size: 13px;
span {
margin-left: 10px;
color: #52c41a;
}
}
}
}
// 主标题
:deep(.box-title) {
color: #071437 !important;
}
// 副标题
:deep(.subtitle) {
color: #78829d !important;
}
:deep(.card-list li),
.region,
.dynamic,
.bottom-wrap {
background: #fff;
border-radius: calc(0.75rem + 4px) !important;
}
.region {
height: 420px;
width: 50%;
padding-top: 10px;
border: 1px solid rgba(219, 223, 233, .6) !important;
}
.column {
display: flex;
justify-content: space-between;
margin-top: 20px;
background-color: transparent !important;
}
.bottom-wrap {
box-sizing: border-box;
display: flex;
justify-content: space-between;
height: 300px;
padding: 20px;
margin-top: 20px;
background: #fff;
h2 {
margin-top: 10px;
font-size: 20px;
font-weight: 500;
}
p {
margin-top: 5px;
font-size: 14px;
color: #78829d;
}
}
}
\ No newline at end of file
<template> <template>
<div class="home card"> <div class="home">
<img class="home-bg" src="@/assets/images/welcome.png" alt="welcome" /> <div class="console">
<CardList :tenant="tenant"></CardList>
<div class="column column2">
<ActiveUser :tenant="tenant"></ActiveUser>
<SalesOverview :tenant="tenant"></SalesOverview>
</div>
</div>
</div> </div>
</template> </template>
<script setup name="home"></script> <script setup name="home">
import { useUserStore } from '@/stores/modules/user';
import { reactive, ref } from 'vue';
import CardList from './components/CardList.vue'
import ActiveUser from './components/ActiveUser.vue'
import SalesOverview from './components/SalesOverview.vue'
const isAdmin = ref(true);
const tenant = ref(null);
const userStore = useUserStore();
let tenants = '';
if (userStore.userInfo?.tenants && userStore.userInfo?.tenants.length > 0) {
if (userStore.userInfo?.tenants.length < 2) {
tenants = userStore.userInfo?.tenants[0]
} else {
tenants = userStore.tenant
}
}
if (userStore.userInfo?.roles && userStore.userInfo?.roles.find(i => i.roleCode === 'tenantAdmin')) {
tenant.value = tenants.id
}
console.log('userStore', userStore.userInfo, userStore.tenant)
</script>
<style scoped lang="scss"> <style scoped lang="scss">
@use './index.scss'; @use './index.scss';
</style> </style>
\ No newline at end of file
...@@ -113,13 +113,20 @@ ...@@ -113,13 +113,20 @@
/> />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="11"> <el-col :span="9">
<el-form-item class="w-full" label="折扣:" prop="discount"> <el-form-item class="w-full" label="完成率:" prop="completionRate" label-width="65px">
<el-input v-model="form.completionRate" style="width: 100%" type="number">
<template #append></template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item class="w-full" label="折扣:" prop="discount" label-width="55px">
<el-input v-model="form.discount" placeholder="请输入" style="width: 100%" /> <el-input v-model="form.discount" placeholder="请输入" style="width: 100%" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="13"> <el-col :span="8">
<el-form-item class="w-full" label="分期最大期数:" prop="maxStagesNum"> <el-form-item class="w-full" label="分期最大期数:" prop="maxStagesNum" label-width="105px">
<el-input <el-input
v-model.number="form.maxStagesNum" v-model.number="form.maxStagesNum"
placeholder="请输入" placeholder="请输入"
...@@ -254,6 +261,12 @@ ...@@ -254,6 +261,12 @@
showOverflow: 'tooltip', showOverflow: 'tooltip',
search: { el: 'input', props: { clearable: true } }, search: { el: 'input', props: { clearable: true } },
}, },
{
field: 'completionRate',
title: '完成率',
width: 120,
showOverflow: 'tooltip'
},
{ {
field: 'mergerCase', field: 'mergerCase',
title: '是否共案处理', title: '是否共案处理',
...@@ -350,6 +363,7 @@ ...@@ -350,6 +363,7 @@
path: '', path: '',
code: '', code: '',
maxStagesNum: '', maxStagesNum: '',
completionRate: '',
canStages: 'Y', canStages: 'Y',
minAmount: '', minAmount: '',
mergerCase: 'Y', mergerCase: 'Y',
...@@ -438,6 +452,7 @@ ...@@ -438,6 +452,7 @@
file: form.path, file: form.path,
code: form.code, code: form.code,
maxStagesNum: form.maxStagesNum, maxStagesNum: form.maxStagesNum,
completionRate: form.completionRate,
canStages: form.canStages, canStages: form.canStages,
minAmount: form.minAmount, minAmount: form.minAmount,
mergerCase: form.mergerCase, mergerCase: form.mergerCase,
...@@ -453,6 +468,7 @@ ...@@ -453,6 +468,7 @@
form['path'] = ''; form['path'] = '';
form['code'] = ''; form['code'] = '';
form['maxStagesNum'] = ''; form['maxStagesNum'] = '';
form['completionRate'] = '';
form['minAmount'] = ''; form['minAmount'] = '';
form['mergerCase'] = 'Y'; form['mergerCase'] = 'Y';
form['canStages'] = 'Y'; form['canStages'] = 'Y';
......
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