Files
medical-mall/pages/mall/admin/finance/transaction_stats.uvue
2026-02-25 11:39:54 +08:00

837 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="finance-transaction-stats">
<!-- 头部筛选 -->
<view class="header-filters">
<view class="date-tabs">
<text
v-for="(item, index) in dateOptions"
:key="index"
class="date-tab-item"
:class="{ active: activeDateTab === index }"
@click="activeDateTab = index"
>{{ item }}</text>
</view>
<view class="date-picker-wrap">
<text class="calendar-icon">D</text>
<text class="date-range-text">2026-02-03 - 2026-02-03</text>
</view>
</view>
<!-- 交易概况区块 (CRMEB 1:1 复刻) -->
<view class="overview-card">
<view class="overview-header">
<view class="header-left">
<text class="section-title">交易概况</text>
<text class="info-tag">?</text>
</view>
<view class="header-right">
<view class="date-picker-inline">
<text class="date-text">2026/01/05 - 2026/02/03</text>
</view>
<button class="btn-query">查询</button>
<button class="btn-export">导出</button>
</view>
</view>
<!-- 指标网格 -->
<view class="overview-grid">
<!-- 第一行 -->
<view class="grid-row">
<view class="overview-item">
<view class="icon-box blue"><text class="icon">🕒</text></view>
<view class="item-info">
<text class="item-label">营业额</text>
<text class="item-value">442753.70</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">44275370% ▲</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box green"><text class="icon">¥</text></view>
<view class="item-info">
<text class="item-label">商品支付金额</text>
<text class="item-value">434693.52</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">43469352% ▲</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box orange"><text class="icon">🔒</text></view>
<view class="item-info">
<text class="item-label">购买会员金额</text>
<text class="item-value">8059.18</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">805918% ▲</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box purple"><text class="icon">💰</text></view>
<view class="item-info">
<text class="item-label">充值金额</text>
<text class="item-value">0.00</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value">0% -</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box cyan"><text class="icon">🛒</text></view>
<view class="item-info">
<text class="item-label">线下收银金额</text>
<text class="item-value">1</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">100% ▲</text>
</view>
</view>
</view>
</view>
<!-- 第二行 -->
<view class="grid-row second">
<view class="overview-item">
<view class="icon-box light-green"><text class="icon">↘</text></view>
<view class="item-info">
<text class="item-label">支出金额</text>
<text class="item-value">442752.69</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">44275269% ▲</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box gold"><text class="icon">💳</text></view>
<view class="item-info">
<text class="item-label">余额支付金额</text>
<text class="item-value">442752.69</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value up">5293.00% ▲</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box red-purple"><text class="icon"></text></view>
<view class="item-info">
<text class="item-label">支付佣金金额</text>
<text class="item-value">0.00</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value">0% -</text>
</view>
</view>
</view>
<view class="overview-item">
<view class="icon-box blue-gray"><text class="icon">📦</text></view>
<view class="item-info">
<text class="item-label">商品退款金额</text>
<text class="item-value">0.00</text>
<view class="trend-row">
<text class="trend-label">环比增长:</text>
<text class="trend-value">0% -</text>
</view>
</view>
</view>
<!-- 占位使其对齐 -->
<view class="overview-item transparent"></view>
</view>
</view>
<!-- 概况图表区 -->
<view class="overview-chart-section">
<view class="overview-chart-legend">
<view class="legend-dot blue"><text class="legend-text">营业额</text></view>
<view class="legend-dot green"><text class="legend-text">商品支付金额</text></view>
<view class="legend-dot gray-blue"><text class="legend-text">购买会员金额</text></view>
<view class="legend-dot red"><text class="legend-text">充值金额</text></view>
<view class="legend-dot orange"><text class="legend-text">支出金额</text></view>
</view>
<view class="overview-chart-box">
<EChartsView v-if="overviewTrendOption != null" :option="overviewTrendOption" class="main-trend-chart" />
</view>
</view>
</view>
<!-- 之前的统计卡片区域 -->
<view class="stats-section">
<!-- 左侧:今日订单金额 -->
<view class="stats-card-main">
<view class="card-header">
<text class="card-title">今日订单金额</text>
</view>
<view class="card-content">
<view class="amount-wrap">
<text class="currency">¥</text>
<text class="amount-value">0</text>
</view>
<view class="chart-legend">
<view class="legend-item">
<view class="dot blue"></view>
<text>今天</text>
</view>
<view class="legend-item">
<view class="dot gray"></view>
<text>昨天</text>
</view>
</view>
<view class="chart-box">
<EChartsView v-if="orderAmountOption != null" :option="orderAmountOption" class="stats-chart" />
</view>
</view>
</view>
<!-- 侧边统计网格 -->
<view class="stats-side-grid">
<view class="side-column">
<!-- 今日订单数 -->
<view class="side-stat-card">
<view class="card-header">
<text class="card-title">今日订单数</text>
</view>
<view class="card-body">
<text class="main-val">0</text>
<view class="compare-row">
<text class="label">昨日:</text>
<text class="val">4</text>
</view>
<view class="compare-row">
<text class="label">日环比:</text>
<text class="val down">-100% ▼</text>
</view>
<view class="mini-chart-placeholder">
<view class="blue-line"></view>
</view>
</view>
</view>
<!-- 本月订单数 -->
<view class="side-stat-card">
<view class="card-header">
<text class="card-title">本月订单数</text>
</view>
<view class="card-body">
<text class="main-val">12</text>
<view class="compare-row">
<text class="label">上月:</text>
<text class="val">206</text>
</view>
<view class="compare-row">
<text class="label">月环比:</text>
<text class="val down">-94% ▼</text>
</view>
<view class="mini-chart-placeholder">
<view class="blue-line"></view>
</view>
</view>
</view>
</view>
<view class="side-column">
<!-- 今日支付人数 -->
<view class="side-stat-card">
<view class="card-header">
<text class="card-title">今日支付人数</text>
</view>
<view class="card-body">
<text class="main-val">0</text>
<view class="compare-row">
<text class="label">昨日:</text>
<text class="val">4</text>
</view>
<view class="compare-row">
<text class="label">日环比:</text>
<text class="val down">-100% ▼</text>
</view>
<view class="mini-chart-placeholder">
<view class="blue-line"></view>
</view>
</view>
</view>
<!-- 本月支付人数 -->
<view class="side-stat-card">
<view class="card-header">
<text class="card-title">本月支付人数</text>
</view>
<view class="card-body">
<text class="main-val">7</text>
<view class="compare-row">
<text class="label">上月:</text>
<text class="val">134</text>
</view>
<view class="compare-row">
<text class="label">月环比:</text>
<text class="val down">-94% ▼</text>
</view>
<view class="mini-chart-placeholder">
<view class="blue-line"></view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { ref, onMounted } from 'vue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
const dateOptions = ['今天', '昨天', '最近7天', '最近30天', '本月', '本年']
const activeDateTab = ref(0)
const orderAmountOption = ref<any>(null)
const overviewTrendOption = ref<any>(null)
/**
* 工具函数:将 UTS 对象转换为纯 JavaScript 对象
* 确保 ECharts 在 renderjs 中能正确接收数据
*/
function toPlainObject(obj : any) : any {
if (obj == null) return null
if (typeof obj !== 'object') return obj
if (Array.isArray(obj)) {
return (obj as Array<any>).map((item : any) : any => toPlainObject(item))
}
const plain : Record<string, any> = {}
const keys = Object.keys(obj as Record<string, any>)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
if (key.startsWith('_') || key == 'toJSON') continue
const value = (obj as Record<string, any>)[key]
if (typeof value == 'function') continue
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
plain[key] = toPlainObject(value)
} else {
plain[key] = value
}
}
return plain
}
onMounted(() => {
// 延迟初始化图表确保容器就位
setTimeout(() => {
initCharts()
}, 300)
})
function initCharts() {
// 模拟趋势数据
const todayData = [120, 132, 101, 134, 90, 230, 210]
const yesterdayData = [220, 182, 191, 234, 290, 330, 310]
const option = {
grid: { left: '3%', right: '4%', bottom: '3%', top: '5%', containLabel: true },
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: { color: '#1890ff', type: 'dashed' }
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '23:59'],
axisLine: { lineStyle: { color: '#f0f0f0' } },
axisLabel: { color: '#999' }
},
yAxis: {
type: 'value',
splitLine: { show: false },
axisLine: { show: false },
axisLabel: { color: '#999' }
},
series: [
{
name: '今天',
type: 'line',
smooth: true,
data: todayData,
lineStyle: { color: '#1890ff', width: 2 },
areaStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(24, 144, 255, 0.2)' },
{ offset: 1, color: 'rgba(24, 144, 255, 0)' }
]
}
},
itemStyle: { color: '#1890ff' }
},
{
name: '昨天',
type: 'line',
smooth: true,
data: yesterdayData,
lineStyle: { color: '#d9d9d9', width: 2, type: 'dashed' },
itemStyle: { color: '#d9d9d9' }
}
]
}
// 核心修复:传递给 ECharts 的 Option 必须是 Plain Object
orderAmountOption.value = toPlainObject(option)
// 初始化交易概况趋势图 (多曲线)
const overviewOption = {
grid: { left: '3%', right: '4%', bottom: '10%', top: '5%', containLabel: true },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
boundaryGap: false,
data: ['2026-01-05', '01-10', '01-15', '01-20', '01-25', '01-31', '02-03'],
axisLine: { lineStyle: { color: '#f0f0f0' } },
axisLabel: { color: '#999', fontSize: 10 }
},
yAxis: {
type: 'value',
axisLine: { show: false },
splitLine: { lineStyle: { color: '#f5f5f5' } },
axisLabel: { color: '#999' }
},
series: [
{
name: '营业额',
type: 'line',
smooth: true,
data: [1000, 5000, 20000, 15000, 80000, 20000, 5000],
itemStyle: { color: '#1890ff' }
},
{
name: '商品支付金额',
type: 'line',
smooth: true,
data: [800, 4000, 18000, 12000, 75000, 18000, 4000],
itemStyle: { color: '#52c41a' }
},
{
name: '支出金额',
type: 'line',
smooth: true,
data: [500, 3000, 15000, 10000, 90000, 15000, 3000],
itemStyle: { color: '#fa8c16' }
}
]
}
overviewTrendOption.value = toPlainObject(overviewOption)
}
</script>
<style scoped lang="scss">
.finance-transaction-stats {
padding: 0;
background-color: transparent;
min-height: auto;
}
/* 头部筛选 */
.header-filters {
background: #fff;
padding: 12px 20px;
border-radius: 4px;
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 20px;
}
.date-tabs {
display: flex;
flex-direction: row;
border: 1px solid #d9d9d9;
border-radius: 2px;
margin-right: 20px;
}
.date-tab-item {
padding: 4px 15px;
font-size: 14px;
color: #666;
border-right: 1px solid #d9d9d9;
cursor: pointer;
&:last-child {
border-right: none;
}
&.active {
background-color: #1890ff;
color: #fff;
border-color: #1890ff;
}
}
.date-picker-wrap {
display: flex;
flex-direction: row;
align-items: center;
padding: 4px 12px;
border: 1px solid #d9d9d9;
border-radius: 2px;
}
.calendar-icon {
margin-right: 8px;
font-size: 14px;
}
.date-range-text {
font-size: 14px;
color: #333;
}
/* 统计区域布局 */
.stats-section {
display: flex;
flex-direction: row;
gap: 16px;
}
.stats-card-main {
flex: 3;
background: #fff;
border-radius: 4px;
padding: 24px;
min-height: 380px;
}
.card-title {
font-size: 16px;
color: #333;
font-weight: 500;
margin-bottom: 20px;
display: block;
}
.amount-wrap {
margin-top: 10px;
}
.currency {
font-size: 24px;
color: #333;
margin-right: 8px;
}
.amount-value {
font-size: 40px;
color: #333;
font-weight: bold;
}
.chart-legend {
display: flex;
flex-direction: row;
margin-top: 16px;
}
.legend-item {
display: flex;
flex-direction: row;
align-items: center;
margin-right: 20px;
font-size: 14px;
color: #666;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 8px;
&.blue { background-color: #1890ff; }
&.gray { background-color: #d9d9d9; }
}
.chart-box {
width: 100%;
height: 220px;
margin-top: 20px;
}
.stats-chart {
width: 100%;
height: 100%;
}
/* 侧边网格 */
.stats-side-grid {
flex: 2;
display: flex;
flex-direction: row;
gap: 16px;
}
.side-column {
flex: 1;
display: flex;
flex-direction: column;
gap: 16px;
}
.side-stat-card {
background: #fff;
border-radius: 4px;
padding: 24px;
display: flex;
flex-direction: column;
}
.main-val {
font-size: 32px;
font-weight: bold;
color: #333;
margin-bottom: 20px;
display: block;
}
.compare-row {
display: flex;
flex-direction: row;
margin-bottom: 8px;
font-size: 14px;
}
.compare-row .label {
color: #999;
margin-right: 8px;
}
.compare-row .val {
color: #333;
&.down { color: #52c41a; }
&.up { color: #f5222d; }
}
.mini-chart-placeholder {
margin-top: 20px;
height: 2px;
background-color: #f0f0f0;
position: relative;
}
.blue-line {
position: absolute;
left: 0;
top: 0;
width: 60%;
height: 100%;
background-color: #1890ff;
}
/* 交易概况复刻样式 */
.overview-card {
background: #fff;
border-radius: 4px;
padding: 20px;
margin-bottom: 20px;
}
.overview-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.overview-header .header-left {
display: flex;
flex-direction: row;
align-items: center;
}
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.info-tag {
width: 16px;
height: 16px;
border-radius: 50%;
background: #eee;
color: #999;
font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
margin-left: 8px;
}
.overview-header .header-right {
display: flex;
flex-direction: row;
gap: 12px;
}
.date-picker-inline {
border: 1px solid #dcdfe6;
padding: 5px 15px;
border-radius: 4px;
display: flex;
align-items: center;
}
.date-text {
font-size: 13px;
color: #606266;
}
.btn-query, .btn-export {
padding: 0 16px;
height: 32px;
line-height: 32px;
font-size: 13px;
border-radius: 4px;
cursor: pointer;
}
.btn-query {
background-color: #1890ff;
color: #fff;
border: none;
}
.btn-export {
background: #fff;
color: #1890ff;
border: 1px solid #1890ff;
}
/* 指标网格 */
.overview-grid {
display: flex;
flex-direction: column;
gap: 30px;
padding-bottom: 30px;
border-bottom: 1px dashed #f0f0f0;
}
.grid-row {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.overview-item {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
}
.overview-item.transparent {
visibility: hidden;
}
.icon-box {
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.icon-box .icon {
font-size: 20px;
color: #fff;
}
.icon-box.blue { background-color: #2f54eb; }
.icon-box.green { background-color: #52c41a; }
.icon-box.orange { background-color: #fa8c16; }
.icon-box.purple { background-color: #722ed1; }
.icon-box.cyan { background-color: #13c2c2; }
.icon-box.light-green { background-color: #a0d911; }
.icon-box.gold { background-color: #faad14; }
.icon-box.red-purple { background-color: #eb2f96; }
.icon-box.blue-gray { background-color: #4096ff; }
.item-info {
display: flex;
flex-direction: column;
}
.item-label {
font-size: 13px;
color: #999;
margin-bottom: 4px;
}
.item-value {
font-size: 24px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.trend-row {
display: flex;
flex-direction: row;
font-size: 12px;
color: #999;
}
.trend-value.up {
color: #f5222d;
margin-left: 4px;
}
/* 图表区 */
.overview-chart-section {
padding-top: 24px;
}
.overview-chart-legend {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 16px;
margin-bottom: 20px;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
}
.legend-dot.blue { background: #1890ff; }
.legend-dot.green { background: #52c41a; }
.legend-dot.gray-blue { background: #607d8b; }
.legend-dot.red { background: #f44336; }
.legend-dot.orange { background: #fa8c16; }
.legend-text {
font-size: 12px;
color: #666;
}
.overview-chart-box {
width: 100%;
height: 400px;
}
.main-trend-chart {
width: 100%;
height: 100%;
}
</style>