优化:getSiteInfo、statistics使用了状态管理模式,提升性能。
This commit is contained in:
101
src/composables/useSiteData.ts
Normal file
101
src/composables/useSiteData.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 网站数据组合式函数
|
||||
* 提供统一的网站信息和统计数据访问接口
|
||||
*/
|
||||
import { computed } from 'vue';
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import { useStatisticsStore } from '@/store/modules/statistics';
|
||||
|
||||
export function useSiteData() {
|
||||
const siteStore = useSiteStore();
|
||||
const statisticsStore = useStatisticsStore();
|
||||
|
||||
// 网站信息相关
|
||||
const siteInfo = computed(() => siteStore.siteInfo);
|
||||
const websiteName = computed(() => siteStore.websiteName);
|
||||
const websiteLogo = computed(() => siteStore.websiteLogo);
|
||||
const websiteComments = computed(() => siteStore.websiteComments);
|
||||
const websiteDarkLogo = computed(() => siteStore.websiteDarkLogo);
|
||||
const websiteDomain = computed(() => siteStore.websiteDomain);
|
||||
const websiteId = computed(() => siteStore.websiteId);
|
||||
const runDays = computed(() => siteStore.runDays);
|
||||
const siteLoading = computed(() => siteStore.loading);
|
||||
|
||||
// 统计数据相关
|
||||
const statistics = computed(() => statisticsStore.statistics);
|
||||
const userCount = computed(() => statisticsStore.userCount);
|
||||
const orderCount = computed(() => statisticsStore.orderCount);
|
||||
const totalSales = computed(() => statisticsStore.totalSales);
|
||||
const todaySales = computed(() => statisticsStore.todaySales);
|
||||
const monthSales = computed(() => statisticsStore.monthSales);
|
||||
const todayOrders = computed(() => statisticsStore.todayOrders);
|
||||
const todayUsers = computed(() => statisticsStore.todayUsers);
|
||||
const statisticsLoading = computed(() => statisticsStore.loading);
|
||||
|
||||
// 整体加载状态
|
||||
const loading = computed(() => siteLoading.value || statisticsLoading.value);
|
||||
|
||||
// 方法
|
||||
const fetchSiteInfo = (forceRefresh = false) => {
|
||||
return siteStore.fetchSiteInfo(forceRefresh);
|
||||
};
|
||||
|
||||
const fetchStatistics = (forceRefresh = false) => {
|
||||
return statisticsStore.fetchStatistics(forceRefresh);
|
||||
};
|
||||
|
||||
const refreshAll = async (forceRefresh = true) => {
|
||||
await Promise.all([
|
||||
fetchSiteInfo(forceRefresh),
|
||||
fetchStatistics(forceRefresh)
|
||||
]);
|
||||
};
|
||||
|
||||
const startAutoRefresh = (interval?: number) => {
|
||||
statisticsStore.startAutoRefresh(interval);
|
||||
};
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
statisticsStore.stopAutoRefresh();
|
||||
};
|
||||
|
||||
const clearCache = () => {
|
||||
siteStore.clearCache();
|
||||
statisticsStore.clearCache();
|
||||
};
|
||||
|
||||
return {
|
||||
// 网站信息
|
||||
siteInfo,
|
||||
websiteName,
|
||||
websiteLogo,
|
||||
websiteComments,
|
||||
websiteDarkLogo,
|
||||
websiteDomain,
|
||||
websiteId,
|
||||
runDays,
|
||||
siteLoading,
|
||||
|
||||
// 统计数据
|
||||
statistics,
|
||||
userCount,
|
||||
orderCount,
|
||||
totalSales,
|
||||
todaySales,
|
||||
monthSales,
|
||||
todayOrders,
|
||||
todayUsers,
|
||||
statisticsLoading,
|
||||
|
||||
// 状态
|
||||
loading,
|
||||
|
||||
// 方法
|
||||
fetchSiteInfo,
|
||||
fetchStatistics,
|
||||
refreshAll,
|
||||
startAutoRefresh,
|
||||
stopAutoRefresh,
|
||||
clearCache
|
||||
};
|
||||
}
|
||||
@@ -150,8 +150,7 @@ import SettingDrawer from './setting-drawer.vue';
|
||||
import {useUserStore} from '@/store/modules/user';
|
||||
import {logout} from '@/utils/page-tab-util';
|
||||
import {listRoles} from '@/api/system/role';
|
||||
import {getSiteInfo} from "@/api/layout";
|
||||
import {CmsWebsite} from "@/api/cms/cmsWebsite/model";
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import Qrcode from "@/components/QrCode/index.vue";
|
||||
|
||||
// 是否开启响应式布局
|
||||
@@ -160,6 +159,8 @@ const {styleResponsive} = storeToRefs(themeStore);
|
||||
const SiteUrl = localStorage.getItem('SiteUrl');
|
||||
// 是否显示二维码
|
||||
const showQrcode = ref(false);
|
||||
// 使用网站信息 store
|
||||
const siteStore = useSiteStore();
|
||||
// const TENANT_ID = localStorage.getItem('TenantId');
|
||||
// const TENANT_NAME = localStorage.getItem('TenantName');
|
||||
const emit = defineEmits<{
|
||||
@@ -235,13 +236,7 @@ const toggleFullscreen = () => {
|
||||
const reload = () => {
|
||||
// 查询网站信息
|
||||
if (!localStorage.getItem('WebsiteId')) {
|
||||
getSiteInfo().then((data) => {
|
||||
if(data){
|
||||
localStorage.setItem('WebsiteId', `${data.websiteId}`);
|
||||
localStorage.setItem('Domain', `${data.domain}`)
|
||||
localStorage.setItem('SiteUrl', `${data.prefix}${data.domain}`)
|
||||
}
|
||||
})
|
||||
siteStore.fetchSiteInfo().catch(console.error);
|
||||
}
|
||||
// 查询商户角色的roleId
|
||||
if (!localStorage.getItem('RoleIdByMerchant')) {
|
||||
|
||||
152
src/store/modules/site.ts
Normal file
152
src/store/modules/site.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 网站信息 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { getSiteInfo } from '@/api/layout';
|
||||
import { CmsWebsite } from '@/api/cms/cmsWebsite/model';
|
||||
|
||||
export interface SiteState {
|
||||
// 网站信息
|
||||
siteInfo: CmsWebsite | null;
|
||||
// 加载状态
|
||||
loading: boolean;
|
||||
// 最后更新时间
|
||||
lastUpdateTime: number | null;
|
||||
// 缓存有效期(毫秒)
|
||||
cacheExpiry: number;
|
||||
}
|
||||
|
||||
export const useSiteStore = defineStore({
|
||||
id: 'site',
|
||||
state: (): SiteState => ({
|
||||
siteInfo: null,
|
||||
loading: false,
|
||||
lastUpdateTime: null,
|
||||
// 默认缓存30分钟
|
||||
cacheExpiry: 30 * 60 * 1000
|
||||
}),
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* 获取网站名称
|
||||
*/
|
||||
websiteName: (state): string => {
|
||||
return state.siteInfo?.websiteName || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站Logo
|
||||
*/
|
||||
websiteLogo: (state): string => {
|
||||
return state.siteInfo?.websiteLogo || '/logo.png';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站描述
|
||||
*/
|
||||
websiteComments: (state): string => {
|
||||
return state.siteInfo?.comments || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取小程序码
|
||||
*/
|
||||
websiteDarkLogo: (state): string => {
|
||||
return state.siteInfo?.websiteDarkLogo || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站域名
|
||||
*/
|
||||
websiteDomain: (state): string => {
|
||||
return state.siteInfo?.domain || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站ID
|
||||
*/
|
||||
websiteId: (state): number | undefined => {
|
||||
return state.siteInfo?.websiteId;
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算系统运行天数
|
||||
*/
|
||||
runDays: (state): number => {
|
||||
if (!state.siteInfo?.createTime) return 0;
|
||||
const createTime = new Date(state.siteInfo.createTime).getTime();
|
||||
const now = new Date().getTime();
|
||||
return Math.floor((now - createTime) / (24 * 60 * 60 * 1000));
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查缓存是否有效
|
||||
*/
|
||||
isCacheValid: (state): boolean => {
|
||||
if (!state.lastUpdateTime) return false;
|
||||
const now = Date.now();
|
||||
return (now - state.lastUpdateTime) < state.cacheExpiry;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 获取网站信息
|
||||
* @param forceRefresh 是否强制刷新
|
||||
*/
|
||||
async fetchSiteInfo(forceRefresh = false) {
|
||||
// 如果缓存有效且不强制刷新,直接返回缓存数据
|
||||
if (!forceRefresh && this.isCacheValid && this.siteInfo) {
|
||||
return this.siteInfo;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const data = await getSiteInfo();
|
||||
this.siteInfo = data;
|
||||
this.lastUpdateTime = Date.now();
|
||||
|
||||
// 更新localStorage中的相关信息
|
||||
if (data.websiteId) {
|
||||
localStorage.setItem('WebsiteId', String(data.websiteId));
|
||||
}
|
||||
if (data.domain) {
|
||||
localStorage.setItem('Domain', data.domain);
|
||||
localStorage.setItem('SiteUrl', `${data.prefix || 'https://'}${data.domain}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('获取网站信息失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新网站信息
|
||||
*/
|
||||
updateSiteInfo(siteInfo: Partial<CmsWebsite>) {
|
||||
if (this.siteInfo) {
|
||||
this.siteInfo = { ...this.siteInfo, ...siteInfo };
|
||||
this.lastUpdateTime = Date.now();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.siteInfo = null;
|
||||
this.lastUpdateTime = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置缓存有效期
|
||||
*/
|
||||
setCacheExpiry(expiry: number) {
|
||||
this.cacheExpiry = expiry;
|
||||
}
|
||||
}
|
||||
});
|
||||
222
src/store/modules/statistics.ts
Normal file
222
src/store/modules/statistics.ts
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* 统计数据 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { pageShopOrder, shopOrderTotal } from '@/api/shop/shopOrder';
|
||||
import { addCmsStatistics, listCmsStatistics, updateCmsStatistics } from '@/api/cms/cmsStatistics';
|
||||
import { CmsStatistics } from '@/api/cms/cmsStatistics/model';
|
||||
import { safeNumber, hasValidId, isValidApiResponse } from '@/utils/type-guards';
|
||||
|
||||
export interface StatisticsState {
|
||||
// 统计数据
|
||||
statistics: CmsStatistics | null;
|
||||
// 加载状态
|
||||
loading: boolean;
|
||||
// 最后更新时间
|
||||
lastUpdateTime: number | null;
|
||||
// 缓存有效期(毫秒)- 统计数据缓存时间较短
|
||||
cacheExpiry: number;
|
||||
// 自动刷新定时器
|
||||
refreshTimer: number | null;
|
||||
}
|
||||
|
||||
export const useStatisticsStore = defineStore({
|
||||
id: 'statistics',
|
||||
state: (): StatisticsState => ({
|
||||
statistics: null,
|
||||
loading: false,
|
||||
lastUpdateTime: null,
|
||||
// 默认缓存5分钟
|
||||
cacheExpiry: 5 * 60 * 1000,
|
||||
refreshTimer: null
|
||||
}),
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* 获取用户总数
|
||||
*/
|
||||
userCount: (state): number => {
|
||||
return safeNumber(state.statistics?.userCount);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取订单总数
|
||||
*/
|
||||
orderCount: (state): number => {
|
||||
return safeNumber(state.statistics?.orderCount);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取总销售额
|
||||
*/
|
||||
totalSales: (state): number => {
|
||||
return safeNumber(state.statistics?.totalSales);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取今日销售额
|
||||
*/
|
||||
todaySales: (state): number => {
|
||||
return safeNumber(state.statistics?.todaySales);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取本月销售额
|
||||
*/
|
||||
monthSales: (state): number => {
|
||||
return safeNumber(state.statistics?.monthSales);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取今日订单数
|
||||
*/
|
||||
todayOrders: (state): number => {
|
||||
return safeNumber(state.statistics?.todayOrders);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取今日新增用户
|
||||
*/
|
||||
todayUsers: (state): number => {
|
||||
return safeNumber(state.statistics?.todayUsers);
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查缓存是否有效
|
||||
*/
|
||||
isCacheValid: (state): boolean => {
|
||||
if (!state.lastUpdateTime) return false;
|
||||
const now = Date.now();
|
||||
return (now - state.lastUpdateTime) < state.cacheExpiry;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 获取统计数据
|
||||
* @param forceRefresh 是否强制刷新
|
||||
*/
|
||||
async fetchStatistics(forceRefresh = false) {
|
||||
// 如果缓存有效且不强制刷新,直接返回缓存数据
|
||||
if (!forceRefresh && this.isCacheValid && this.statistics) {
|
||||
return this.statistics;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
// 并行获取各种统计数据
|
||||
const [users, orders, total, statisticsData] = await Promise.all([
|
||||
pageUsers({}),
|
||||
pageShopOrder({}),
|
||||
shopOrderTotal(),
|
||||
listCmsStatistics({})
|
||||
]);
|
||||
|
||||
let statistics: CmsStatistics;
|
||||
|
||||
if (statisticsData && statisticsData.length > 0) {
|
||||
// 更新现有统计数据
|
||||
const existingStatistics = statisticsData[0];
|
||||
|
||||
// 确保数据存在且有有效的 ID
|
||||
if (hasValidId(existingStatistics)) {
|
||||
const updateData: Partial<CmsStatistics> = {
|
||||
id: existingStatistics.id,
|
||||
userCount: safeNumber(isValidApiResponse(users) ? users.count : 0),
|
||||
orderCount: safeNumber(isValidApiResponse(orders) ? orders.count : 0),
|
||||
totalSales: safeNumber(total),
|
||||
};
|
||||
|
||||
// 异步更新数据库
|
||||
setTimeout(() => {
|
||||
updateCmsStatistics(updateData).catch((error) => {
|
||||
console.error('更新统计数据失败:', error);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// 更新本地数据
|
||||
statistics = { ...existingStatistics, ...updateData };
|
||||
} else {
|
||||
// 如果现有数据无效,使用基础数据
|
||||
statistics = {
|
||||
userCount: safeNumber(isValidApiResponse(users) ? users.count : 0),
|
||||
orderCount: safeNumber(isValidApiResponse(orders) ? orders.count : 0),
|
||||
totalSales: safeNumber(total),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 创建新的统计数据
|
||||
statistics = {
|
||||
userCount: safeNumber(isValidApiResponse(users) ? users.count : 0),
|
||||
orderCount: safeNumber(isValidApiResponse(orders) ? orders.count : 0),
|
||||
totalSales: safeNumber(total),
|
||||
};
|
||||
|
||||
// 异步保存到数据库
|
||||
setTimeout(() => {
|
||||
addCmsStatistics(statistics).catch((error) => {
|
||||
console.error('保存统计数据失败:', error);
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
this.statistics = statistics;
|
||||
this.lastUpdateTime = Date.now();
|
||||
|
||||
return statistics;
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新统计数据
|
||||
*/
|
||||
updateStatistics(statistics: Partial<CmsStatistics>) {
|
||||
if (this.statistics) {
|
||||
this.statistics = { ...this.statistics, ...statistics };
|
||||
this.lastUpdateTime = Date.now();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.statistics = null;
|
||||
this.lastUpdateTime = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置缓存有效期
|
||||
*/
|
||||
setCacheExpiry(expiry: number) {
|
||||
this.cacheExpiry = expiry;
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始自动刷新
|
||||
* @param interval 刷新间隔(毫秒),默认5分钟
|
||||
*/
|
||||
startAutoRefresh(interval = 5 * 60 * 1000) {
|
||||
this.stopAutoRefresh();
|
||||
this.refreshTimer = window.setInterval(() => {
|
||||
this.fetchStatistics(true).catch(console.error);
|
||||
}, interval);
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止自动刷新
|
||||
*/
|
||||
stopAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
105
src/utils/type-guards.ts
Normal file
105
src/utils/type-guards.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 类型保护工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 检查值是否为有效的数字
|
||||
*/
|
||||
export function isValidNumber(value: unknown): value is number {
|
||||
return typeof value === 'number' && !isNaN(value) && isFinite(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查值是否为有效的字符串
|
||||
*/
|
||||
export function isValidString(value: unknown): value is string {
|
||||
return typeof value === 'string' && value.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查对象是否有有效的 ID
|
||||
*/
|
||||
export function hasValidId(obj: unknown): obj is { id: number } {
|
||||
return (
|
||||
typeof obj === 'object' &&
|
||||
obj !== null &&
|
||||
'id' in obj &&
|
||||
isValidNumber((obj as any).id)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全获取数字值,提供默认值
|
||||
*/
|
||||
export function safeNumber(value: unknown, defaultValue = 0): number {
|
||||
if (isValidNumber(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const parsed = Number(value);
|
||||
return isValidNumber(parsed) ? parsed : defaultValue;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全获取字符串值,提供默认值
|
||||
*/
|
||||
export function safeString(value: unknown, defaultValue = ''): string {
|
||||
return typeof value === 'string' ? value : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 API 响应是否有效
|
||||
*/
|
||||
export function isValidApiResponse<T>(response: unknown): response is { count: number; list?: T[] } {
|
||||
return (
|
||||
typeof response === 'object' &&
|
||||
response !== null &&
|
||||
'count' in response &&
|
||||
isValidNumber((response as any).count)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查统计数据是否有效
|
||||
*/
|
||||
export function isValidStatistics(data: unknown): data is {
|
||||
id?: number;
|
||||
userCount?: number;
|
||||
orderCount?: number;
|
||||
totalSales?: number;
|
||||
} {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的对象合并,过滤掉 undefined 值
|
||||
*/
|
||||
export function safeMerge<T extends Record<string, any>>(
|
||||
target: T,
|
||||
source: Partial<T>
|
||||
): T {
|
||||
const result = { ...target };
|
||||
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (value !== undefined) {
|
||||
result[key as keyof T] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建带有默认值的对象
|
||||
*/
|
||||
export function withDefaults<T extends Record<string, any>>(
|
||||
data: Partial<T>,
|
||||
defaults: T
|
||||
): T {
|
||||
return safeMerge(defaults, data);
|
||||
}
|
||||
@@ -11,20 +11,20 @@
|
||||
:height="80"
|
||||
:preview="false"
|
||||
style="border-radius: 8px"
|
||||
:src="siteInfo.websiteLogo"
|
||||
:src="siteStore.websiteLogo"
|
||||
fallback="/logo.png"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="14">
|
||||
<div class="system-info">
|
||||
<h2 class="ele-text-heading">{{ siteInfo.websiteName }}</h2>
|
||||
<p class="ele-text-secondary">{{ siteInfo.comments }}</p>
|
||||
<h2 class="ele-text-heading">{{ siteStore.websiteName }}</h2>
|
||||
<p class="ele-text-secondary">{{ siteStore.websiteComments }}</p>
|
||||
<a-space>
|
||||
<a-tag color="blue">版本 {{ systemInfo.version }}</a-tag>
|
||||
<a-tag color="green">{{ systemInfo.status }}</a-tag>
|
||||
<a-popover title="小程序码">
|
||||
<template #content>
|
||||
<p><img :src="siteInfo.websiteDarkLogo" alt="小程序码" width="300" height="300"></p>
|
||||
<p><img :src="siteStore.websiteDarkLogo" alt="小程序码" width="300" height="300"></p>
|
||||
</template>
|
||||
<a-tag>
|
||||
<QrcodeOutlined/>
|
||||
@@ -47,8 +47,9 @@
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="用户总数"
|
||||
:value="form.userCount"
|
||||
:value="userCount"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<UserOutlined/>
|
||||
@@ -61,8 +62,9 @@
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="订单总数"
|
||||
:value="form.orderCount"
|
||||
:value="orderCount"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<AccountBookOutlined/>
|
||||
@@ -75,8 +77,9 @@
|
||||
<a-card :bordered="false" class="stat-card">
|
||||
<a-statistic
|
||||
title="总营业额"
|
||||
:value="form.totalSales"
|
||||
:value="totalSales"
|
||||
:value-style="{ color: '#cf1322' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<MoneyCollectOutlined/>
|
||||
@@ -92,6 +95,7 @@
|
||||
:value="runDays"
|
||||
suffix="天"
|
||||
:value-style="{ color: '#722ed1' }"
|
||||
:loading="loading"
|
||||
>
|
||||
<template #prefix>
|
||||
<ClockCircleOutlined/>
|
||||
@@ -164,7 +168,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, reactive, onMounted} from 'vue';
|
||||
import {ref, onMounted, onUnmounted, computed} from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
CalendarOutlined,
|
||||
@@ -176,18 +180,18 @@ import {
|
||||
FileTextOutlined,
|
||||
MoneyCollectOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {assignObject} from 'ele-admin-pro';
|
||||
import {getSiteInfo} from "@/api/layout";
|
||||
import {CmsWebsite} from "@/api/cms/cmsWebsite/model";
|
||||
import {pageUsers} from "@/api/system/user";
|
||||
import {addShopOrder, pageShopOrder, shopOrderTotal} from "@/api/shop/shopOrder";
|
||||
import {openNew} from "@/utils/common";
|
||||
import {addCmsStatistics, listCmsStatistics, updateCmsStatistics} from "@/api/cms/cmsStatistics";
|
||||
import {CmsStatistics} from "@/api/cms/cmsStatistics/model";
|
||||
import {addCmsArticle, updateCmsArticle} from "@/api/cms/cmsArticle";
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import { useStatisticsStore } from '@/store/modules/statistics';
|
||||
import { storeToRefs } from 'pinia';
|
||||
|
||||
// 当前小程序项目
|
||||
const siteInfo = ref<CmsWebsite>({});
|
||||
// 使用状态管理
|
||||
const siteStore = useSiteStore();
|
||||
const statisticsStore = useStatisticsStore();
|
||||
|
||||
// 从 store 中获取响应式数据
|
||||
const { siteInfo, loading: siteLoading } = storeToRefs(siteStore);
|
||||
const { loading: statisticsLoading } = storeToRefs(statisticsStore);
|
||||
|
||||
// 系统信息
|
||||
const systemInfo = ref({
|
||||
@@ -202,118 +206,34 @@ const systemInfo = ref({
|
||||
expirationTime: '2024-01-01 09:00:00'
|
||||
});
|
||||
|
||||
const runDays = ref<number>(0)
|
||||
// 计算属性
|
||||
const runDays = computed(() => siteStore.runDays);
|
||||
const userCount = computed(() => statisticsStore.userCount);
|
||||
const orderCount = computed(() => statisticsStore.orderCount);
|
||||
const totalSales = computed(() => statisticsStore.totalSales);
|
||||
|
||||
// 统计数据
|
||||
const form = reactive<CmsStatistics>({
|
||||
websiteId: undefined,
|
||||
// 用户总数
|
||||
userCount: undefined,
|
||||
// 订单总数
|
||||
orderCount: undefined,
|
||||
// 商品总数
|
||||
productCount: undefined,
|
||||
// 总销售额
|
||||
totalSales: undefined,
|
||||
// 本月销售额
|
||||
monthSales: undefined,
|
||||
// 今日销售额
|
||||
todaySales: undefined,
|
||||
// 昨日销售额
|
||||
yesterdaySales: undefined,
|
||||
// 本周销售额
|
||||
weekSales: undefined,
|
||||
// 本年销售额
|
||||
yearSales: undefined,
|
||||
// 今日订单数
|
||||
todayOrders: undefined,
|
||||
// 本月订单数
|
||||
monthOrders: undefined,
|
||||
// 今日新增用户
|
||||
todayUsers: undefined,
|
||||
// 本月新增用户
|
||||
monthUsers: undefined,
|
||||
// 今日访问量
|
||||
todayVisits: undefined,
|
||||
// 总访问量
|
||||
totalVisits: undefined,
|
||||
// 商户总数
|
||||
merchantCount: undefined,
|
||||
// 活跃用户数
|
||||
activeUsers: undefined,
|
||||
// 转化率(%)
|
||||
conversionRate: undefined,
|
||||
// 平均订单金额
|
||||
avgOrderAmount: undefined,
|
||||
// 统计日期
|
||||
statisticsDate: undefined,
|
||||
// 统计类型: 1日统计, 2月统计, 3年统计
|
||||
statisticsType: undefined,
|
||||
// 运行天数
|
||||
runDays: undefined,
|
||||
// 排序号
|
||||
sortNumber: undefined,
|
||||
// 操作用户ID
|
||||
userId: undefined,
|
||||
// 商户ID
|
||||
merchantId: undefined,
|
||||
// 状态: 0禁用, 1启用
|
||||
status: undefined,
|
||||
// 是否删除: 0否, 1是
|
||||
deleted: undefined,
|
||||
// 租户ID
|
||||
tenantId: undefined,
|
||||
// 创建时间
|
||||
createTime: undefined,
|
||||
// 修改时间
|
||||
updateTime: undefined,
|
||||
// 加载状态
|
||||
const loading = computed(() => siteLoading.value || statisticsLoading.value);
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载网站信息和统计数据
|
||||
try {
|
||||
await Promise.all([
|
||||
siteStore.fetchSiteInfo(),
|
||||
statisticsStore.fetchStatistics()
|
||||
]);
|
||||
|
||||
// 开始自动刷新统计数据(每5分钟)
|
||||
statisticsStore.startAutoRefresh();
|
||||
} catch (error) {
|
||||
console.error('加载数据失败:', error);
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
// 加载系统信息和统计数据
|
||||
loadSystemInfo();
|
||||
loadStatistics();
|
||||
onUnmounted(() => {
|
||||
// 组件卸载时停止自动刷新
|
||||
statisticsStore.stopAutoRefresh();
|
||||
});
|
||||
|
||||
const loadSystemInfo = async () => {
|
||||
// TODO: 调用API获取系统信息
|
||||
siteInfo.value = await getSiteInfo();
|
||||
if (siteInfo.value.createTime) {
|
||||
// 根据创建时间计算运行天数
|
||||
runDays.value = Math.floor((new Date().getTime() - new Date(siteInfo.value.createTime).getTime()) / (24 * 60 * 60 * 1000))
|
||||
}
|
||||
};
|
||||
|
||||
const loadStatistics = async () => {
|
||||
// TODO: 调用API获取统计数据
|
||||
const users = await pageUsers({})
|
||||
const orders = await pageShopOrder({})
|
||||
const total = await shopOrderTotal()
|
||||
const data = await listCmsStatistics({});
|
||||
|
||||
// 获取统计表数据
|
||||
if (data) {
|
||||
const saveOrUpdate = data.length > 0 ? updateCmsStatistics : addCmsStatistics;
|
||||
if (data.length > 0) {
|
||||
const saveData = data[0]
|
||||
assignObject(form, saveData);
|
||||
// 更新数据
|
||||
setTimeout(() => {
|
||||
if (saveData && users && orders) {
|
||||
const id = saveData.id
|
||||
saveOrUpdate({
|
||||
id,
|
||||
userCount: users.count,
|
||||
orderCount: orders.count,
|
||||
totalSales: Number(total),
|
||||
})
|
||||
}
|
||||
},2000)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -164,7 +164,7 @@ import {
|
||||
} from "@/utils/domain";
|
||||
import {openNew, openPreview} from "@/utils/common";
|
||||
import {FileRecord} from "@/api/system/file/model";
|
||||
import {getSiteInfo} from "@/api/layout";
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
|
||||
|
||||
const useForm = Form.useForm;
|
||||
@@ -173,6 +173,7 @@ import {getSiteInfo} from "@/api/layout";
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const userStore = useUserStore();
|
||||
const siteStore = useSiteStore();
|
||||
// 当前开发环境
|
||||
const env = import.meta.env.MODE;
|
||||
// tab 页选中
|
||||
@@ -326,21 +327,28 @@ const onDeleteItem = (index: number) => {
|
||||
form.websiteLogo = '';
|
||||
}
|
||||
|
||||
const query = () => {
|
||||
const query = async () => {
|
||||
logo.value = [];
|
||||
getSiteInfo().then(data => {
|
||||
assignFields(data)
|
||||
logo.value.push({
|
||||
uid: 1,
|
||||
url: data.websiteLogo,
|
||||
status: 'done'
|
||||
});
|
||||
darkLogo.value.push({
|
||||
uid: 1,
|
||||
url: data.websiteDarkLogo,
|
||||
status: 'done'
|
||||
})
|
||||
})
|
||||
try {
|
||||
const data = await siteStore.fetchSiteInfo();
|
||||
assignFields(data);
|
||||
if (data.websiteLogo) {
|
||||
logo.value.push({
|
||||
uid: 1,
|
||||
url: data.websiteLogo,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
if (data.websiteDarkLogo) {
|
||||
darkLogo.value.push({
|
||||
uid: 1,
|
||||
url: data.websiteDarkLogo,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取网站信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
query();
|
||||
|
||||
@@ -136,7 +136,7 @@ import {updateCmsDomain} from '@/api/cms/cmsDomain';
|
||||
import {updateTenant} from "@/api/system/tenant";
|
||||
import {getPageTitle, push} from "@/utils/common";
|
||||
import router from "@/router";
|
||||
import {getSiteInfo} from "@/api/layout";
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import useFormData from "@/utils/use-form-data";
|
||||
import type {User} from "@/api/system/user/model";
|
||||
|
||||
@@ -146,6 +146,7 @@ const useForm = Form.useForm;
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const {styleResponsive} = storeToRefs(themeStore);
|
||||
const siteStore = useSiteStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
@@ -349,19 +350,23 @@ const save = () => {
|
||||
};
|
||||
|
||||
const reload = async () => {
|
||||
const data = await getSiteInfo()
|
||||
if (data) {
|
||||
console.log(data)
|
||||
assignFields({
|
||||
...data
|
||||
});
|
||||
images.value.push(
|
||||
{
|
||||
uid: uuid(),
|
||||
url: data.websiteLogo,
|
||||
status: 'done'
|
||||
try {
|
||||
const data = await siteStore.fetchSiteInfo();
|
||||
if (data) {
|
||||
console.log(data);
|
||||
assignFields({
|
||||
...data
|
||||
});
|
||||
if (data.websiteLogo) {
|
||||
images.value.push({
|
||||
uid: uuid(),
|
||||
url: data.websiteLogo,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取网站信息失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
263
src/views/test/store-test.vue
Normal file
263
src/views/test/store-test.vue
Normal file
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div class="store-test-page">
|
||||
<a-card title="状态管理测试页面" :bordered="false">
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
|
||||
<!-- 网站信息测试 -->
|
||||
<a-card title="网站信息 Store 测试" size="small">
|
||||
<a-spin :spinning="siteLoading">
|
||||
<a-descriptions :column="2" size="small">
|
||||
<a-descriptions-item label="网站名称">
|
||||
{{ websiteName || '暂无数据' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网站Logo">
|
||||
<img v-if="websiteLogo" :src="websiteLogo" alt="logo" style="width: 50px; height: 50px;" />
|
||||
<span v-else>暂无Logo</span>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网站描述">
|
||||
{{ websiteComments || '暂无描述' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="运行天数">
|
||||
{{ runDays }} 天
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网站域名">
|
||||
{{ websiteDomain || '暂无域名' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网站ID">
|
||||
{{ websiteId || '暂无ID' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-space style="margin-top: 16px">
|
||||
<a-button @click="refreshSiteInfo" :loading="siteLoading">
|
||||
刷新网站信息
|
||||
</a-button>
|
||||
<a-button @click="clearSiteCache">
|
||||
清除网站缓存
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
|
||||
<!-- 统计数据测试 -->
|
||||
<a-card title="统计数据 Store 测试" size="small">
|
||||
<a-spin :spinning="statisticsLoading">
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<a-statistic
|
||||
title="用户总数"
|
||||
:value="userCount"
|
||||
:value-style="{ color: '#3f8600' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic
|
||||
title="订单总数"
|
||||
:value="orderCount"
|
||||
:value-style="{ color: '#1890ff' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic
|
||||
title="总销售额"
|
||||
:value="totalSales"
|
||||
:value-style="{ color: '#cf1322' }"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<a-statistic
|
||||
title="今日销售额"
|
||||
:value="todaySales"
|
||||
:value-style="{ color: '#722ed1' }"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-space style="margin-top: 16px">
|
||||
<a-button @click="refreshStatistics" :loading="statisticsLoading">
|
||||
刷新统计数据
|
||||
</a-button>
|
||||
<a-button @click="clearStatisticsCache">
|
||||
清除统计缓存
|
||||
</a-button>
|
||||
<a-button
|
||||
@click="toggleAutoRefresh"
|
||||
:type="autoRefreshEnabled ? 'primary' : 'default'"
|
||||
>
|
||||
{{ autoRefreshEnabled ? '停止自动刷新' : '开始自动刷新' }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
|
||||
<!-- 缓存状态信息 -->
|
||||
<a-card title="缓存状态信息" size="small">
|
||||
<a-descriptions :column="2" size="small">
|
||||
<a-descriptions-item label="网站信息缓存有效">
|
||||
<a-tag :color="siteStore.isCacheValid ? 'green' : 'red'">
|
||||
{{ siteStore.isCacheValid ? '有效' : '无效' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="统计数据缓存有效">
|
||||
<a-tag :color="statisticsStore.isCacheValid ? 'green' : 'red'">
|
||||
{{ statisticsStore.isCacheValid ? '有效' : '无效' }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="网站信息最后更新">
|
||||
{{ siteStore.lastUpdateTime ? new Date(siteStore.lastUpdateTime).toLocaleString() : '从未更新' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="统计数据最后更新">
|
||||
{{ statisticsStore.lastUpdateTime ? new Date(statisticsStore.lastUpdateTime).toLocaleString() : '从未更新' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
|
||||
<!-- 操作日志 -->
|
||||
<a-card title="操作日志" size="small">
|
||||
<a-list
|
||||
:data-source="logs"
|
||||
size="small"
|
||||
:pagination="{ pageSize: 5 }"
|
||||
>
|
||||
<template #renderItem="{ item }">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #title>
|
||||
<span>{{ item.action }}</span>
|
||||
<a-tag size="small" style="margin-left: 8px">
|
||||
{{ item.timestamp }}
|
||||
</a-tag>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ item.result }}
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-card>
|
||||
|
||||
</a-space>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { useSiteData } from '@/composables/useSiteData';
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import { useStatisticsStore } from '@/store/modules/statistics';
|
||||
|
||||
// 使用组合式函数
|
||||
const {
|
||||
websiteName,
|
||||
websiteLogo,
|
||||
websiteComments,
|
||||
websiteDomain,
|
||||
websiteId,
|
||||
runDays,
|
||||
userCount,
|
||||
orderCount,
|
||||
totalSales,
|
||||
todaySales,
|
||||
siteLoading,
|
||||
statisticsLoading,
|
||||
fetchSiteInfo,
|
||||
fetchStatistics,
|
||||
clearCache
|
||||
} = useSiteData();
|
||||
|
||||
// 直接使用 store(用于访问更多详细信息)
|
||||
const siteStore = useSiteStore();
|
||||
const statisticsStore = useStatisticsStore();
|
||||
|
||||
// 本地状态
|
||||
const autoRefreshEnabled = ref(false);
|
||||
const logs = ref<Array<{ action: string; timestamp: string; result: string }>>([]);
|
||||
|
||||
// 添加日志
|
||||
const addLog = (action: string, result: string) => {
|
||||
logs.value.unshift({
|
||||
action,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
result
|
||||
});
|
||||
// 只保留最近20条日志
|
||||
if (logs.value.length > 20) {
|
||||
logs.value = logs.value.slice(0, 20);
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新网站信息
|
||||
const refreshSiteInfo = async () => {
|
||||
try {
|
||||
await fetchSiteInfo(true);
|
||||
addLog('刷新网站信息', '成功');
|
||||
} catch (error) {
|
||||
addLog('刷新网站信息', `失败: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 刷新统计数据
|
||||
const refreshStatistics = async () => {
|
||||
try {
|
||||
await fetchStatistics(true);
|
||||
addLog('刷新统计数据', '成功');
|
||||
} catch (error) {
|
||||
addLog('刷新统计数据', `失败: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
// 清除网站缓存
|
||||
const clearSiteCache = () => {
|
||||
siteStore.clearCache();
|
||||
addLog('清除网站缓存', '成功');
|
||||
};
|
||||
|
||||
// 清除统计缓存
|
||||
const clearStatisticsCache = () => {
|
||||
statisticsStore.clearCache();
|
||||
addLog('清除统计缓存', '成功');
|
||||
};
|
||||
|
||||
// 切换自动刷新
|
||||
const toggleAutoRefresh = () => {
|
||||
if (autoRefreshEnabled.value) {
|
||||
statisticsStore.stopAutoRefresh();
|
||||
autoRefreshEnabled.value = false;
|
||||
addLog('停止自动刷新', '成功');
|
||||
} else {
|
||||
statisticsStore.startAutoRefresh(10000); // 10秒间隔用于测试
|
||||
autoRefreshEnabled.value = true;
|
||||
addLog('开始自动刷新', '10秒间隔');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
addLog('页面加载', '开始初始化');
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchSiteInfo(),
|
||||
fetchStatistics()
|
||||
]);
|
||||
addLog('初始化数据', '成功');
|
||||
} catch (error) {
|
||||
addLog('初始化数据', `失败: ${error}`);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
// 清理自动刷新
|
||||
if (autoRefreshEnabled.value) {
|
||||
statisticsStore.stopAutoRefresh();
|
||||
}
|
||||
addLog('页面卸载', '清理完成');
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.store-test-page {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user