chore(config): 添加项目配置文件和隐私协议
- 添加 .editorconfig 文件统一代码风格 - 添加 .env.development 和 .env.example 环境变量配置 - 添加 .eslintignore 和 .eslintrc.js 代码检查配置 - 添加 .gitignore 版本控制忽略文件配置 - 添加 .prettierignore 代码格式化忽略配置 - 添加隐私协议 HTML 文件 - 添加 accesskey 编辑组件基础结构
This commit is contained in:
6
src/store/index.ts
Normal file
6
src/store/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* pinia
|
||||
*/
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
export default createPinia();
|
||||
141
src/store/modules/bszx-statistics.ts
Normal file
141
src/store/modules/bszx-statistics.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 百色中学统计数据 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { bszxOrderTotal } from '@/api/bszx/bszxOrder';
|
||||
import { safeNumber } from '@/utils/type-guards';
|
||||
|
||||
export interface BszxStatisticsState {
|
||||
// 总营业额
|
||||
totalPrice: number;
|
||||
// 加载状态
|
||||
loading: boolean;
|
||||
// 最后更新时间
|
||||
lastUpdateTime: number | null;
|
||||
// 缓存有效期(毫秒)- 5分钟缓存
|
||||
cacheExpiry: number;
|
||||
// 自动刷新定时器
|
||||
refreshTimer: number | null;
|
||||
}
|
||||
|
||||
export const useBszxStatisticsStore = defineStore({
|
||||
id: 'bszx-statistics',
|
||||
state: (): BszxStatisticsState => ({
|
||||
totalPrice: 0,
|
||||
loading: false,
|
||||
lastUpdateTime: null,
|
||||
// 默认缓存5分钟
|
||||
cacheExpiry: 5 * 60 * 1000,
|
||||
refreshTimer: null
|
||||
}),
|
||||
|
||||
getters: {
|
||||
/**
|
||||
* 获取总营业额
|
||||
*/
|
||||
bszxTotalPrice: (state): number => {
|
||||
return safeNumber(state.totalPrice);
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查缓存是否有效
|
||||
*/
|
||||
isCacheValid: (state): boolean => {
|
||||
if (!state.lastUpdateTime) return false;
|
||||
const now = Date.now();
|
||||
return (now - state.lastUpdateTime) < state.cacheExpiry;
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 获取百色中学统计数据
|
||||
* @param forceRefresh 是否强制刷新
|
||||
*/
|
||||
async fetchBszxStatistics(forceRefresh = false) {
|
||||
// 如果缓存有效且不强制刷新,直接返回缓存数据
|
||||
if (!forceRefresh && this.isCacheValid && this.totalPrice > 0) {
|
||||
return this.totalPrice;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
try {
|
||||
const result = await bszxOrderTotal();
|
||||
|
||||
// 处理返回的数据 - bszxOrderTotal 返回 ShopOrder[] 数组
|
||||
let totalPrice = 0;
|
||||
if (Array.isArray(result)) {
|
||||
// 累加所有订单的金额
|
||||
result.forEach((order: any) => {
|
||||
if (order.payPrice) {
|
||||
totalPrice += safeNumber(order.payPrice);
|
||||
} else if (order.totalPrice) {
|
||||
totalPrice += safeNumber(order.totalPrice);
|
||||
}
|
||||
});
|
||||
} else if (typeof result === 'number') {
|
||||
totalPrice = result;
|
||||
} else if (typeof result === 'string') {
|
||||
totalPrice = safeNumber(result);
|
||||
} else if (result && typeof result === 'object' && 'totalPrice' in result) {
|
||||
totalPrice = safeNumber((result as any).totalPrice);
|
||||
}
|
||||
|
||||
this.totalPrice = totalPrice;
|
||||
this.lastUpdateTime = Date.now();
|
||||
|
||||
return totalPrice;
|
||||
} catch (error) {
|
||||
console.error('获取百色中学统计数据失败:', error);
|
||||
// 发生错误时不重置现有数据,只记录错误
|
||||
throw error;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新统计数据
|
||||
*/
|
||||
updateStatistics(data: Partial<BszxStatisticsState>) {
|
||||
Object.assign(this, data);
|
||||
this.lastUpdateTime = Date.now();
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.totalPrice = 0;
|
||||
this.lastUpdateTime = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置缓存有效期
|
||||
*/
|
||||
setCacheExpiry(expiry: number) {
|
||||
this.cacheExpiry = expiry;
|
||||
},
|
||||
|
||||
/**
|
||||
* 开始自动刷新
|
||||
* @param interval 刷新间隔(毫秒),默认5分钟
|
||||
*/
|
||||
startAutoRefresh(interval = 5 * 60 * 1000) {
|
||||
this.stopAutoRefresh();
|
||||
this.refreshTimer = window.setInterval(() => {
|
||||
this.fetchBszxStatistics(true).catch(console.error);
|
||||
}, interval);
|
||||
},
|
||||
|
||||
/**
|
||||
* 停止自动刷新
|
||||
*/
|
||||
stopAutoRefresh() {
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
134
src/store/modules/chat.ts
Normal file
134
src/store/modules/chat.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { getToken } from '@/utils/token-util';
|
||||
import { ChatConversation, ChatMessage } from '@/api/system/chat/model';
|
||||
import {
|
||||
pageChatConversation,
|
||||
updateChatConversation
|
||||
} from '@/api/system/chat';
|
||||
import { emitter } from '@/utils/common';
|
||||
|
||||
const SOCKET_URL: string = 'wss://server.websoft.top';
|
||||
|
||||
interface ConnectionOptions {
|
||||
token: string;
|
||||
userId: number;
|
||||
isAdmin: boolean;
|
||||
}
|
||||
|
||||
export interface ChatState {
|
||||
socket: Socket | undefined;
|
||||
conversations: ChatConversation[];
|
||||
}
|
||||
|
||||
export const useChatStore = defineStore({
|
||||
id: 'chat',
|
||||
state: (): ChatState => ({
|
||||
socket: undefined,
|
||||
conversations: []
|
||||
}),
|
||||
getters: {
|
||||
unReadLetter(): number {
|
||||
return this.conversations.reduce((count, item) => count + item.unRead, 0);
|
||||
},
|
||||
unReadConversations(): ChatConversation[] {
|
||||
return this.conversations
|
||||
.filter((item) => item.unRead > 0)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
new Date(b.updateTime).getTime() - new Date(a.updateTime).getTime()
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
readConversation(id) {
|
||||
const index = this.conversations.findIndex((item) => item.id === id);
|
||||
if (index >= 0) {
|
||||
updateChatConversation({
|
||||
id: this.conversations[index].id,
|
||||
unRead: 0
|
||||
});
|
||||
this.conversations.splice(index, 1);
|
||||
}
|
||||
},
|
||||
async connectSocketIO(userId: number) {
|
||||
console.log(
|
||||
'---------------------------------connectSocketIO----------------------------------'
|
||||
);
|
||||
const options: ConnectionOptions = {
|
||||
token: getToken() || '',
|
||||
userId: userId,
|
||||
isAdmin: true
|
||||
};
|
||||
|
||||
const socket: Socket = io(SOCKET_URL, {
|
||||
query: options,
|
||||
transports: ['websocket', 'polling'],
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
this.socket = socket;
|
||||
console.log(
|
||||
'---------------------------------socket connect----------------------------------'
|
||||
);
|
||||
// 获取聊天列表
|
||||
pageChatConversation({
|
||||
keywords: '',
|
||||
status: 1,
|
||||
page: 1,
|
||||
limit: 100,
|
||||
onlyFake: true
|
||||
}).then((res) => {
|
||||
if (res?.list) {
|
||||
this.conversations = res.list;
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(
|
||||
'---------------------------------socket----------------------------------',
|
||||
socket
|
||||
);
|
||||
console.log('收到socket消息>>>');
|
||||
// 收到新消息
|
||||
socket.on('message', (data: ChatMessage) => {
|
||||
console.log('收到socket消息>>>');
|
||||
const index = this.conversations.findIndex(
|
||||
(item) =>
|
||||
item.friendId === data.formUserId && item.userId === data.toUserId
|
||||
);
|
||||
let content = '';
|
||||
if (data.type == 'image') {
|
||||
content = '图片';
|
||||
} else if (data.type === 'card') {
|
||||
content = '卡片';
|
||||
} else {
|
||||
content = data.content;
|
||||
}
|
||||
if (index >= 0) {
|
||||
this.conversations[index].unRead++;
|
||||
this.conversations[index].content = content;
|
||||
this.conversations[index].updateTime = Date.now();
|
||||
} else {
|
||||
this.conversations.push({
|
||||
content: content,
|
||||
friendInfo: data.formUserInfo,
|
||||
userInfo: data.toUserInfo,
|
||||
messages: [],
|
||||
unRead: 1,
|
||||
updateTime: Date.now(),
|
||||
userId: data.toUserId,
|
||||
friendId: data.formUserId
|
||||
});
|
||||
}
|
||||
|
||||
emitter.emit('message', data);
|
||||
});
|
||||
|
||||
socket.on('connect_error', () => {
|
||||
console.log('connect_error');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
40
src/store/modules/params.ts
Normal file
40
src/store/modules/params.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 接收传参 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export interface ParamsState {
|
||||
title: string | null;
|
||||
comments: string | null;
|
||||
back: string | null;
|
||||
redirect: string | null | undefined;
|
||||
}
|
||||
|
||||
export const useParamsStore = defineStore({
|
||||
id: 'params',
|
||||
state: (): ParamsState => ({
|
||||
// 标题
|
||||
title: '操作成功',
|
||||
// 描述
|
||||
comments: '您的申请已提交',
|
||||
// 当前页面路径
|
||||
back: null,
|
||||
// 跳转的页面
|
||||
redirect: null
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setTitle(value: string) {
|
||||
this.title = value;
|
||||
},
|
||||
setComments(value: string) {
|
||||
this.comments = value;
|
||||
},
|
||||
setBack(value: string) {
|
||||
this.back = value;
|
||||
},
|
||||
setRedirect(value: string) {
|
||||
this.redirect = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
25
src/store/modules/setting.ts
Normal file
25
src/store/modules/setting.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 网站设置 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
export interface ParamsState {
|
||||
setting: any | null;
|
||||
}
|
||||
|
||||
export const useWebsiteSettingStore = defineStore({
|
||||
id: 'setting',
|
||||
state: (): ParamsState => ({
|
||||
// 初始化时确保所有字段都已赋值
|
||||
setting: null,
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
setSetting(value: any) {
|
||||
this.setting = value;
|
||||
},
|
||||
getSetting(value: any){
|
||||
return value;
|
||||
}
|
||||
},
|
||||
});
|
||||
172
src/store/modules/site.ts
Normal file
172
src/store/modules/site.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 应用信息 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { getSiteInfo } from '@/api/layout';
|
||||
import {AppInfo, CmsWebsite} from '@/api/cms/cmsWebsite/model';
|
||||
|
||||
export interface SiteState {
|
||||
// 应用信息
|
||||
siteInfo: AppInfo | 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: {
|
||||
/**
|
||||
* 获取应用ID
|
||||
*/
|
||||
appId: (state): number | undefined => {
|
||||
return state.siteInfo?.appId;
|
||||
},
|
||||
/**
|
||||
* 获取应用名称
|
||||
*/
|
||||
appName: (state): string => {
|
||||
return state.siteInfo?.appName || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取应用Logo
|
||||
*/
|
||||
logo: (state): string => {
|
||||
return state.siteInfo?.logo || '/oa.png';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取应用描述
|
||||
*/
|
||||
description: (state): string => {
|
||||
return state.siteInfo?.description || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取小程序码
|
||||
*/
|
||||
mpQrCode: (state): string => {
|
||||
return state.siteInfo?.mpQrCode || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取应用域名
|
||||
*/
|
||||
domain: (state): string => {
|
||||
return state.siteInfo?.domain || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取应用版本
|
||||
* @param state
|
||||
*/
|
||||
version: (state): string => {
|
||||
if(state.siteInfo?.version == 10){
|
||||
return '基础版'
|
||||
}
|
||||
if(state.siteInfo?.version == 20){
|
||||
return '专业版'
|
||||
}
|
||||
if(state.siteInfo?.version == 30){
|
||||
return '企业版'
|
||||
}
|
||||
return '';
|
||||
},
|
||||
|
||||
statusText: (state): string => {
|
||||
return state.siteInfo?.statusText || '';
|
||||
},
|
||||
|
||||
/**
|
||||
* 计算系统运行天数
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
320
src/store/modules/statistics.ts
Normal file
320
src/store/modules/statistics.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
/**
|
||||
* 统计数据 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 } 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 {
|
||||
// 并行获取各种统计数据,使用Promise.allSettled确保部分失败不影响整体
|
||||
const [usersResult, ordersResult, totalResult, statisticsResult] =
|
||||
await Promise.allSettled([
|
||||
pageUsers({}),
|
||||
pageShopOrder({}),
|
||||
shopOrderTotal(),
|
||||
listCmsStatistics({})
|
||||
]);
|
||||
|
||||
// 安全提取结果
|
||||
const users =
|
||||
usersResult.status === 'fulfilled' ? usersResult.value : null;
|
||||
const orders =
|
||||
ordersResult.status === 'fulfilled' ? ordersResult.value : null;
|
||||
const total =
|
||||
totalResult.status === 'fulfilled' ? totalResult.value : null;
|
||||
const statisticsData =
|
||||
statisticsResult.status === 'fulfilled'
|
||||
? statisticsResult.value
|
||||
: null;
|
||||
|
||||
// 记录失败的API调用
|
||||
if (usersResult.status === 'rejected') {
|
||||
console.error('❌ 用户API调用失败:', usersResult.reason);
|
||||
}
|
||||
if (ordersResult.status === 'rejected') {
|
||||
console.error('❌ 订单API调用失败:', ordersResult.reason);
|
||||
}
|
||||
if (totalResult.status === 'rejected') {
|
||||
console.error('❌ 订单总额API调用失败:', totalResult.reason);
|
||||
}
|
||||
if (statisticsResult.status === 'rejected') {
|
||||
console.error('❌ 统计数据API调用失败:', statisticsResult.reason);
|
||||
}
|
||||
|
||||
// 添加调试日志
|
||||
console.log('🔍 统计数据获取结果:', {
|
||||
users: users,
|
||||
orders: orders,
|
||||
total: total,
|
||||
statisticsData: statisticsData
|
||||
});
|
||||
|
||||
let statistics: CmsStatistics;
|
||||
|
||||
// 安全获取用户数量,添加更详细的验证
|
||||
const userCount = (() => {
|
||||
if (!users) {
|
||||
console.warn('⚠️ 用户API返回空数据');
|
||||
return 0;
|
||||
}
|
||||
if (typeof users === 'object' && 'count' in users) {
|
||||
const count = users.count;
|
||||
console.log('✅ 用户数量:', count);
|
||||
return safeNumber(count);
|
||||
}
|
||||
console.warn('⚠️ 用户API返回数据格式不正确:', users);
|
||||
return 0;
|
||||
})();
|
||||
|
||||
// 安全获取订单数量
|
||||
const orderCount = (() => {
|
||||
if (!orders) {
|
||||
console.warn('⚠️ 订单API返回空数据');
|
||||
return 0;
|
||||
}
|
||||
if (typeof orders === 'object' && 'count' in orders) {
|
||||
const count = orders.count;
|
||||
console.log('✅ 订单数量:', count);
|
||||
return safeNumber(count);
|
||||
}
|
||||
console.warn('⚠️ 订单API返回数据格式不正确:', orders);
|
||||
return 0;
|
||||
})();
|
||||
|
||||
// 安全获取总销售额,处理数组情况
|
||||
const totalSales = (() => {
|
||||
if (!total) {
|
||||
console.warn('⚠️ 订单总额API返回空数据');
|
||||
return 0;
|
||||
}
|
||||
if (Array.isArray(total)) {
|
||||
// 如果是数组,计算总金额
|
||||
const sum = total.reduce((acc, order) => {
|
||||
const amount = order.payPrice || order.totalPrice || 0;
|
||||
return acc + safeNumber(amount);
|
||||
}, 0);
|
||||
console.log('✅ 总销售额(数组计算):', sum);
|
||||
return sum;
|
||||
}
|
||||
const amount = safeNumber(total);
|
||||
console.log('✅ 总销售额(直接值):', amount);
|
||||
return amount;
|
||||
})();
|
||||
|
||||
if (statisticsData && statisticsData.length > 0) {
|
||||
// 更新现有统计数据
|
||||
const existingStatistics = statisticsData[0];
|
||||
|
||||
// 确保数据存在且有有效的 ID
|
||||
if (hasValidId(existingStatistics)) {
|
||||
const updateData: Partial<CmsStatistics> = {
|
||||
id: existingStatistics.id,
|
||||
userCount: userCount,
|
||||
orderCount: orderCount,
|
||||
totalSales: totalSales
|
||||
};
|
||||
|
||||
// 异步更新数据库
|
||||
setTimeout(() => {
|
||||
updateCmsStatistics(updateData).catch((error) => {
|
||||
console.error('更新统计数据失败:', error);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// 更新本地数据
|
||||
statistics = { ...existingStatistics, ...updateData };
|
||||
} else {
|
||||
// 如果现有数据无效,使用基础数据
|
||||
statistics = {
|
||||
userCount: userCount,
|
||||
orderCount: orderCount,
|
||||
totalSales: totalSales
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// 创建新的统计数据
|
||||
statistics = {
|
||||
userCount: userCount,
|
||||
orderCount: orderCount,
|
||||
totalSales: totalSales
|
||||
};
|
||||
|
||||
// 异步保存到数据库
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* 强制刷新统计数据
|
||||
*/
|
||||
async forceRefresh() {
|
||||
console.log('🔄 强制刷新统计数据...');
|
||||
this.clearCache();
|
||||
return await this.fetchStatistics(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置缓存有效期
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
119
src/store/modules/template.ts
Normal file
119
src/store/modules/template.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 网站配置 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import {configWebsiteField} from '@/api/cms/cmsWebsiteField';
|
||||
import type { Config } from '@/api/cms/cmsWebsiteField/model';
|
||||
|
||||
export interface ConfigState {
|
||||
config: Config | null;
|
||||
loading: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export const useConfigStore = defineStore({
|
||||
id: 'config',
|
||||
state: (): ConfigState => ({
|
||||
// 网站配置数据
|
||||
config: null,
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 错误信息
|
||||
error: null
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
* 获取网站配置
|
||||
*/
|
||||
getConfig(state): Config | null {
|
||||
return state.config;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取API地址
|
||||
*/
|
||||
getApiUrl(state): string | undefined {
|
||||
return state.config?.ApiUrl;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站名称
|
||||
*/
|
||||
getSiteName(state): string | undefined {
|
||||
return state.config?.siteName;
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取网站Logo
|
||||
*/
|
||||
getSiteLogo(state): string | undefined {
|
||||
return state.config?.siteLogo;
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 获取网站配置数据
|
||||
*/
|
||||
async fetchConfig() {
|
||||
try {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
const data = await configWebsiteField();
|
||||
this.config = data;
|
||||
|
||||
// 保存到localStorage中,供其他地方使用
|
||||
localStorage.setItem('config', JSON.stringify(data));
|
||||
|
||||
// 设置主题
|
||||
if (data.theme && !localStorage.getItem('user_theme')) {
|
||||
localStorage.setItem('user_theme', data.theme);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
this.error = err instanceof Error ? err : new Error('获取配置失败');
|
||||
console.error('获取网站配置失败:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新配置数据
|
||||
*/
|
||||
setConfig(value: Config) {
|
||||
this.config = value;
|
||||
// 同时更新localStorage
|
||||
localStorage.setItem('config', JSON.stringify(value));
|
||||
},
|
||||
|
||||
/**
|
||||
* 重新获取配置数据
|
||||
*/
|
||||
async refetchConfig() {
|
||||
try {
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
const data = await configWebsiteField();
|
||||
this.config = data;
|
||||
localStorage.setItem('config', JSON.stringify(data));
|
||||
return data;
|
||||
} catch (err) {
|
||||
this.error = err instanceof Error ? err : new Error('获取配置失败');
|
||||
console.error('重新获取网站配置失败:', err);
|
||||
throw err;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 清除配置数据
|
||||
*/
|
||||
clearConfig() {
|
||||
this.config = null;
|
||||
localStorage.removeItem('config');
|
||||
}
|
||||
}
|
||||
});
|
||||
70
src/store/modules/tenant.ts
Normal file
70
src/store/modules/tenant.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 租户信息 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { formatTreeData } from 'ele-admin-pro';
|
||||
import type { MenuItem } from 'ele-admin-pro';
|
||||
import { getTenantInfo } from '@/api/layout';
|
||||
import { Tenant } from '@/api/system/tenant/model';
|
||||
import { Company } from '@/api/system/company/model';
|
||||
// const EXTRA_MENUS: any = [];
|
||||
|
||||
export interface UserState {
|
||||
tenant: Tenant | null;
|
||||
company: Company | null;
|
||||
menus: MenuItem[] | null | undefined;
|
||||
}
|
||||
|
||||
export const useTenantStore = defineStore({
|
||||
id: 'tenant',
|
||||
state: (): UserState => ({
|
||||
// 租户信息
|
||||
tenant: null,
|
||||
// 企业信息
|
||||
company: null,
|
||||
// 当前登录用户的菜单
|
||||
menus: null
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* 请求用户信息、权限、角色、菜单
|
||||
*/
|
||||
async fetchTenantInfo() {
|
||||
const company = await getTenantInfo().catch(() => void 0);
|
||||
if (!company) {
|
||||
return {};
|
||||
}
|
||||
// 租户信息
|
||||
this.company = company;
|
||||
// 企业信息
|
||||
if (company) {
|
||||
this.company = company;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 更新租户信息
|
||||
*/
|
||||
setInfo(value: Tenant) {
|
||||
this.tenant = value;
|
||||
},
|
||||
/**
|
||||
* 更新菜单的 badge
|
||||
*/
|
||||
setMenuBadge(path: string, value?: number | string, color?: string) {
|
||||
this.menus = formatTreeData(this.menus, (m) => {
|
||||
if (path === m.path) {
|
||||
return {
|
||||
...m,
|
||||
meta: {
|
||||
...m.meta,
|
||||
badge: value,
|
||||
badgeColor: color
|
||||
}
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
698
src/store/modules/theme.ts
Normal file
698
src/store/modules/theme.ts
Normal file
@@ -0,0 +1,698 @@
|
||||
/**
|
||||
* 主题状态管理
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import {
|
||||
changeColor,
|
||||
screenWidth,
|
||||
screenHeight,
|
||||
contentWidth,
|
||||
contentHeight,
|
||||
WEAK_CLASS,
|
||||
BODY_LIMIT_CLASS,
|
||||
DISABLES_CLASS
|
||||
} from 'ele-admin-pro/es';
|
||||
import type {
|
||||
TabItem,
|
||||
HeadStyleType,
|
||||
SideStyleType,
|
||||
LayoutStyleType,
|
||||
SideMenuStyleType,
|
||||
TabStyleType,
|
||||
TabRemoveOption
|
||||
} from 'ele-admin-pro/es';
|
||||
import {
|
||||
TAB_KEEP_ALIVE,
|
||||
KEEP_ALIVE_EXCLUDES,
|
||||
THEME_STORE_NAME
|
||||
} from '@/config/setting';
|
||||
// import { getCache } from '@/api/system/cache';
|
||||
|
||||
/**
|
||||
* state 默认值
|
||||
*/
|
||||
const DEFAULT_STATE: ThemeState = Object.freeze({
|
||||
// 页签数据
|
||||
tabs: [],
|
||||
// 是否折叠侧栏
|
||||
collapse: false,
|
||||
// 是否折叠一级侧栏
|
||||
sideNavCollapse: true,
|
||||
// 内容区域是否全屏
|
||||
bodyFullscreen: false,
|
||||
// 是否开启页签栏
|
||||
showTabs: false,
|
||||
// 是否开启页脚
|
||||
showFooter: true,
|
||||
// 顶栏风格: light(亮色), dark(暗色), primary(主色)
|
||||
headStyle: 'light',
|
||||
// 侧栏风格: light(亮色), dark(暗色)
|
||||
sideStyle: 'light',
|
||||
// 布局风格: side(默认), top(顶栏导航), mix(混合导航)
|
||||
layoutStyle: 'mix',
|
||||
// 侧栏菜单风格: default(默认), mix(双排侧栏)
|
||||
sideMenuStyle: 'default',
|
||||
// 页签风格: default(默认), dot(圆点), card(卡片)
|
||||
tabStyle: 'default',
|
||||
// 路由切换动画
|
||||
transitionName: 'fade',
|
||||
// 是否固定顶栏
|
||||
fixedHeader: true,
|
||||
// 是否固定侧栏
|
||||
fixedSidebar: true,
|
||||
// 是否固定主体
|
||||
fixedBody: true,
|
||||
// 内容区域宽度铺满
|
||||
bodyFull: true,
|
||||
// logo 是否自适应宽度
|
||||
logoAutoSize: false,
|
||||
// 侧栏是否彩色图标
|
||||
colorfulIcon: false,
|
||||
// 侧栏是否只保持一个子菜单展开
|
||||
sideUniqueOpen: true,
|
||||
// 是否是色弱模式
|
||||
weakMode: false,
|
||||
// 是否是暗黑模式
|
||||
darkMode: false,
|
||||
// 主题色
|
||||
color: '#2CA8FE',
|
||||
// 主页的组件名称
|
||||
homeComponents: [],
|
||||
// 刷新路由时的参数
|
||||
routeReload: null,
|
||||
// 屏幕宽度
|
||||
screenWidth: screenWidth(),
|
||||
// 屏幕高度
|
||||
screenHeight: screenHeight(),
|
||||
// 内容区域宽度
|
||||
contentWidth: contentWidth(),
|
||||
// 内容区域高度
|
||||
contentHeight: contentHeight(),
|
||||
// 是否开启响应式
|
||||
styleResponsive: true
|
||||
});
|
||||
// 延时操作定时器
|
||||
let disableTransitionTimer: number, updateContentSizeTimer: number;
|
||||
|
||||
/**
|
||||
* 读取缓存配置
|
||||
*/
|
||||
function getCacheSetting(): any {
|
||||
try {
|
||||
const value = localStorage.getItem(THEME_STORE_NAME);
|
||||
if (value) {
|
||||
const cache = JSON.parse(value);
|
||||
if (typeof cache === 'object') {
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存配置
|
||||
*/
|
||||
function cacheSetting(key: string, value: any) {
|
||||
const cache = getCacheSetting();
|
||||
if (cache[key] !== value) {
|
||||
cache[key] = value;
|
||||
// console.log(value);
|
||||
// localStorage.setItem(THEME_STORE_NAME, JSON.stringify(cache));
|
||||
// updateCacheTheme({
|
||||
// key: 'theme',
|
||||
// content: JSON.stringify(cache)
|
||||
// }).then((res) => {
|
||||
// console.log(res);
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开关响应式布局
|
||||
*/
|
||||
function changeStyleResponsive(styleResponsive: boolean) {
|
||||
if (styleResponsive) {
|
||||
document.body.classList.remove(BODY_LIMIT_CLASS);
|
||||
} else {
|
||||
document.body.classList.add(BODY_LIMIT_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换色弱模式
|
||||
*/
|
||||
function changeWeakMode(weakMode: boolean) {
|
||||
if (weakMode) {
|
||||
document.body.classList.add(WEAK_CLASS);
|
||||
} else {
|
||||
document.body.classList.remove(WEAK_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换主题
|
||||
*/
|
||||
function changeTheme(value?: string | null, dark?: boolean) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
try {
|
||||
changeColor(value, dark);
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换布局时禁用过渡动画
|
||||
*/
|
||||
function disableTransition() {
|
||||
disableTransitionTimer && clearTimeout(disableTransitionTimer);
|
||||
document.body.classList.add(DISABLES_CLASS);
|
||||
disableTransitionTimer = setTimeout(() => {
|
||||
document.body.classList.remove(DISABLES_CLASS);
|
||||
}, 100) as unknown as number;
|
||||
}
|
||||
|
||||
export const useThemeStore = defineStore({
|
||||
id: 'theme',
|
||||
state: (): ThemeState => {
|
||||
const state = { ...DEFAULT_STATE };
|
||||
// 读取本地缓存
|
||||
const cache = getCacheSetting();
|
||||
Object.keys(state).forEach((key) => {
|
||||
if (typeof cache[key] !== 'undefined') {
|
||||
state[key] = cache[key];
|
||||
}
|
||||
});
|
||||
return state;
|
||||
},
|
||||
getters: {
|
||||
// 需要 keep-alive 的组件
|
||||
keepAliveInclude(): string[] {
|
||||
if (!TAB_KEEP_ALIVE || !this.showTabs) {
|
||||
return [];
|
||||
}
|
||||
const components = new Set<string>();
|
||||
const { reloadPath, reloadHome } = this.routeReload || {};
|
||||
this.tabs?.forEach((t) => {
|
||||
const isAlive = t.meta?.keepAlive !== false;
|
||||
const isExclude = KEEP_ALIVE_EXCLUDES.includes(t.path as string);
|
||||
const isReload = reloadPath && reloadPath === t.fullPath;
|
||||
if (isAlive && !isExclude && !isReload && t.components) {
|
||||
t.components.forEach((c) => {
|
||||
if (typeof c === 'string' && c) {
|
||||
components.add(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!reloadHome) {
|
||||
this.homeComponents?.forEach((c) => {
|
||||
if (typeof c === 'string' && c) {
|
||||
components.add(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Array.from(components);
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setTabs(value: TabItem[]) {
|
||||
this.tabs = value;
|
||||
//cacheSetting('tabs', value);
|
||||
},
|
||||
setCollapse(value: boolean) {
|
||||
this.collapse = value;
|
||||
this.delayUpdateContentSize(800);
|
||||
},
|
||||
setSideNavCollapse(value: boolean) {
|
||||
this.sideNavCollapse = value;
|
||||
this.delayUpdateContentSize(800);
|
||||
},
|
||||
setBodyFullscreen(value: boolean) {
|
||||
disableTransition();
|
||||
this.bodyFullscreen = value;
|
||||
this.delayUpdateContentSize(800);
|
||||
},
|
||||
setShowTabs(value: boolean) {
|
||||
this.showTabs = value;
|
||||
cacheSetting('showTabs', value);
|
||||
this.delayUpdateContentSize();
|
||||
},
|
||||
setShowFooter(value: boolean) {
|
||||
this.showFooter = value;
|
||||
cacheSetting('showFooter', value);
|
||||
this.delayUpdateContentSize();
|
||||
},
|
||||
setHeadStyle(value: HeadStyleType) {
|
||||
this.headStyle = value;
|
||||
cacheSetting('headStyle', value);
|
||||
},
|
||||
setSideStyle(value: SideStyleType) {
|
||||
this.sideStyle = value;
|
||||
cacheSetting('sideStyle', value);
|
||||
},
|
||||
setLayoutStyle(value: LayoutStyleType) {
|
||||
disableTransition();
|
||||
this.layoutStyle = value;
|
||||
cacheSetting('layoutStyle', value);
|
||||
this.delayUpdateContentSize();
|
||||
},
|
||||
setSideMenuStyle(value: SideMenuStyleType) {
|
||||
disableTransition();
|
||||
this.sideMenuStyle = value;
|
||||
cacheSetting('sideMenuStyle', value);
|
||||
this.delayUpdateContentSize();
|
||||
},
|
||||
setTabStyle(value: TabStyleType) {
|
||||
this.tabStyle = value;
|
||||
cacheSetting('tabStyle', value);
|
||||
},
|
||||
setTransitionName(value: string) {
|
||||
this.transitionName = value;
|
||||
cacheSetting('transitionName', value);
|
||||
},
|
||||
setFixedHeader(value: boolean) {
|
||||
disableTransition();
|
||||
this.fixedHeader = value;
|
||||
cacheSetting('fixedHeader', value);
|
||||
},
|
||||
setFixedSidebar(value: boolean) {
|
||||
disableTransition();
|
||||
this.fixedSidebar = value;
|
||||
cacheSetting('fixedSidebar', value);
|
||||
},
|
||||
setFixedBody(value: boolean) {
|
||||
disableTransition();
|
||||
this.fixedBody = value;
|
||||
cacheSetting('fixedBody', value);
|
||||
},
|
||||
setBodyFull(value: boolean) {
|
||||
this.bodyFull = value;
|
||||
cacheSetting('bodyFull', value);
|
||||
this.delayUpdateContentSize();
|
||||
},
|
||||
setLogoAutoSize(value: boolean) {
|
||||
disableTransition();
|
||||
this.logoAutoSize = value;
|
||||
cacheSetting('logoAutoSize', value);
|
||||
},
|
||||
setColorfulIcon(value: boolean) {
|
||||
this.colorfulIcon = value;
|
||||
cacheSetting('colorfulIcon', value);
|
||||
},
|
||||
setSideUniqueOpen(value: boolean) {
|
||||
this.sideUniqueOpen = value;
|
||||
cacheSetting('sideUniqueOpen', value);
|
||||
},
|
||||
setStyleResponsive(value: boolean) {
|
||||
changeStyleResponsive(value);
|
||||
this.styleResponsive = value;
|
||||
cacheSetting('styleResponsive', value);
|
||||
},
|
||||
/**
|
||||
* 切换色弱模式
|
||||
* @param value 是否是色弱模式
|
||||
*/
|
||||
setWeakMode(value: boolean) {
|
||||
return new Promise<void>((resolve) => {
|
||||
changeWeakMode(value);
|
||||
this.weakMode = value;
|
||||
cacheSetting('weakMode', value);
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 切换暗黑模式
|
||||
* @param value 是否是暗黑模式
|
||||
*/
|
||||
setDarkMode(value: boolean) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
changeTheme(this.color, value)
|
||||
.then(() => {
|
||||
this.darkMode = value;
|
||||
cacheSetting('darkMode', value);
|
||||
resolve();
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 切换主题色
|
||||
* @param value 主题色
|
||||
*/
|
||||
setColor(value?: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
changeTheme(value, this.darkMode)
|
||||
.then(() => {
|
||||
this.color = value;
|
||||
cacheSetting('color', value);
|
||||
resolve();
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 设置主页路由对应的组件名称
|
||||
* @param components 组件名称
|
||||
*/
|
||||
setHomeComponents(components: string[]) {
|
||||
this.homeComponents = components;
|
||||
},
|
||||
/**
|
||||
* 设置刷新路由信息
|
||||
* @param option 路由刷新参数
|
||||
*/
|
||||
setRouteReload(option: RouteReloadOption | null) {
|
||||
this.routeReload = option;
|
||||
},
|
||||
/**
|
||||
* 更新屏幕尺寸
|
||||
*/
|
||||
updateScreenSize() {
|
||||
this.screenWidth = screenWidth();
|
||||
this.screenHeight = screenHeight();
|
||||
this.updateContentSize();
|
||||
},
|
||||
/**
|
||||
* 更新内容区域尺寸
|
||||
*/
|
||||
updateContentSize() {
|
||||
this.contentWidth = contentWidth();
|
||||
this.contentHeight = contentHeight();
|
||||
},
|
||||
/**
|
||||
* 延时更新内容区域尺寸
|
||||
* @param delay 延迟时间
|
||||
*/
|
||||
delayUpdateContentSize(delay?: number) {
|
||||
updateContentSizeTimer && clearTimeout(updateContentSizeTimer);
|
||||
updateContentSizeTimer = setTimeout(() => {
|
||||
this.updateContentSize();
|
||||
}, delay ?? 100) as unknown as number;
|
||||
},
|
||||
/**
|
||||
* 重置设置
|
||||
*/
|
||||
resetSetting() {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
disableTransition();
|
||||
this.showTabs = DEFAULT_STATE.showTabs;
|
||||
this.showFooter = DEFAULT_STATE.showFooter;
|
||||
this.headStyle = DEFAULT_STATE.headStyle;
|
||||
this.sideStyle = DEFAULT_STATE.sideStyle;
|
||||
this.layoutStyle = DEFAULT_STATE.layoutStyle;
|
||||
this.sideMenuStyle = DEFAULT_STATE.sideMenuStyle;
|
||||
this.tabStyle = DEFAULT_STATE.tabStyle;
|
||||
this.transitionName = DEFAULT_STATE.transitionName;
|
||||
this.fixedHeader = DEFAULT_STATE.fixedHeader;
|
||||
this.fixedSidebar = DEFAULT_STATE.fixedSidebar;
|
||||
this.fixedBody = DEFAULT_STATE.fixedBody;
|
||||
this.bodyFull = DEFAULT_STATE.bodyFull;
|
||||
this.logoAutoSize = DEFAULT_STATE.logoAutoSize;
|
||||
this.colorfulIcon = DEFAULT_STATE.colorfulIcon;
|
||||
this.sideUniqueOpen = DEFAULT_STATE.sideUniqueOpen;
|
||||
this.styleResponsive = DEFAULT_STATE.styleResponsive;
|
||||
this.weakMode = DEFAULT_STATE.weakMode;
|
||||
this.darkMode = DEFAULT_STATE.darkMode;
|
||||
this.color = DEFAULT_STATE.color;
|
||||
localStorage.removeItem(THEME_STORE_NAME);
|
||||
Promise.all([
|
||||
changeStyleResponsive(this.styleResponsive),
|
||||
changeWeakMode(this.weakMode),
|
||||
changeTheme(this.color, this.darkMode)
|
||||
])
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 恢复主题
|
||||
*/
|
||||
recoverTheme() {
|
||||
// 关闭响应式布局
|
||||
if (!this.styleResponsive) {
|
||||
changeStyleResponsive(false);
|
||||
}
|
||||
// 恢复色弱模式
|
||||
if (this.weakMode) {
|
||||
changeWeakMode(true);
|
||||
}
|
||||
// 恢复主题色
|
||||
if (this.color || this.darkMode) {
|
||||
changeTheme(this.color, this.darkMode).catch((e) => {
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 添加页签或更新相同 key 的页签数据
|
||||
* @param data 页签数据
|
||||
*/
|
||||
tabAdd(data: TabItem | TabItem[]) {
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach((d) => {
|
||||
this.tabAdd(d);
|
||||
});
|
||||
return;
|
||||
}
|
||||
const i = this.tabs.findIndex((d) => d.key === data.key);
|
||||
if (i === -1) {
|
||||
this.setTabs(this.tabs.concat([data]));
|
||||
} else if (data.fullPath !== this.tabs[i].fullPath) {
|
||||
this.setTabs(
|
||||
this.tabs
|
||||
.slice(0, i)
|
||||
.concat([data])
|
||||
.concat(this.tabs.slice(i + 1))
|
||||
);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 关闭页签
|
||||
* @param key 页签 key
|
||||
*/
|
||||
async tabRemove({
|
||||
key,
|
||||
active
|
||||
}: TabRemoveOption): Promise<TabRemoveResult> {
|
||||
const i = this.tabs.findIndex((t) => t.key === key || t.fullPath === key);
|
||||
if (i === -1) {
|
||||
return {};
|
||||
}
|
||||
const t = this.tabs[i];
|
||||
if (!t.closable) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const path = this.tabs[i - 1]?.fullPath;
|
||||
this.setTabs(this.tabs.filter((_d, j) => j !== i));
|
||||
return t.key === active ? { path, home: !path } : {};
|
||||
},
|
||||
/**
|
||||
* 关闭左侧页签
|
||||
*/
|
||||
async tabRemoveLeft({
|
||||
key,
|
||||
active
|
||||
}: TabRemoveOption): Promise<TabRemoveResult> {
|
||||
let index = -1; // 选中页签的 index
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].key === active) {
|
||||
index = i;
|
||||
}
|
||||
if (this.tabs[i].key === key) {
|
||||
if (i === 0) {
|
||||
break;
|
||||
}
|
||||
const temp = this.tabs.filter((d, j) => !d.closable && j < i);
|
||||
if (temp.length === i + 1) {
|
||||
break;
|
||||
}
|
||||
const path = index === -1 ? void 0 : this.tabs[i].fullPath;
|
||||
this.setTabs(temp.concat(this.tabs.slice(i)));
|
||||
return { path };
|
||||
}
|
||||
}
|
||||
return Promise.reject();
|
||||
},
|
||||
/**
|
||||
* 关闭右侧页签
|
||||
*/
|
||||
async tabRemoveRight({
|
||||
key,
|
||||
active
|
||||
}: TabRemoveOption): Promise<TabRemoveResult> {
|
||||
if (this.tabs.length) {
|
||||
let index = -1; // 选中页签的 index
|
||||
for (let i = 0; i < this.tabs.length; i++) {
|
||||
if (this.tabs[i].key === active) {
|
||||
index = i;
|
||||
}
|
||||
if (this.tabs[i].key === key) {
|
||||
if (i === this.tabs.length - 1) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const temp = this.tabs.filter((d, j) => !d.closable && j > i);
|
||||
if (temp.length === this.tabs.length - i - 1) {
|
||||
return Promise.reject();
|
||||
}
|
||||
const path = index === -1 ? this.tabs[i].fullPath : void 0;
|
||||
this.setTabs(
|
||||
this.tabs
|
||||
.slice(0, i + 1)
|
||||
.concat(this.tabs.filter((d, j) => !d.closable && j > i))
|
||||
);
|
||||
return { path };
|
||||
}
|
||||
}
|
||||
// 主页时关闭全部
|
||||
const temp = this.tabs.filter((d) => !d.closable);
|
||||
if (temp.length !== this.tabs.length) {
|
||||
this.setTabs(temp);
|
||||
return { home: index !== -1 };
|
||||
}
|
||||
}
|
||||
return Promise.reject();
|
||||
},
|
||||
/**
|
||||
* 关闭其它页签
|
||||
*/
|
||||
async tabRemoveOther({
|
||||
key,
|
||||
active
|
||||
}: TabRemoveOption): Promise<TabRemoveResult> {
|
||||
let index = -1; // 选中页签的 index
|
||||
let path: string | undefined; // 关闭后跳转的 path
|
||||
const temp = this.tabs.filter((d, i) => {
|
||||
if (d.key === active) {
|
||||
index = i;
|
||||
}
|
||||
if (d.key === key) {
|
||||
path = d.fullPath;
|
||||
}
|
||||
return !d.closable || d.key === key;
|
||||
});
|
||||
if (temp.length === this.tabs.length) {
|
||||
return Promise.reject();
|
||||
}
|
||||
this.setTabs(temp);
|
||||
if (index === -1) {
|
||||
return {};
|
||||
}
|
||||
return key === active ? {} : { path, home: !path };
|
||||
},
|
||||
/**
|
||||
* 关闭全部页签
|
||||
* @param active 选中页签的 key
|
||||
*/
|
||||
async tabRemoveAll(active: string): Promise<TabRemoveResult> {
|
||||
const t = this.tabs.find((d) => d.key === active);
|
||||
const home = typeof t !== 'undefined' && t.closable === true; // 是否跳转主页
|
||||
const temp = this.tabs.filter((d) => !d.closable);
|
||||
if (temp.length === this.tabs.length) {
|
||||
return Promise.reject();
|
||||
}
|
||||
this.setTabs(temp);
|
||||
return { home };
|
||||
},
|
||||
/**
|
||||
* 修改页签
|
||||
* @param data 页签数据
|
||||
*/
|
||||
tabSetItem(data: TabItem) {
|
||||
let i = -1;
|
||||
if (data.key) {
|
||||
i = this.tabs.findIndex((d) => d.key === data.key);
|
||||
} else if (data.fullPath) {
|
||||
i = this.tabs.findIndex((d) => d.fullPath === data.fullPath);
|
||||
} else if (data.path) {
|
||||
i = this.tabs.findIndex((d) => d.path === data.path);
|
||||
}
|
||||
if (i !== -1) {
|
||||
const item = { ...this.tabs[i] };
|
||||
if (data.title) {
|
||||
item.title = data.title;
|
||||
}
|
||||
if (typeof data.closable === 'boolean') {
|
||||
item.closable = data.closable;
|
||||
}
|
||||
if (data.components) {
|
||||
item.components = data.components;
|
||||
}
|
||||
this.setTabs(
|
||||
this.tabs
|
||||
.slice(0, i)
|
||||
.concat([item])
|
||||
.concat(this.tabs.slice(i + 1))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 主题 State 类型
|
||||
*/
|
||||
export interface ThemeState {
|
||||
tabs: TabItem[];
|
||||
collapse: boolean;
|
||||
sideNavCollapse: boolean;
|
||||
bodyFullscreen: boolean;
|
||||
showTabs: boolean;
|
||||
showFooter: boolean;
|
||||
headStyle: HeadStyleType;
|
||||
sideStyle: SideStyleType;
|
||||
layoutStyle: LayoutStyleType;
|
||||
sideMenuStyle: SideMenuStyleType;
|
||||
tabStyle: TabStyleType;
|
||||
transitionName: string;
|
||||
fixedHeader: boolean;
|
||||
fixedSidebar: boolean;
|
||||
fixedBody: boolean;
|
||||
bodyFull: boolean;
|
||||
logoAutoSize: boolean;
|
||||
colorfulIcon: boolean;
|
||||
sideUniqueOpen: boolean;
|
||||
weakMode: boolean;
|
||||
darkMode: boolean;
|
||||
color?: string | null;
|
||||
homeComponents: string[];
|
||||
routeReload: RouteReloadOption | null;
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
contentWidth: number;
|
||||
contentHeight: number;
|
||||
styleResponsive: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置路由刷新方法的参数
|
||||
*/
|
||||
export interface RouteReloadOption {
|
||||
// 是否是刷新主页
|
||||
reloadHome?: boolean;
|
||||
// 要刷新的页签路由地址
|
||||
reloadPath?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭页签返回类型
|
||||
*/
|
||||
export interface TabRemoveResult {
|
||||
// 关闭后要跳转的地址
|
||||
path?: string;
|
||||
// 关闭后是否跳转到主页
|
||||
home?: boolean;
|
||||
}
|
||||
145
src/store/modules/user.ts
Normal file
145
src/store/modules/user.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 登录用户 store
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { formatMenus, toTreeData, formatTreeData } from 'ele-admin-pro/es';
|
||||
import type { MenuItemType } from 'ele-admin-pro/es';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
import { TOKEN_STORE_NAME, USER_MENUS } from '@/config/setting';
|
||||
import {getUserInfo} from '@/api/layout';
|
||||
import { initialization } from '@/api/layout';
|
||||
import {clone} from "@/api/system/menu";
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import {logout} from "@/utils/page-tab-util";
|
||||
// import { isExternalLink } from 'ele-admin-pro';
|
||||
const EXTRA_MENUS: any = [];
|
||||
|
||||
export interface UserState {
|
||||
info: User | null;
|
||||
menus: MenuItemType[] | null | undefined;
|
||||
authorities: (string | undefined)[];
|
||||
roles: (string | undefined)[];
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: 'user',
|
||||
state: (): UserState => ({
|
||||
// 当前登录用户的信息
|
||||
info: null,
|
||||
// 当前登录用户的菜单
|
||||
menus: null,
|
||||
// 当前登录用户的权限
|
||||
authorities: [],
|
||||
// 当前登录用户的角色
|
||||
roles: []
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
/**
|
||||
* 请求用户信息、权限、角色、菜单
|
||||
*/
|
||||
async fetchUserInfo() {
|
||||
// const company = await getTenantInfo().catch(() => void 0);
|
||||
const result = await getUserInfo().catch(() => {});
|
||||
if (!result) {
|
||||
logout(false, '/login');
|
||||
return {};
|
||||
}
|
||||
// 系统初始化
|
||||
if (!result.installed && result.username === 'superAdmin') {
|
||||
const hide = message.loading('正在分配资源请勿刷新页面...', 500);
|
||||
// @ts-ignore
|
||||
clone({tenantId: Number(result.templateId)}).then(() => {
|
||||
if (result.authorities?.length == 0) {
|
||||
result.roles?.map((d) => {
|
||||
if (d.roleCode === 'superAdmin') {
|
||||
initialization(d.roleId).then(() => {
|
||||
hide();
|
||||
location.reload();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
// 用户信息
|
||||
this.info = result;
|
||||
// 缓存租户信息
|
||||
localStorage.setItem('TenantName', `${this.info.tenantName}`);
|
||||
// 缓存企业信息
|
||||
if (this.info.companyInfo) {
|
||||
localStorage.setItem(
|
||||
'CompanyLogo',
|
||||
`${this.info.companyInfo?.companyLogo}`
|
||||
);
|
||||
localStorage.setItem('PlanId', `${this.info.companyInfo?.planId}`);
|
||||
localStorage.setItem(
|
||||
'ModulesUrl',
|
||||
`${this.info.companyInfo.modulesUrl}`
|
||||
);
|
||||
}
|
||||
// 用户权限
|
||||
this.authorities =
|
||||
result.authorities
|
||||
?.filter((d) => !!d.authority)
|
||||
?.map((d) => d.authority) ?? [];
|
||||
// 用户角色
|
||||
this.roles = result.roles?.map((d) => d.roleCode) ?? [];
|
||||
// 获取token
|
||||
const token = localStorage.getItem(TOKEN_STORE_NAME);
|
||||
// 用户菜单, 过滤掉按钮类型并转为 children 形式
|
||||
const { menus, homePath } = formatMenus(
|
||||
USER_MENUS ??
|
||||
toTreeData({
|
||||
data: result.authorities
|
||||
?.filter((d) => d.menuType !== 1)
|
||||
.map((d) => {
|
||||
// 改造子模块的访问路径
|
||||
if (d.modulesUrl) {
|
||||
d.component = `${d.modulesUrl}${d.path}?token=${token}`;
|
||||
}
|
||||
return d;
|
||||
}),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId'
|
||||
}).concat(EXTRA_MENUS)
|
||||
);
|
||||
this.menus = menus;
|
||||
return { menus, homePath };
|
||||
},
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
setInfo(value: User) {
|
||||
this.info = value;
|
||||
},
|
||||
/**
|
||||
* 更新菜单的 badge
|
||||
*/
|
||||
setMenuBadge(path: string, value?: number | string, color?: string) {
|
||||
this.menus = formatTreeData(this.menus, (m) => {
|
||||
if (path === m.path) {
|
||||
return {
|
||||
...m,
|
||||
meta: {
|
||||
...m.meta,
|
||||
badge: value,
|
||||
badgeColor: color
|
||||
}
|
||||
};
|
||||
}
|
||||
return m;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 重置用户状态(退出登录时调用)
|
||||
*/
|
||||
resetUserState() {
|
||||
this.info = null;
|
||||
this.menus = null;
|
||||
this.authorities = [];
|
||||
this.roles = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user