refactor(shop-dashboard): 调整商城后台Dashboard布局及面板结构

- 基本信息面板改为左右两列并排显示,响应式列数调整为2列
- 今日数据概况与基本信息合并至同一行,分别占用半宽空间
- 今日数据概况面板栅格调整为2列,样式和间距优化
- 删除快捷操作面板及相关图标导入和缓存清理功能
- 快速入口功能调整为动态渲染卡片布局取代原按键列表
- 优化部分样式,如待办任务列表的内边距和字体大小调整
- 头像显示增加默认图片占位,避免空白显示
This commit is contained in:
2026-07-16 21:25:22 +08:00
parent 0b6563aad9
commit 1d69923e06
2 changed files with 103 additions and 142 deletions

View File

@@ -29,3 +29,20 @@
1. 后端:`cd /Users/gxwebsoft/JAVA/websopy-java && git push`,然后远程服务器 `git pull && mvn clean package -DskipTests && docker-compose up -d --build cms-api`(或对应重启方式)。
2. 小程序端:`websopy-taro` 重新编译上传体验版/正式版。
3. 联调验证admin 点"立即续费" → 弹码 → 微信扫码 → 进小程序应显示"确认支付"页(不再误显成功)→ 点"立即支付" → 微信支付 → 显示"支付成功" → 跳转 `/user/apps/index`
## 商城后台 Dashboard 布局调整
- 文件:`src/views/shop/dashboard/index.vue`
- 需求:今日数据概况页,「基本信息」改为左右两列并排;删除「快捷操作」面板。
- 改动:
- `a-descriptions``:column="1"` 改为 `:column="{ xs: 1, sm: 2 }"`(响应式两列并排)。
- 基本信息 `a-col``:span="12"` 改为 `:span="24"`(占满整行)。
- 删除「快捷操作」`a-col` 整块并清理其专属的图标导入ShoppingCartOutlined/ShopOutlined/AppstoreOutlined/GiftOutlined/TeamOutlined/SettingOutlined/ClearOutlined`removeSiteInfoCache` 导入及 `handleClearCache` 函数。
### 今日数据概况 + 基本信息改为左右两栏布局
- 需求:参考「待处理事项 + 快速入口」的左右结构,将「今日数据概况」和「基本信息」也并排为左右各半。
- 改动:
- 原来各自独占整行的两个 panel合并到一个 `<a-row>` 中,各占 `:md="12"`
- 今日数据概况栅格从 4 列改为 2 列(`grid-template-columns: repeat(2, 1fr)`),适配半宽空间。
- 基本信息 `a-descriptions` 列数改为 `:column="1"`,半宽下单列更不拥挤。

View File

