同步代码
This commit is contained in:
150
pages/statPage/components/StatInfoCard.vue
Normal file
150
pages/statPage/components/StatInfoCard.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<view class="info-wrapper">
|
||||
<view class="dev-title" v-if="dev" @tap="devType.open(vdata.devTitle.value)">
|
||||
{{ vdata.devTitle.label }}
|
||||
<image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
<view class="info-title" :class="{ 'fixed-header': fixed }">
|
||||
<block v-for="(v, i) in vdata.titleList" :key="i">
|
||||
<view class="info-item" :class="{ 'active-title': vdata.selected == v.value }" @tap="selectedTitle(v)">
|
||||
{{ v.title }}
|
||||
<view class="stat-sort" :class="[calcClassName(v)]"></view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
<!-- 默认插槽位置 -->
|
||||
<slot />
|
||||
</view>
|
||||
<JSinglePopup :list="devTypeList" ref="devType" @confirm="confirmType" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
const props = defineProps({
|
||||
dev: { type: Boolean, default: false }, //是否展示设备类型切换标题
|
||||
fixed: { type: Boolean, default: false } //是否固定头部 默认 false
|
||||
});
|
||||
const emits = defineEmits(['device', 'sortClick']);
|
||||
const vdata = reactive({
|
||||
selected: 'totalSuccAmt',
|
||||
devTitle: {
|
||||
label: '全部设备类型',
|
||||
value: ''
|
||||
},
|
||||
titleList: [
|
||||
{ title: '成交金额', value: 'totalSuccAmt', sort: 'ascend' },
|
||||
{ title: '退款金额', value: 'totalRefundAmt', sort: 'ascend' },
|
||||
{ title: '成交笔数', value: 'totalSuccNum', sort: 'ascend' },
|
||||
{ title: '成功率', value: 'succRate', sort: 'ascend' }
|
||||
]
|
||||
});
|
||||
const devType = ref(null);
|
||||
const selectedTitle = (val) => {
|
||||
if (vdata.selected != val.value) {
|
||||
vdata.selected = val.value;
|
||||
emits('sortClick', val);
|
||||
return;
|
||||
}
|
||||
val.sort = val.sort == 'ascend' ? 'descend' : 'ascend';
|
||||
emits('sortClick', val);
|
||||
};
|
||||
const devTypeList = reactive([
|
||||
{ label: '全部', value: '' },
|
||||
{ label: '电子码牌', value: '0' },
|
||||
{ label: '实体码牌', value: '1' },
|
||||
{ label: '实体立牌', value: '2' },
|
||||
{ label: '云音响码牌', value: '3' },
|
||||
{ label: '扫码POS', value: 'scan_pos"' },
|
||||
{ label: '智能POS', value: 'auto_pos"' },
|
||||
{ label: '收银插件', value: 'cash_plugin' }
|
||||
]);
|
||||
// 计算需要展示的上下箭头
|
||||
const calcClassName = (val) => {
|
||||
// 判断 选中 与 集合中的 value 值是否相等 不相等 则不添加类名
|
||||
if (vdata.selected == val.value) {
|
||||
// 返回上箭头 下箭头类名
|
||||
return val.sort == 'descend' ? 'asc-sort' : 'des-sort';
|
||||
}
|
||||
return '';
|
||||
};
|
||||
const confirmType = (val) => {
|
||||
vdata.devTitle = val;
|
||||
emits('device', val.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-wrapper {
|
||||
margin: 0 35rpx 40rpx 35rpx;
|
||||
border-radius: $J-b-r32;
|
||||
background-color: $J-bg-ff;
|
||||
.dev-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 30rpx;
|
||||
height: 100rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
image {
|
||||
margin-left: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.info-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 102rpx;
|
||||
font-size: 24rpx;
|
||||
color: $J-color-t80;
|
||||
background-color: #fff;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
.active-title {
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
border-bottom: 5rpx solid #2980fd;
|
||||
}
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
.stat-sort {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: 10rpx;
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border: 8rpx solid;
|
||||
border-color: #d9d9d9 transparent transparent transparent;
|
||||
}
|
||||
&::before {
|
||||
margin-bottom: 4rpx;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
.asc-sort::before {
|
||||
border-color: #2980fd transparent transparent transparent;
|
||||
}
|
||||
.des-sort::after {
|
||||
border-color: #2980fd transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.fixed-header {
|
||||
position: sticky;
|
||||
top: 112rpx;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
</style>
|
||||
50
pages/statPage/components/StoreCell.vue
Normal file
50
pages/statPage/components/StoreCell.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<view class="store-wrapper">
|
||||
<view class="store-left">
|
||||
<image :src="storeImg" mode="scaleToFill" />
|
||||
{{ storeName }}
|
||||
</view>
|
||||
<view class="store-right">
|
||||
<text>¥{{ storeNum }}</text>
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
storeName: { type: String }, //门店名称
|
||||
storeImg: { type: String }, //门店照片
|
||||
storeNum: { type: Number }, //收款金额
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.store-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 30rpx;
|
||||
height: 100rpx;
|
||||
.store-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
image {
|
||||
margin-right: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
.store-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
text {
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
113
pages/statPage/components/payInfoPopup.vue
Normal file
113
pages/statPage/components/payInfoPopup.vue
Normal file
@@ -0,0 +1,113 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change="change" :safe-area="false">
|
||||
<view class="info-wrapper">
|
||||
<view class="info-title">
|
||||
<text>支付方式统计明细</text>
|
||||
<view class="icon-close" @tap="popup.close()"><image src="/static/iconImg/icon-x.svg" mode="scaleToFill" /></view>
|
||||
</view>
|
||||
<StatInfoCard @sortClick="switchField" :fixed="true">
|
||||
<JTableList ref="refTable" :reqTableDataFunc="reqTableDataFunc" :initData="false" height="220rpx" :searchData="vdata.params">
|
||||
<template #tableBody="{ record }">
|
||||
<statCell
|
||||
:imgNone="false"
|
||||
:title="record.wayCode"
|
||||
:money="calcData(record[vdata.cardSelected])"
|
||||
:reality="calcReality(record.totalSuccNum, record.totalNum)"
|
||||
:round="vdata.cardSelected == 'round'"
|
||||
:iconPre="vdata.cardSelected == 'totalFinalAmt' || vdata.cardSelected == 'totalRefundAmt '"
|
||||
:iconNext="vdata.cardSelected == 'round'"
|
||||
/>
|
||||
</template>
|
||||
</JTableList>
|
||||
</StatInfoCard>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { nextTick, onMounted, reactive, ref, inject } from 'vue';
|
||||
import { onReachBottom } from '@dcloudio/uni-app';
|
||||
import { $getStatistic } from '@/http/apiManager.js';
|
||||
import StatInfoCard from './StatInfoCard.vue';
|
||||
import statCell from './statCell.vue';
|
||||
import cal from '@/commons/utils/cal.js';
|
||||
// 获取弹窗实例
|
||||
const popup = ref(null);
|
||||
const refTable = ref(null);
|
||||
const vdata = reactive({
|
||||
cardSelected: 'totalFinalAmt',
|
||||
record: {},
|
||||
params: {}
|
||||
});
|
||||
const open = (params, record) => {
|
||||
const obj = JSON.parse(JSON.stringify(params));
|
||||
vdata.record = record;
|
||||
obj.wayCodeType = record.wayType;
|
||||
obj.method = 'wayCode';
|
||||
vdata.params = obj;
|
||||
popup.value.open();
|
||||
nextTick(() => {
|
||||
refTable.value.refTable(true);
|
||||
});
|
||||
};
|
||||
const reqTableDataFunc = (params) => {
|
||||
return $getStatistic(params);
|
||||
};
|
||||
//切换字段 切换排序
|
||||
const switchField = (val) => {
|
||||
vdata.cardSelected = val.value;
|
||||
vdata.params.sortOrder = val.sort;
|
||||
vdata.params.sortField = val.value;
|
||||
refTable.value.refTable(true);
|
||||
};
|
||||
// 计算数据需不需要除以 100
|
||||
const calcData = (val) => {
|
||||
if (vdata.cardSelected == 'totalFinalAmt' || vdata.cardSelected == 'totalRefundAmt') return cal.cert2Dollar(val);
|
||||
return val;
|
||||
};
|
||||
// 计算进度条
|
||||
const calcReality = (val, totalNum) => {
|
||||
return (val / totalNum) * 100;
|
||||
};
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show);
|
||||
}
|
||||
};
|
||||
onReachBottom(() => {});
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-wrapper {
|
||||
height: 70vh;
|
||||
overflow-y: scroll;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
.info-title {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 110rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
.icon-close {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f2f2f2;
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
pages/statPage/components/statCell.vue
Normal file
98
pages/statPage/components/statCell.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<view class="pay-wrapper">
|
||||
<view class="pay-left">
|
||||
<template v-if="imgNone">
|
||||
<image v-if="devImage" :src="imgObj[devImage]" mode="scaleToFill" />
|
||||
<image v-else :src="imgUrl" mode="scaleToFill" />
|
||||
</template>
|
||||
<view>{{ imgUrl }}</view>
|
||||
<view class="pay-title single-text-beyond">{{ title }}</view>
|
||||
</view>
|
||||
<view class="pro-bar" :style="{ '--bar-width': reality + '%' }" v-if="round"></view>
|
||||
<view class="pay-right">
|
||||
<text v-show="iconPre">¥</text>
|
||||
{{ money }}
|
||||
<text v-show="iconNext">%</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
reality: [String, Number], //实际进度
|
||||
imgUrl: { type: String }, //左侧图片
|
||||
title: { type: String }, //左侧标题
|
||||
money: [String, Number], //右侧金额
|
||||
round: { type: Boolean, default: false }, //是否展示进度条
|
||||
iconPre: { type: Boolean, default: false }, //金钱
|
||||
iconNext: { type: Boolean, default: false }, // 百分比
|
||||
devImage: { type: String }, //如果设备
|
||||
imgNone: { type: Boolean, default: true } //不需要图片
|
||||
});
|
||||
const imgObj = reactive({
|
||||
store: '/static/indexImg/icon-store.svg', //门店
|
||||
qr_code: '/static/devIconImg/icon-code.svg',
|
||||
scan_pos: '/static/devIconImg/icon-pos.svg',
|
||||
auto_pos: '/static/devIconImg/icon-scanPos.svg',
|
||||
cash_plugin: '/static/devIconImg/icon-horn.svg',
|
||||
face_app: '/static/devIconImg/icon-face-1.svg', //刷脸设备
|
||||
ALIPAY: '/static/payImg/icon-zfb.svg', //支付宝
|
||||
OTHER: '/static/payImg/icon-qt.svg', //其他
|
||||
DCEPPAY: '/static/payImg/icon-qt.svg', //数字人民币
|
||||
UNIONPAY: '/static/payImg/icon-yl.svg', //银联
|
||||
WECHAT: '/static/payImg/icon-wx.svg', //微信
|
||||
YSFPAY: '/static/payImg/icon-ysf.svg' //云闪付
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pay-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 30rpx;
|
||||
height: 100rpx;
|
||||
.pay-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
image {
|
||||
margin-right: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
.pay-title {
|
||||
width: 178rpx;
|
||||
}
|
||||
}
|
||||
.pro-bar {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
width: 100rpx;
|
||||
height: 6rpx;
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
border-radius: 6rpx;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: var(--bar-width);
|
||||
height: 100%;
|
||||
border-radius: 6rpx;
|
||||
background: $jeepay-bg-primary;
|
||||
}
|
||||
}
|
||||
.pay-right {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
203
pages/statPage/components/statInfoPopup.vue
Normal file
203
pages/statPage/components/statInfoPopup.vue
Normal file
@@ -0,0 +1,203 @@
|
||||
<template>
|
||||
<uni-popup ref="popup" type="bottom" mask-background-color="rgba(0,0,0,.5)" @change="change" :safe-area="false">
|
||||
<view class="info-wrapper">
|
||||
<view class="info-title">
|
||||
<!-- <image :src="imgObj[vdata.record.deviceType]" mode="scaleToFill" /> -->
|
||||
<view class="info-content">
|
||||
<view class="info-name">
|
||||
<text>{{ vdata.record?.storeName || vdata.record?.deviceName }}</text>
|
||||
<view class="icon-close" @tap="popup.close()">
|
||||
<image src="/static/iconImg/icon-x.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="sub-name">{{ vdata.record?.storeId || vdata.record?.deviceNo }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="stat-card">
|
||||
<view class="stat-money">
|
||||
<view class="stat-title">入账金额 (元)</view>
|
||||
<view class="stat-num-title">{{ cal.cert2Dollar(vdata.totalSuccAmt - vdata.totalFeeAmt - vdata.totalRefundAmt) }}</view>
|
||||
</view>
|
||||
<view class="stat-card-info">
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">成交订单金额 (元)</view>
|
||||
<view class="stat-num">{{ cal.cert2Dollar(vdata.totalSuccAmt) }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">成交订单笔数</view>
|
||||
<view class="stat-num">{{ vdata.totalSuccNum }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">交易成功率</view>
|
||||
<view class="stat-num">{{ vdata.succRate }}%</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">交易手续费</view>
|
||||
<view class="stat-num">{{ cal.cert2Dollar(vdata.totalFeeAmt) }}</view>
|
||||
</view>
|
||||
<!-- <view class="stat-item">
|
||||
<view class="stat-title">垫资手续费</view>
|
||||
<view class="stat-num">---</view>
|
||||
</view> -->
|
||||
<!-- <view class="stat-item">
|
||||
<view class="stat-title">优惠金额</view>
|
||||
<view class="stat-num">{{ cal.cert2Dollar(vdata.totalDiscountAmt) }}%</view>
|
||||
</view> -->
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">退款金额 (元)</view>
|
||||
<view class="stat-num">{{ cal.cert2Dollar(vdata.totalRefundAmt) }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">退款笔数</view>
|
||||
<view class="stat-num">{{ vdata.totalRefundNum }}</view>
|
||||
</view>
|
||||
<!-- <view class="stat-item">
|
||||
<view class="stat-title">补贴金额</view>
|
||||
<view class="stat-num">---</view>
|
||||
</view> -->
|
||||
<!-- <view class="stat-item">
|
||||
<view class="stat-title">交易成功笔数</view>
|
||||
<view class="stat-num">{{ vdata.totalSuccNum }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">成功交易金额 (元)</view>
|
||||
<view class="stat-num">{{ cal.cert2Dollar(vdata.totalSuccAmt) }}</view>
|
||||
</view>
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">总交易笔数</view>
|
||||
<view class="stat-num">{{ vdata.totalNum }}</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, inject } from 'vue';
|
||||
import { $getStatInfo, $getStatistic } from '@/http/apiManager.js';
|
||||
import cal from '@/commons/utils/cal.js';
|
||||
|
||||
// 获取弹窗实例
|
||||
const popup = ref(null);
|
||||
const vdata = reactive({
|
||||
record: {}
|
||||
});
|
||||
const imgObj = reactive({
|
||||
store: '/static/indexImg/icon-store.svg',
|
||||
qr_code: '/pageDevice/static/devIconImg/icon-code.svg',
|
||||
scan_pos: '/pageDevice/static/devIconImg/icon-pos.svg',
|
||||
auto_pos: '/pageDevice/static/devIconImg/icon-scanPos.svg',
|
||||
cash_plugin: '/pageDevice/static/devIconImg/icon-horn.svg'
|
||||
});
|
||||
const getInfo = (params) => {
|
||||
$getStatInfo(params).then(({ bizData }) => {
|
||||
Object.assign(vdata, bizData);
|
||||
});
|
||||
};
|
||||
const open = (params, record) => {
|
||||
const obj = JSON.parse(JSON.stringify(params));
|
||||
vdata.record = record;
|
||||
if (!vdata.record.deviceType) vdata.record.deviceType = 'store';
|
||||
obj.deviceNo = record.deviceNo ? record.deviceNo : '';
|
||||
obj.storeId = record.storeId ? record.storeId : '';
|
||||
getInfo(obj);
|
||||
popup.value.open();
|
||||
};
|
||||
let changePageMetaOverflowFunc = inject('changePageMetaOverflowFunc');
|
||||
const change = (e) => {
|
||||
if (changePageMetaOverflowFunc) {
|
||||
changePageMetaOverflowFunc(!e.show);
|
||||
}
|
||||
};
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.info-wrapper {
|
||||
// height: 777rpx;
|
||||
border-radius: 32rpx 32rpx 0 0;
|
||||
background-color: #fff;
|
||||
padding-bottom: 32upx;
|
||||
}
|
||||
.info-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 40rpx;
|
||||
height: 160rpx;
|
||||
border-bottom: 1rpx solid #ededed;
|
||||
image {
|
||||
margin-right: 20rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
.info-content {
|
||||
flex: 1;
|
||||
.info-name {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
.icon-close {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border-radius: 10rpx;
|
||||
background-color: #f2f2f2;
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.sub-name {
|
||||
font-size: 26rpx;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
}
|
||||
.stat-card {
|
||||
position: relative;
|
||||
margin: 0 30rpx;
|
||||
background-color: $J-bg-ff;
|
||||
border-radius: $J-b-r32;
|
||||
.stat-title {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
.stat-money {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 202rpx;
|
||||
box-shadow: 0 50rpx 70rpx -60rpx rgba(107, 130, 153, 0.2);
|
||||
.stat-num-title {
|
||||
font-size: 60rpx;
|
||||
font-weight: 700;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
.stat-card-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 68rpx 50rpx 68rpx;
|
||||
.stat-item {
|
||||
width: 175rpx;
|
||||
margin-top: 50rpx;
|
||||
text-align: center;
|
||||
.stat-num {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
.stat-tips {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
522
pages/statPage/statPage.vue
Normal file
522
pages/statPage/statPage.vue
Normal file
@@ -0,0 +1,522 @@
|
||||
<template>
|
||||
<JeepayBackground :bgColorStyle="{ background: 'linear-gradient(270deg,#238FFC 0%, #1A66FF 100%)' }">
|
||||
<view>
|
||||
<JeepayCustomNavbar title="统计报表" textColor="#fff" bgDefaultColor="linear-gradient(270deg,#238FFC 0%, #1A66FF 100%)" backCtrl="back" />
|
||||
<!-- 统计时间选择部分 -->
|
||||
<view class="screen-time">
|
||||
<block v-for="(v, i) in screenList" :key="i">
|
||||
<view class="time-main" :class="{ 'active-screen': vdata.selected == v.value }" @tap="selectedTime(v)">{{ v.title }}</view>
|
||||
</block>
|
||||
</view>
|
||||
<template v-if="vdata.selected == 'custom'">
|
||||
<view class="time-selection time-custom">
|
||||
<image src="/static/iconImg/icon-calendar.svg" mode="scaleToFill" />
|
||||
<view class="selection-main" @tap="startTime.show()">{{ vdata.startTime || '请选择开始时间' }}</view>
|
||||
<view style="color: #fff">---</view>
|
||||
<view class="selection-main" @tap="endTime.show()">{{ vdata.endTime || '请选择结束时间' }}</view>
|
||||
<image src="/static/iconImg/icon-arrow-downWhite.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
</template>
|
||||
<view class="time-selection flex-center" v-else @tap="timeSelected.show()">
|
||||
<image src="/static/iconImg/icon-calendar.svg" mode="scaleToFill" />
|
||||
<view class="selection-main">
|
||||
{{ vdata.time }}
|
||||
<text v-if="isToDay()">今天</text>
|
||||
</view>
|
||||
<image src="/static/iconImg/icon-arrow-downWhite.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
<!-- 统计卡片部分 -->
|
||||
<view class="stat-card" @click="go.to('PAGES_PAY_ORDER', {}, 'switchTab')">
|
||||
<view class="stat-money">
|
||||
<view class="stat-title">入账金额 (元)</view>
|
||||
<view class="stat-num-title">{{ vdata.totalFinalAmt }}</view>
|
||||
<view class="stat-tips"><uni-icons type="help-filled" size="20" color="#c6c6c6" @tap.stop="openTips()" /></view>
|
||||
</view>
|
||||
<view class="stat-card-info">
|
||||
<block v-for="(v, i) in vdata.infoList" :key="i">
|
||||
<view class="stat-item">
|
||||
<view class="stat-title">{{ v.title }}</view>
|
||||
<view class="stat-num" v-if="v.value == 'totalSuccAmt' || v.value == 'totalFeeAmt' || v.value == 'totalRefundAmt'">
|
||||
{{ cal.cert2Dollar(v.text) }}
|
||||
</view>
|
||||
<view class="stat-num" v-else>
|
||||
<template v-if="v.value == 'succRate'">{{ v.text }}%</template>
|
||||
<template v-else>{{ v.text }}</template>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 统计选择门店等方式部分 -->
|
||||
<view class="stat-selection">
|
||||
<block v-for="v in vdata.statList" :key="v.value">
|
||||
<view class="stat-item" :class="{ 'active-stat': vdata.statSelected == v.value }" @tap="switchStatType(v.value)">{{ v.title }}</view>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<StatInfoCard :dev="vdata.statSelected == 'device'" @sortClick="switchField" @device="switchDevType">
|
||||
<JeepayTableList ref="refTable" :reqTableDataFunc="reqTableDataFunc" :searchData="params" :showListNull="false">
|
||||
<template #tableBody="{ record }">
|
||||
<statCell
|
||||
:reality="record.succRate"
|
||||
:title="record[vdata.namObj[vdata.statSelected]]"
|
||||
:money="money(record)"
|
||||
round
|
||||
:iconPre="vdata.cardSelected == 'totalSuccAmt' || vdata.cardSelected == 'totalRefundAmt'"
|
||||
:iconNext="vdata.cardSelected == 'succRate'"
|
||||
:devImage="vdata.statSelected == 'wayCodeType' ? record.wayType : vdata.statSelected == 'store' ? 'store' : record.deviceType"
|
||||
@tap="showDetail(params, record)"
|
||||
></statCell>
|
||||
</template>
|
||||
</JeepayTableList>
|
||||
</StatInfoCard>
|
||||
<!-- 占位 -->
|
||||
<view class="store-block"></view>
|
||||
<!-- 选择门店按钮 -->
|
||||
<view class="store-name" @tap="selectedStore">
|
||||
{{ vdata.store?.storeName }}
|
||||
<image src="/static/iconImg/icon-arrow-black.svg" mode="scaleToFill" />
|
||||
</view>
|
||||
<JeepayBizinfoSelect :isShowAllBiz="true" ref="jeepayPopupListSelect" />
|
||||
<JTipsPopupContent ref="tips" title="统计释义" buttonText="我知道了" @cancel="vdata.flag = true">
|
||||
<block v-for="(v, i) in tipsList" :key="i">
|
||||
<view class="tips-wrapper">
|
||||
<view class="tips-title">{{ v.title }}</view>
|
||||
<view class="tips-info">{{ v.info }}</view>
|
||||
</view>
|
||||
</block>
|
||||
</JTipsPopupContent>
|
||||
<xp-picker @confirm="dayConfirm" ref="timeSelected" mode="ymd" v-if="vdata.selected == 'date'" />
|
||||
<xp-picker @confirm="dayConfirm" ref="timeSelected" mode="ym" v-if="vdata.selected == 'month'" />
|
||||
<xp-picker @confirm="dayConfirm" ref="timeSelected" mode="y" v-if="vdata.selected == 'year'" />
|
||||
<xp-picker ref="startTime" mode="ymd" @confirm="customTime($event, 'startTime')" />
|
||||
<xp-picker ref="endTime" mode="ymd" @confirm="customTime($event, 'endTime')" />
|
||||
<!-- 选择门店 -->
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<statInfoPopup ref="infoPopup" />
|
||||
<payInfoPopup ref="payInfo" />
|
||||
</view>
|
||||
</JeepayBackground>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, getCurrentInstance, nextTick, computed } from 'vue';
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
|
||||
import { startAndEndTime } from '@/commons/utils/timeInspect';
|
||||
import dayjs from 'dayjs';
|
||||
import { req, reqLoad, $getStatInfo, $getStatistic, API_URL_MCH_STORE_LIST } from '@/http/apiManager.js';
|
||||
import StatInfoCard from './components/StatInfoCard.vue';
|
||||
import statInfoPopup from './components/statInfoPopup.vue';
|
||||
import payInfoPopup from './components/payInfoPopup.vue';
|
||||
// import StoreCell from './components/StoreCell.vue'
|
||||
import statCell from './components/statCell.vue';
|
||||
|
||||
import cal from '@/commons/utils/cal.js';
|
||||
import go from '@/commons/utils/go.js';
|
||||
|
||||
// 提示框
|
||||
const tips = ref(null);
|
||||
// 时间选怎器
|
||||
const timeSelected = ref(null);
|
||||
const startTime = ref(null);
|
||||
const endTime = ref(null);
|
||||
// 获取表格实例
|
||||
const refTable = ref(null);
|
||||
// 获取门店实例
|
||||
const jeepayPopupListSelect = ref(null);
|
||||
const infoPopup = ref(null);
|
||||
const payInfo = ref(null);
|
||||
const vdata = reactive({
|
||||
selected: 'date',
|
||||
statSelected: 'store',
|
||||
totalSuccNum: '0.00',
|
||||
originalData: {}, //原始数据 计算进度条
|
||||
infoList: [
|
||||
{ title: '退款金额 (元)', money: '13.15', value: 'totalRefundAmt' },
|
||||
{ title: '退款笔数', money: '13.15', value: 'totalRefundNum' },
|
||||
{ title: '成交订单笔数', money: '13.15', value: 'totalSuccNum' },
|
||||
{ title: '成交订单金额 (元)', money: '6365876.15', value: 'totalSuccAmt' },
|
||||
{ title: '手续费(元)', money: '13.15', value: 'totalFeeAmt' },
|
||||
{ title: '交易成功率', money: '98.25%', value: 'succRate' }
|
||||
],
|
||||
statList: [
|
||||
{ title: '门店统计', value: 'store' },
|
||||
{ title: '支付类型统计', value: 'wayCodeType' },
|
||||
{ title: '设备统计', value: 'device' }
|
||||
],
|
||||
|
||||
namObj: {
|
||||
store: 'storeName',
|
||||
device: 'deviceName',
|
||||
wayCodeType: 'name'
|
||||
},
|
||||
cardSelected: 'totalSuccAmt',
|
||||
store: {
|
||||
storeId: '',
|
||||
storeName: '全部门店'
|
||||
}
|
||||
});
|
||||
const screenList = reactive([
|
||||
{ title: '日报', value: 'date' },
|
||||
{ title: '月报', value: 'month' },
|
||||
{ title: '年报', value: 'year' },
|
||||
{ title: '自定义', value: 'custom' }
|
||||
]);
|
||||
const timeFormat = {
|
||||
date: 'YYYY/MM/DD',
|
||||
month: 'YYYY/MM',
|
||||
year: 'YYYY'
|
||||
};
|
||||
const tipsList = reactive([
|
||||
{ title: '入账金额:', info: '扣除已退款金额及手续费,入账金额为商户实际到账金额。' },
|
||||
{ title: '退款笔数:', info: '实际退款订单笔数,若一笔已成交订单退款多次,则计多次。' },
|
||||
{ title: '成交订单金额:', info: '支付成功的订单金额,包含部分退款及全额退款的订单。' },
|
||||
{ title: '成交订单笔数:', info: '支付成功的订单笔数,包含已全额退款的订单。' },
|
||||
{ title: '交易成功率:', info: '成交笔数与全部订单笔数除得的百分比。' }
|
||||
]);
|
||||
// 选择年月日
|
||||
let orgSelectedTime = undefined;
|
||||
const selectedTime = (val) => {
|
||||
vdata.selected = val.value;
|
||||
if (val.value != 'custom') orgSelectedTime = val.value;
|
||||
if (val.value == 'custom') {
|
||||
vdata.startTime = dayjs(vdata.time).startOf(orgSelectedTime).format(timeFormat['date']);
|
||||
vdata.endTime = dayjs(vdata.time).endOf(orgSelectedTime).format(timeFormat['date']);
|
||||
return;
|
||||
}
|
||||
dayConfirm({ value: dayjs(new Date()).format(timeFormat[vdata.selected]) });
|
||||
};
|
||||
const params = {
|
||||
method: 'store',
|
||||
queryDateRange: '',
|
||||
sortOrder: 'ascend', //排序默认正序
|
||||
sortField: 'totalSuccAmt' //默认字段
|
||||
};
|
||||
// 日报时间选择器确认
|
||||
const dayConfirm = (e) => {
|
||||
vdata.time = dayjs(e.value).format(timeFormat[vdata.selected]);
|
||||
params.queryDateRange = `customDateTime_${dayjs(e.value).startOf('date').format('YYYY-MM-DD HH:mm:ss')}_${dayjs(e.value).endOf(vdata.selected).format('YYYY-MM-DD HH:mm:ss')}`;
|
||||
nextTick(() => {
|
||||
getStatList();
|
||||
refTable.value.refTable(true);
|
||||
// refTable.value.refTable(true);
|
||||
});
|
||||
};
|
||||
// 自定义时间选择器
|
||||
const customTime = (e, val) => {
|
||||
vdata[val] = dayjs(e.value).format('YYYY/MM/DD');
|
||||
if (vdata.endTime) {
|
||||
if (!startAndEndTime(vdata.startTime, vdata.endTime)) return;
|
||||
params.queryDateRange = `customDateTime_${dayjs(vdata.startTime).startOf('date').format('YYYY-MM-DD HH:mm:ss')}_${dayjs(vdata.endTime)
|
||||
.endOf('date')
|
||||
.format('YYYY-MM-DD HH:mm:ss')}`;
|
||||
getStatList();
|
||||
refTable.value.refTable(true);
|
||||
// refTable.value.refTable(true);
|
||||
}
|
||||
};
|
||||
const getStatList = () => {
|
||||
$getStatInfo(params).then(({ bizData }) => {
|
||||
vdata.originalData = JSON.parse(JSON.stringify(bizData)); //储存原始数据
|
||||
// bizData.totalRefundAmt = cal.cert2Dollar(bizData.totalRefundAmt);
|
||||
bizData.allAmount = cal.cert2Dollar(bizData.allAmount);
|
||||
bizData.fee = cal.cert2Dollar(bizData.fee);
|
||||
vdata.totalFinalAmt =
|
||||
cal.cert2Dollar(bizData.totalSuccAmt - bizData.totalFeeAmt - bizData.totalRefundAmt) < 0
|
||||
? '0.00'
|
||||
: cal.cert2Dollar(bizData.totalSuccAmt - bizData.totalFeeAmt - bizData.totalRefundAmt);
|
||||
bizData.round = bizData.round + '%';
|
||||
vdata.infoList.forEach((v) => {
|
||||
v.text = bizData[v.value];
|
||||
});
|
||||
});
|
||||
};
|
||||
//切换字段 切换排序
|
||||
const switchField = (val) => {
|
||||
vdata.cardSelected = val.value;
|
||||
params.sortOrder = val.sort;
|
||||
params.sortField = val.value;
|
||||
refTable.value.refTable(true);
|
||||
// refTable.value.refTable(true);
|
||||
};
|
||||
const reqTableDataFunc = (params) => {
|
||||
return $getStatistic(params);
|
||||
};
|
||||
const openTips = () => {
|
||||
tips.value.open();
|
||||
};
|
||||
// 判断是不是当天
|
||||
const isToDay = () => {
|
||||
return dayjs(vdata.time).format('YYYY/MM/DD') == dayjs(new Date()).format('YYYY/MM/DD');
|
||||
};
|
||||
// 计算进度条
|
||||
const calcReality = (val, totalNum) => {
|
||||
return (val / totalNum) * 100;
|
||||
};
|
||||
|
||||
// 计算数据需不需要除以 100
|
||||
const calcData = (val) => {
|
||||
if (vdata.cardSelected == 'totalSuccNum' || vdata.cardSelected == 'totalRefundAmt') return cal.cert2Dollar(val);
|
||||
return val;
|
||||
};
|
||||
const switchStatType = (val) => {
|
||||
vdata.statSelected = val;
|
||||
params.method = val;
|
||||
nextTick(() => {
|
||||
refTable.value.refTable(true);
|
||||
// refTable.value.refTable(true);
|
||||
// getStatList();
|
||||
});
|
||||
};
|
||||
|
||||
// 显示列表明细
|
||||
function showDetail(params, record) {
|
||||
if (vdata.statSelected == 'store') {
|
||||
// payInfo.value.open(params, record);
|
||||
infoPopup.value.open(params, record);
|
||||
} else if (vdata.statSelected == 'device') {
|
||||
infoPopup.value.open(params, record);
|
||||
}
|
||||
}
|
||||
|
||||
// 切换设备类型
|
||||
const switchDevType = (val) => {
|
||||
params.deviceType = val;
|
||||
refTable.value.refTable(true);
|
||||
refTable.value.refTable(true);
|
||||
};
|
||||
// 获取门店数据
|
||||
function reqTableDataByStoreFunc(params) {
|
||||
return reqLoad.list(API_URL_MCH_STORE_LIST, params);
|
||||
}
|
||||
// 选择门店
|
||||
const selectedStore = () => {
|
||||
jeepayPopupListSelect.value.open(vdata.store).then((selected) => {
|
||||
if (selected) {
|
||||
vdata.store = selected;
|
||||
params.storeId = selected.storeId;
|
||||
getStatList();
|
||||
refTable.value.refTable(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 根据类型计算值
|
||||
const money = (record) => {
|
||||
if (vdata.cardSelected == 'totalSuccAmt') {
|
||||
return cal.cert2Dollar(record.totalSuccAmt);
|
||||
} else if (vdata.cardSelected == 'totalSuccNum') {
|
||||
return record.totalSuccNum;
|
||||
} else if (vdata.cardSelected == 'totalRefundAmt') {
|
||||
return cal.cert2Dollar(record.totalRefundAmt);
|
||||
} else {
|
||||
return record.succRate;
|
||||
}
|
||||
};
|
||||
|
||||
// 进入页面初始化数据
|
||||
onLoad(() => {
|
||||
params.queryDateRange = `customDateTime_${dayjs(new Date()).startOf('date').format('YYYY-MM-DD HH:mm:ss')}_${dayjs(new Date()).endOf('date').format('YYYY-MM-DD HH:mm:ss')}`;
|
||||
vdata.time = dayjs().format('YYYY/MM/DD');
|
||||
getStatList();
|
||||
});
|
||||
onReachBottom(() => {});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.screen-time {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
margin: 10rpx 30rpx;
|
||||
margin-top: 20rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 20rpx;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
|
||||
.time-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
border-radius: 20rpx;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.active-screen {
|
||||
width: 171rpx;
|
||||
background-color: #fff;
|
||||
color: $J-color-t21;
|
||||
}
|
||||
}
|
||||
|
||||
.time-selection {
|
||||
margin-bottom: 10rpx;
|
||||
height: 100rpx;
|
||||
|
||||
image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.selection-main {
|
||||
height: auto;
|
||||
margin: 0 10rpx 0 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
position: relative;
|
||||
margin: 0 30rpx;
|
||||
background-color: $J-bg-ff;
|
||||
border-radius: $J-b-r32;
|
||||
|
||||
.stat-title {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #a6a6a6;
|
||||
}
|
||||
|
||||
.stat-money {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 202rpx;
|
||||
box-shadow: 0 50rpx 70rpx -60rpx rgba(107, 130, 153, 0.2);
|
||||
|
||||
.stat-num-title {
|
||||
font-size: 60rpx;
|
||||
font-weight: 700;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-card-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 68rpx 50rpx 68rpx;
|
||||
|
||||
.stat-item {
|
||||
width: 175rpx;
|
||||
margin-top: 50rpx;
|
||||
text-align: center;
|
||||
|
||||
.stat-num {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stat-tips {
|
||||
position: absolute;
|
||||
top: 30rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-selection {
|
||||
position: sticky;
|
||||
top: 88rpx;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 99;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 46rpx;
|
||||
height: 110rpx;
|
||||
background-color: #f7f7f7;
|
||||
|
||||
.stat-item {
|
||||
flex-grow: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
font-size: 27rpx;
|
||||
color: $J-color-t80;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.active-stat {
|
||||
font-weight: 500;
|
||||
color: #2980fd;
|
||||
}
|
||||
}
|
||||
|
||||
.tips-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 0 50rpx;
|
||||
height: 170rpx;
|
||||
|
||||
.tips-title {
|
||||
margin-bottom: 12rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.tips-info {
|
||||
font-size: 26rpx;
|
||||
color: #808080;
|
||||
}
|
||||
}
|
||||
|
||||
.store-name {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-bottom: 60rpx;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
height: 100rpx;
|
||||
border-top: 1rpx solid #ededed;
|
||||
font-size: 30rpx;
|
||||
backdrop-filter: blur(20rpx);
|
||||
background-color: rgba(252, 252, 252, 0.85);
|
||||
}
|
||||
|
||||
.store-block {
|
||||
height: 90rpx;
|
||||
}
|
||||
|
||||
.time-custom {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 60rpx;
|
||||
}
|
||||
|
||||
.store-name {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-bottom: 60rpx;
|
||||
text-align: center;
|
||||
line-height: 100rpx;
|
||||
height: 100rpx;
|
||||
border-top: 1rpx solid #ededed;
|
||||
font-size: 30rpx;
|
||||
background-color: rgba(252, 252, 252, 0.85);
|
||||
|
||||
image {
|
||||
margin-left: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user