修复bug

This commit is contained in:
2026-03-20 15:24:59 +08:00
parent 14b506036c
commit 29f588a2b2
13 changed files with 321 additions and 2003 deletions

View File

@@ -1,42 +0,0 @@
<template>
<view class="page-container">
<view class="page-content">
<view class="placeholder-card">
<text class="placeholder-title">正在跳转...</text>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { onMounted } from 'vue'
import { openRoute } from '@/layouts/admin/store/adminNavStore.uts'
onMounted(() => {
// 跳转到最新的订单管理页面
openRoute('OrderList')
})
</script>
<style scoped lang="scss">
.page-container {
padding: 20px;
min-height: 100vh;
background: #f5f5f5;
}
.page-content {
background: #fff;
border-radius: 4px;
padding: 24px;
}
.placeholder-card {
text-align: center;
padding: 60px 20px;
}
.placeholder-title {
display: block;
font-size: 18px;
font-weight: 600;
color: #666;
}
</style>

View File

@@ -1,124 +1,125 @@
<template>
<AdminLayout :currentPage="currentPage">
<view class="order-statistic-page">
<!-- 时间选择卡片 -->
<view class="filter-card">
<view class="filter-item">
<text class="filter-label">时间选择:</text>
<AnalyticsDateRangePicker
:initialStartDate="startDate"
:initialEndDate="endDate"
@apply="onApplyRange"
@clear="onClearRange"
/>
</view>
</view>
<view class="admin-page">
<view class="admin-sections">
<!-- 数据汇总卡片 -->
<view class="stat-cards-row">
<!-- 订单量 -->
<view class="stat-card">
<view class="icon-wrap blue-bg">
<view class="custom-icon icon-order"></view>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.order_count ?? 0 }}</text>
<text class="stat-desc">订单量</text>
</view>
</view>
<!-- 订单销售额 -->
<view class="stat-card">
<view class="icon-wrap orange-bg">
<view class="custom-icon icon-money"></view>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.total_amount?.toFixed(2) ?? '0.00' }}</text>
<text class="stat-desc">订单销售额</text>
</view>
</view>
<!-- 退款订单数 -->
<view class="stat-card">
<view class="icon-wrap green-bg">
<view class="custom-icon icon-refund"></view>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.refund_count ?? 0 }}</text>
<text class="stat-desc">退款订单数</text>
</view>
</view>
<!-- 退款金额 -->
<view class="stat-card last-card">
<view class="icon-wrap pink-bg">
<view class="custom-icon icon-refund-money"></view>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.refund_amount?.toFixed(2) ?? '0.00' }}</text>
<text class="stat-desc">退款金额</text>
</view>
</view>
</view>
<!-- 营业趋势图表 -->
<view class="chart-card">
<view class="card-header">
<text class="card-title">营业趋势</text>
</view>
<view class="chart-container">
<EChartsView :option="trendOption" class="trend-chart" />
</view>
</view>
<!-- 底部双图表区域 -->
<view class="bottom-charts-row">
<!-- 订单来源分析 -->
<view class="bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单来源分析</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
<!-- 时间选择卡片 -->
<view class="admin-card filter-section">
<view class="filter-item">
<text class="filter-label">时间选择:</text>
<AnalyticsDateRangePicker
:initialStartDate="startDate"
:initialEndDate="endDate"
@apply="onApplyRange"
@clear="onClearRange"
/>
</view>
</view>
<view class="pie-chart-container">
<EChartsView :option="sourceOption" class="source-chart" />
</view>
</view>
<!-- 订单类型分析 -->
<view class="bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单类型分析</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
<!-- 数据汇总卡片 (响应式 kpi-grid 4列) -->
<view class="kpi-grid">
<!-- 订单量 -->
<view class="admin-card stat-card">
<view class="icon-wrap blue-bg">
<text class="icon-char">≡</text>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.order_count ?? 0 }}</text>
<text class="stat-desc">订单量</text>
</view>
</view>
<!-- 订单销售额 -->
<view class="admin-card stat-card">
<view class="icon-wrap orange-bg">
<text class="icon-char">¥</text>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.total_amount?.toFixed(2) ?? '0.00' }}</text>
<text class="stat-desc">订单销售额</text>
</view>
</view>
<!-- 退款订单数 -->
<view class="admin-card stat-card">
<view class="icon-wrap green-bg">
<text class="icon-char">↩</text>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.refund_count ?? 0 }}</text>
<text class="stat-desc">退款订单数</text>
</view>
</view>
<!-- 退款金额 -->
<view class="admin-card stat-card">
<view class="icon-wrap pink-bg">
<text class="icon-char">↺</text>
</view>
<view class="stat-info">
<text class="stat-value">{{ orderStats?.refund_amount?.toFixed(2) ?? '0.00' }}</text>
<text class="stat-desc">退款金额</text>
</view>
</view>
</view>
<view class="type-table-container">
<view class="table-header">
<text class="th-text col-id">序号</text>
<text class="th-text col-name">来源</text>
<text class="th-text col-money">金额</text>
<text class="th-text col-rate">占比率</text>
<!-- 营业趋势图表 -->
<view class="admin-card chart-card">
<view class="chart-card-header">
<text class="card-title">营业趋势</text>
<text class="download-icon-text">↓</text>
</view>
<view class="table-body">
<view v-for="(item, index) in orderTypeData" :key="index" class="table-row">
<text class="td-text col-id">{{ index + 1 }}</text>
<text class="td-text col-name">{{ item.name }}</text>
<text class="td-text col-money">{{ item.amount }}</text>
<view class="col-rate rate-box">
<view class="progress-wrap">
<view class="progress-bar" :style="{ width: item.rate + '%', backgroundColor: '#1890ff' }"></view>
<EChartsView :option="trendOption" class="trend-chart" />
</view>
<!-- 底部双图表区域 -->
<view class="bottom-charts-grid">
<!-- 订单来源分析 -->
<view class="admin-card bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单来源分析</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
</view>
</view>
<view class="pie-chart-container">
<EChartsView :option="sourceOption" class="source-chart" />
</view>
</view>
<!-- 订单类型分析 -->
<view class="admin-card bottom-chart-card">
<view class="card-header-row">
<text class="card-title">订单类型分析</text>
<view class="style-toggle">
<text class="toggle-text">切换样式</text>
</view>
</view>
<view class="type-table-container">
<view class="table-header">
<text class="th-text col-id">序号</text>
<text class="th-text col-name">来源</text>
<text class="th-text col-money">金额</text>
<text class="th-text col-rate">占比率</text>
</view>
<view class="table-body">
<view v-for="(item, index) in orderTypeData" :key="index" class="table-row">
<text class="td-text col-id">{{ index + 1 }}</text>
<text class="td-text col-name">{{ item.name }}</text>
<text class="td-text col-money">{{ item.amount }}</text>
<view class="col-rate rate-box">
<view class="progress-wrap">
<view class="progress-bar" :style="{ width: item.rate + '%', backgroundColor: '#1890ff' }"></view>
</view>
<text class="rate-val">{{ item.rate }}%</text>
</view>
</view>
<text class="rate-val">{{ item.rate }}%</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</AdminLayout>
</template>
<script setup lang="uts">
@@ -173,23 +174,37 @@ async function loadAllData() {
const st = startDate.value ? (startDate.value + ' 00:00:00') : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString()
const et = endDate.value ? (endDate.value + ' 23:59:59') : new Date().toISOString()
// 各图表独立 try-catch单个接口失败不影响其他图表展示
// 1. 加载汇总数据
try {
// 1. 加载汇总数据
orderStats.value = await fetchOrderStats(st, et)
// 2. 加载趋势数据
} catch (e) {
console.error('[order-stats] 汇总数据加载失败', e)
}
// 2. 加载趋势数据
try {
const trendData = await fetchOrderTrend(st, et)
initTrendChart(trendData)
// 3. 加载来源数据
} catch (e) {
console.error('[order-stats] 趋势图加载失败', e)
}
// 3. 加载来源数据
try {
const sourceData = await fetchOrderSourceStats(st, et)
initSourceChart(sourceData)
} catch (e) {
console.error('[order-stats] 来源图加载失败', e)
}
// 4. 加载订单类型数据
// 4. 加载订单类型数据rpc_admin_order_type_stats 目前返回 400单独隔离
try {
const typeData = await fetchOrderTypeStats(st, et)
orderTypeData.value = typeData
} catch (e) {
uni.showToast({ title: '加载统计数据失败', icon: 'none' })
console.error('[order-stats] 订单类型数据加载失败', e)
}
}
@@ -340,97 +355,125 @@ function initTrendChart(data: any[]) {
</script>
<style scoped lang="scss">
.order-statistic-page {
/* 使用 Layout 的背景和内边距 */
min-height: 100%;
}
.filter-card {
background-color: #fff;
padding: var(--admin-card-padding);
border-radius: 4px;
margin-bottom: var(--admin-section-gap);
/* ===== 筛选区 ===== */
.filter-section {
/* admin-card 提供 background / padding / border-radius */
}
.filter-item {
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.filter-label {
font-size: 14px;
color: #333;
margin-right: 12px;
white-space: nowrap;
}
.date-picker-mock {
/* ===== KPI 统计卡片 ===== */
/* .kpi-grid 由 admin-responsive.css 全局提供响应式 grid>=1200px 4列 / >=768px 2列 / <768px 1列 */
.stat-card {
/* admin-card 提供背景/内边距,这里只定义内部 flex 布局 */
display: flex;
flex-direction: row;
align-items: center;
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 4px 12px;
width: 240px;
min-width: 0;
}
.calendar-icon {
width: 16px;
height: 16px;
margin-right: 8px;
opacity: 0.45;
.icon-wrap {
width: 54px;
height: 54px;
border-radius: 27px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
flex-shrink: 0;
}
.date-range {
font-size: 14px;
color: #595959;
.blue-bg { background-color: #1890ff; }
.orange-bg { background-color: #fa8c16; }
.green-bg { background-color: #52c41a; }
.pink-bg { background-color: #eb2f96; }
.stat-info {
display: flex;
flex-direction: column;
min-width: 0;
}
.stat-cards-row {
.stat-value {
font-size: 28px;
font-weight: 500;
color: #1a1a1a;
line-height: 1.2;
}
.stat-desc {
font-size: 13px;
color: #8c8c8c;
margin-top: 4px;
}
/* 自定义图标 */
.icon-char {
font-size: 20px;
color: #fff;
font-weight: bold;
line-height: 1;
}
/* ===== 趋势图表 ===== */
.chart-card {
/* admin-card 提供背景/内边距 */
}
.chart-card-header {
display: flex;
flex-direction: row;
gap: var(--admin-section-gap);
margin-bottom: var(--admin-section-gap);
}
.chart-card {
background-color: #fff;
border-radius: 4px;
padding: var(--admin-card-padding);
}
.card-header {
margin-bottom: 20px;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.card-title {
font-size: 16px;
font-weight: bold;
font-size: 15px;
font-weight: 600;
color: #1a1a1a;
}
.chart-container {
width: 100%;
height: 400px;
.download-icon-text {
font-size: 18px;
color: #bfbfbf;
cursor: pointer;
padding: 4px 6px;
}
.trend-chart {
width: 100%;
height: 100%;
height: 400px;
}
.bottom-charts-row {
display: flex;
flex-direction: row;
gap: var(--admin-section-gap);
margin-top: var(--admin-section-gap);
/* ===== 底部双图表 ===== */
.bottom-charts-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--admin-section-gap, 20px);
}
@media (max-width: 900px) {
.bottom-charts-grid {
grid-template-columns: 1fr;
}
}
.bottom-chart-card {
flex: 1;
background-color: #fff;
border-radius: 4px;
padding: var(--admin-card-padding);
/* admin-card 提供背景/内边距 */
min-width: 0;
}
.card-header-row {
@@ -445,6 +488,7 @@ function initTrendChart(data: any[]) {
border: 1px solid #d9d9d9;
border-radius: 4px;
padding: 2px 8px;
cursor: pointer;
}
.toggle-text {
@@ -452,6 +496,7 @@ function initTrendChart(data: any[]) {
color: #595959;
}
/* 来源饼图 */
.pie-chart-container {
width: 100%;
height: 320px;
@@ -462,6 +507,7 @@ function initTrendChart(data: any[]) {
height: 100%;
}
/* 类型分析表格 */
.type-table-container {
width: 100%;
}
@@ -479,18 +525,13 @@ function initTrendChart(data: any[]) {
color: #595959;
}
/* 统一列宽与对齐方式 */
.col-id { width: 80px; text-align: center; }
.col-name { flex: 1; text-align: left; padding-left: 40px; }
.col-money { width: 180px; text-align: left; }
.col-rate { width: 240px; text-align: left; }
.table-row {
display: flex;
flex-direction: row;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child { border-bottom: none; }
}
.td-text {
@@ -498,20 +539,27 @@ function initTrendChart(data: any[]) {
color: #262626;
}
/* 列宽 */
.col-id { width: 60px; text-align: center; flex-shrink: 0; }
.col-name { flex: 1; text-align: left; padding-left: 24px; min-width: 0; }
.col-money { width: 120px; text-align: left; flex-shrink: 0; }
.col-rate { width: 200px; text-align: left; flex-shrink: 0; }
.rate-box {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start; /* 改为左对齐,与表头对齐样式一致 */
justify-content: flex-start;
}
.progress-wrap {
width: 120px;
width: 100px;
height: 6px;
background-color: #f5f5f5;
border-radius: 3px;
margin-right: 12px;
margin-right: 10px;
overflow: hidden;
flex-shrink: 0;
}
.progress-bar {
@@ -522,129 +570,19 @@ function initTrendChart(data: any[]) {
.rate-val {
font-size: 13px;
color: #595959;
width: 50px;
min-width: 44px;
text-align: right;
}
.stat-card {
flex: 1;
background-color: #fff;
border-radius: 4px;
padding: var(--admin-card-padding);
display: flex;
flex-direction: row;
align-items: center;
}
.icon-wrap {
width: 54px;
height: 54px;
border-radius: 27px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20px;
}
/* 颜色背景 - 1:1 匹配截图 */
.blue-bg {
background-color: #e6f7ff;
border: 2px solid #bae7ff;
}
.orange-bg {
background-color: #fff7e6;
border: 2px solid #ffe58f;
}
.green-bg {
background-color: #f6ffed;
border: 2px solid #b7eb8f;
}
.pink-bg {
background-color: #fff0f6;
border: 2px solid #ffadd2;
}
.stat-info {
display: flex;
flex-direction: column;
}
.stat-value {
font-size: 28px;
font-weight: 500;
color: #1a1a1a;
line-height: 1.2;
}
.stat-desc {
font-size: 13px;
color: #8c8c8c;
margin-top: 4px;
}
/* 自定义图标 1:1 形状模拟 - 内部使用伪元素或形状模拟截图形状 */
.custom-icon {
width: 24px;
height: 24px;
position: relative;
}
.icon-order {
background-color: #1890ff;
border-radius: 4px;
&::before {
content: '';
position: absolute;
top: 6px; left: 4px; right: 4px; height: 2px;
background: #fff;
}
}
.icon-money {
background-color: #faad14;
border-radius: 50%;
&::after {
content: '¥';
color: #fff;
font-size: 12px;
font-weight: bold;
display: flex; justify-content: center; align-items: center; height: 100%;
}
}
.icon-refund {
background-color: #52c41a;
border-radius: 4px;
&::before {
content: '↺';
color: #fff;
font-size: 16px;
display: flex; justify-content: center; align-items: center; height: 100%;
}
}
.icon-refund-money {
background-color: #eb2f96;
border-radius: 50%;
&::after {
content: '';
color: #fff;
font-size: 18px;
display: flex; justify-content: center; align-items: center; height: 100%;
}
}
</style>
<style scoped lang="scss">
@import '@/uni.scss';
.page {
/* 使用 Layout 的背景和内边距 */
}
.header {
padding: var(--admin-card-padding);
border-radius: $radius;
background: $background-primary;
box-shadow: $shadow-xs;
.page { }
.header {
padding: var(--admin-card-padding);
border-radius: $radius;
background: $background-primary;
box-shadow: $shadow-xs;
}
.title { font-size: $font-size-lg; font-weight: $font-weight-bold; color: $text-primary; }
.sub-title { margin-top: $space-xs; font-size: $font-size-md; color: $text-secondary; }