@@ -4,7 +4,7 @@
<div class="welcome-banner">
<div class="welcome-left flex flex-row gap-2 items-center">
<a-avatar
:src="shopLogoUrl"
:src="shopLogoUrl || 'https://oss.wsdns.cn/20240822/0252ad4ed46449cdafe12f8d3d96c2ea.svg'"
:size="64"
shape="square"
class="welcome-logo"
@@ -25,7 +25,6 @@
<a-button size="small" :disabled="!notifyEnabled" @click="onTestNotify">测试</a-button>
</div>
<a-space>
<a-tag color="green" style="font-size:13px;padding:4px 12px">{{ siteStore.statusText || '正常' }}</a-tag>
<a-button size="small" @click="refreshStatistics" :loading="loading">
<template #icon><ReloadOutlined /></template>
刷新数据
@@ -64,11 +63,11 @@
</div>
<div class="todo-list">
<div
v-for="todo in todoItems"
:key="todo.label"
class="todo-item"
:class="{ 'todo-item-urgent': todo.urgent }"
@click="navigateTo(todo.to)"
v-for="todo in todoItems"
:key="todo.label"
class="todo-item"
:class="{ 'todo-item-urgent': todo.urgent }"
@click="navigateTo(todo.to)"
>
<div class="todo-dot" :class="todo.dotColor"></div>
<div class="todo-content">
@@ -80,82 +79,63 @@
</div>
<RightOutlined class="todo-arrow" />
</div>
<div v-if="!loading && todoItems.every(t => t.value === 0)" class="todo-empty">
🎉 暂无待处理事项一切正常
</div>
<!-- <div v-if="!loading && todoItems.every(t => t.value === 0)" class="todo-empty">-->
<!-- 🎉 暂无待处理事项一切正常-->
<!-- </div>-->
</div>
</div>
</a-col>
<!-- 快速入口 -->
<!-- 今日数据概况 -->
<a-col :xs="24" :md="12">
<div class="panel">
<div class="panel-header">
<span class="panel-title"> 快速入口</span>
<span class="panel-title">📊 今日数据概况</span>
</div>
<div class="quick-grid">
<div
v-for="item in quickLinks"
:key="item.to"
class="quick-card"
@click="navigateTo(item.to)"
>
<div class="quick-icon" :style="{ background: item.bg }">
{{ item.icon }}
<span v-if="item.badge" class="quick-icon-dot"></span>
<div class="today-stats">
<div class="today-stat">
<div class="today-stat-icon">💰</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ formatMoney(todayStats.salesAmount) }}</div>
<div class="today-stat-label">今日销售额</div>
</div>
</div>
<div class="quick-label">{{ item.label }}</div>
<div class="today-stat">
<div class="today-stat-icon">📦</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.orderCount || 0 }}</div>
<div class="today-stat-label">今日订单数</div>
</div>
</div>
<div class="today-stat">
<div class="today-stat-icon">👥</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.newUsers || 0 }}</div>
<div class="today-stat-label">新增用户</div>
</div>
</div>
<div class="today-stat">
<div class="today-stat-icon">🎁</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.couponUsed || 0 }}</div>
<div class="today-stat-label">使用优惠券</div>
</div>
</div>
</div>
</div>
</a-col>
</a-row>
<!-- 今日数据趋势 -->
<div class="panel">
<div class="panel-header">
<span class="panel-title">📊 今日数据概况</span>
</div>
<div class="today-stats">
<div class="today-stat">
<div class="today-stat-icon">💰</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ formatMoney(todayStats.salesAmount) }}</div>
<div class="today-stat-label">今日销售额</div>
</div>
</div>
<div class="today-stat">
<div class="today-stat-icon">📦</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.orderCount || 0 }}</div>
<div class="today-stat-label">今日订单数</div>
</div>
</div>
<div class="today-stat">
<div class="today-stat-icon">👥</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.newUsers || 0 }}</div>
<div class="today-stat-label">新增用户</div>
</div>
</div>
<div class="today-stat">
<div class="today-stat-icon">🎁</div>
<div class="today-stat-info">
<div class="today-stat-value">{{ todayStats.couponUsed || 0 }}</div>
<div class="today-stat-label">使用优惠券</div>
</div>
</div>
</div>
</div>
<!-- 今日数据概况 + 基本信息 -->
<a-row :gutter="[16, 16]">
<!-- 系统基本信息 -->
<a-row :gutter="[16, 16]" style="margin-top: 16px">
<a-col :span="12">
<!-- 基本信息 -->
<a-col :xs="24" :md="12">
<div class="panel">
<div class="panel-header">
<span class="panel-title">📋 基本信息</span>
</div>
<div style="padding: 16px 18px;">
<div style="padding: 23px 18px;">
<a-descriptions :column="1" size="small">
<a-descriptions-item label="软件名称">
<template v-if="subscriptionLoading">
@@ -163,16 +143,16 @@
</template>
<template v-else>{{ currentProduct?.productName || '-' }}</template>
</a-descriptions-item>
<a-descriptions-item label="版本">
<a-descriptions-item label="当前版本">
<template v-if="subscriptionLoading">
<a-skeleton-input :active="true" size="small" style="width: 80px" />
<a-skeleton-input :active="true" size="small" style="width: 160px" />
</template>
<template v-else>{{ currentProduct?.version || '-' }}</template>
</a-descriptions-item>
<a-descriptions-item label="运行状态">
<a-tag :color="productStatusText.color">{{ productStatusText.text }}</a-tag>
<a-descriptions-item label="授权编号">
<span>{{ currentSubscription?.subscriptionNo || '-' }}</span>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
<a-descriptions-item label="开通时间">
<template v-if="subscriptionLoading">
<a-skeleton-input :active="true" size="small" style="width: 160px" />
</template>
@@ -184,7 +164,7 @@
</template>
<template v-else-if="currentSubscription && currentSubscription.status === 'active' && currentSubscription.expireTime">
<span>{{ formatDateTime(currentSubscription.expireTime) }}</span>
<a-tag :color="isExpired ? 'red' : 'green'" style="margin-left: 8px">
<a-tag v-if="isExpired" :color="isExpired ? 'red' : 'green'" style="margin-left: 8px">
{{ isExpired ? '已过期' : '已激活' }}
</a-tag>
<span v-if="daysToExpire !== null && daysToExpire >= 0 && daysToExpire <= 7" class="expire-warn">
@@ -207,62 +187,45 @@
<a-button type="link" size="small" @click="onSubscribe" style="padding: 0 0 0 8px">立即订阅</a-button>
</template>
</a-descriptions-item>
<a-descriptions-item label="系统运行">
<template v-if="subscriptionLoading">
<a-skeleton-input :active="true" size="small" style="width: 60px" />
</template>
<template v-else>
<a-tooltip title="自您首次开通小程序商城起">
<span>{{ runDays }} </span>
</a-tooltip>
</template>
</a-descriptions-item>
<!-- <a-descriptions-item label="系统运行">-->
<!-- <template v-if="subscriptionLoading">-->
<!-- <a-skeleton-input :active="true" size="small" style="width: 60px" />-->
<!-- </template>-->
<!-- <template v-else>-->
<!-- <a-tooltip title="自您首次开通小程序商城起">-->
<!-- <span>{{ runDays }} </span>-->
<!-- </a-tooltip>-->
<!-- </template>-->
<!-- </a-descriptions-item>-->
<!-- <a-descriptions-item label="绑定小程序">-->
<!-- {{ currentSubscription?.miniProgramId || 'wx75cbc65759eb981a' }}-->
<!-- </a-descriptions-item>-->
<!-- <a-descriptions-item label="售后服务">-->
<!-- <a href="https://websoft.top/login" class="text-gray-500">提交售后工单</a>-->
<!-- </a-descriptions-item>-->
</a-descriptions>
</div>
</div>
</a-col>
<!-- 快捷操作 -->
<a-col :span="12">
<div class="panel" style="min-height: 353px">
<!-- 快速入口 -->
<a-col :xs="24" :md="12">
<div class="panel">
<div class="panel-header">
<span class="panel-title"> 捷操作</span>
<span class="panel-title"> 速入口</span>
</div>
<div style="padding: 16px 18px;">
<a-space direction="vertical" style="width: 100%">
<a-button
type="primary"
block
@click="navigateTo('/shop/shopOrder')"
>
<ShoppingCartOutlined />
订单管理
</a-button>
<a-button block @click="navigateTo('/shop/shopGoods')">
<ShopOutlined />
商品管理
</a-button>
<a-button block @click="navigateTo('/shop/shopGoodsCategory')">
<AppstoreOutlined />
商品分类
</a-button>
<a-button block @click="navigateTo('/market/coupon')">
<GiftOutlined />
优惠券管理
</a-button>
<a-button block @click="navigateTo('/system/user')">
<TeamOutlined />
会员管理
</a-button>
<a-button block @click="navigateTo('/shop/shopSetting')">
<SettingOutlined />
商城设置
</a-button>
<a-button block @click="handleClearCache">
<ClearOutlined />
清除缓存
</a-button>
</a-space>
<div class="quick-grid">
<div
v-for="item in quickLinks"
:key="item.to"
class="quick-card"
@click="navigateTo(item.to)"
>
<div class="quick-icon" :style="{ background: item.bg }">
{{ item.icon }}
<span v-if="item.badge" class="quick-icon-dot"></span>
</div>
<div class="quick-label">{{ item.label }}</div>
</div>
</div>
</div>
</a-col>
@@ -350,13 +313,6 @@ import { useOrderNotify } from '@/views/shop/shopOrder/useOrderNotify';
import {
ReloadOutlined,
RightOutlined,
ShoppingCartOutlined,
ShopOutlined,
AppstoreOutlined,
GiftOutlined,
TeamOutlined,
SettingOutlined,
ClearOutlined,
InfoCircleOutlined,
QrcodeOutlined
} from '@ant-design/icons-vue';
@@ -370,7 +326,6 @@ import { pageShopOrder } from '@/api/shop/shopOrder';
import { getShopSettingCategoryValues } from '@/api/shop/shopSetting';
import { getCompressedImageUrl } from '@/utils/image';
import { storeToRefs } from 'pinia';
import { removeSiteInfoCache } from '@/api/cms/cmsWebsite';
import { getProductDetail } from '@/api/app/appProduct';
import type { AppProduct } from '@/api/app/appProduct/model';
import type { AppSubscription } from '@/api/app/appSubscription/model';
@@ -769,17 +724,6 @@ const formatMoney = (value?: number | string) => {
return Number.isFinite(num) ? num.toFixed(2) : '0.00';
};
// 清除缓存
const handleClearCache = () => {
removeSiteInfoCache('SiteInfo:' + localStorage.getItem('TenantId')).then(
(msg) => {
if (msg) {
message.success(msg);
}
}
);
};
// 新订单声音提醒(全局单例:轮询由 layout 启动,此处仅消费状态 + UI
const { enabled: notifyEnabled, hasNewOrder, toggle: onNotifyToggle, testNotify: onTestNotify } = useOrderNotify({
onNewOrder: () => loadData()
@@ -971,7 +915,7 @@ onUnmounted(() => {
.panel-title { font-size: 14px; font-weight: 600; color: rgba(0,0,0,0.85); }
/* 待办 */
.todo-list { padding: 8px 0; }
.todo-list { padding: 2px 0; }
.todo-item {
display: flex; align-items: center; gap: 12px;
padding: 12px 18px; cursor: pointer; transition: background 0.15s;
@@ -1029,9 +973,9 @@ onUnmounted(() => {
/* 今日数据 */
.today-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
padding: 20px;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 16px;
}
.today-stat {
display: flex;