refactor(store): 重构状态管理模块,迁移至pinia架构
将原有vuex状态管理迁移至pinia架构,包括用户、购物车、订单等核心模块 新增支付方式映射、验证工具函数等实用工具 优化状态持久化配置,统一使用uni-app存储API 重构企业微信登录逻辑,增加模拟登录功能
This commit is contained in:
parent
23041a62d2
commit
1b82b8baab
|
|
@ -0,0 +1,13 @@
|
|||
import { createPinia } from "pinia"
|
||||
import { createPersistedState } from "pinia-plugin-persistedstate"
|
||||
|
||||
export const pinia = createPinia();
|
||||
|
||||
pinia.use(
|
||||
createPersistedState({
|
||||
storage: {
|
||||
getItem: uni.getStorageSync,
|
||||
setItem: uni.setStorageSync,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
import { pinia } from "@/pinia"
|
||||
import { LoginData } from "@/api/ab98/types"
|
||||
import { defineStore } from "pinia"
|
||||
|
||||
// 本地存储键名常量
|
||||
const STORAGE_KEYS = {
|
||||
FACE: 'ab98_face',
|
||||
SEX: 'ab98_sex',
|
||||
NAME: 'ab98_name',
|
||||
USERID: 'ab98_userid',
|
||||
REGISTERED: 'ab98_registered',
|
||||
TEL: 'ab98_tel',
|
||||
TOKEN: 'ab98_token',
|
||||
LOGIN_CODE: 'ab98_login_code'
|
||||
}
|
||||
|
||||
/**
|
||||
* AB98用户信息存储
|
||||
* @description 管理AB98系统用户相关状态信息
|
||||
*/
|
||||
export const useAb98UserStore = defineStore("ab98User", () => {
|
||||
// 用户面部图像URL
|
||||
const storedFace = localStorage.getItem(STORAGE_KEYS.FACE)
|
||||
const face_img = ref<string>(storedFace ? decodeURIComponent(storedFace) : '')
|
||||
// 用户性别(男/女)
|
||||
const storedSex = localStorage.getItem(STORAGE_KEYS.SEX)
|
||||
const sex = ref<string>(storedSex ? decodeURIComponent(storedSex) : '')
|
||||
// 用户真实姓名
|
||||
const storedName = localStorage.getItem(STORAGE_KEYS.NAME)
|
||||
const name = ref<string>(storedName ? decodeURIComponent(storedName) : '')
|
||||
// AB98系统用户唯一标识
|
||||
const storedUserId = localStorage.getItem(STORAGE_KEYS.USERID)
|
||||
const userid = ref<string>(storedUserId ? decodeURIComponent(storedUserId) : "")
|
||||
// 是否已完成注册流程
|
||||
const registered = ref<boolean>(JSON.parse(localStorage.getItem(STORAGE_KEYS.REGISTERED) || "false"))
|
||||
// 用户绑定手机号
|
||||
const storedTel = localStorage.getItem(STORAGE_KEYS.TEL)
|
||||
const tel = ref<string>(storedTel ? decodeURIComponent(storedTel) : "")
|
||||
// 用户认证令牌
|
||||
const storedToken = localStorage.getItem(STORAGE_KEYS.TOKEN)
|
||||
const token = ref<string>(storedToken ? decodeURIComponent(storedToken) : "");
|
||||
|
||||
const tokenLogin = ref<string>("");
|
||||
// 登录验证码
|
||||
const loginCode = '1'; // 默认验证码
|
||||
// 用户登录状态
|
||||
const isLogin = ref<boolean>(false);
|
||||
const storedLoginCode = localStorage.getItem(STORAGE_KEYS.LOGIN_CODE);
|
||||
isLogin.value = tel.value && storedLoginCode === String(loginCode) ? true : false;
|
||||
|
||||
/**
|
||||
* 更新用户基本信息
|
||||
* @param data - 登录接口返回的用户数据
|
||||
*/
|
||||
const setUserInfo = (data: LoginData) => {
|
||||
face_img.value = data.face_img
|
||||
localStorage.setItem(STORAGE_KEYS.FACE, encodeURIComponent(data.face_img))
|
||||
sex.value = data.sex
|
||||
localStorage.setItem(STORAGE_KEYS.SEX, encodeURIComponent(data.sex))
|
||||
name.value = data.name
|
||||
localStorage.setItem(STORAGE_KEYS.NAME, encodeURIComponent(data.name))
|
||||
userid.value = data.userid
|
||||
localStorage.setItem(STORAGE_KEYS.USERID, encodeURIComponent(data.userid))
|
||||
registered.value = data.registered
|
||||
localStorage.setItem(STORAGE_KEYS.REGISTERED, JSON.stringify(data.registered))
|
||||
tel.value = data.tel
|
||||
localStorage.setItem(STORAGE_KEYS.TEL, encodeURIComponent(data.tel))
|
||||
localStorage.setItem(STORAGE_KEYS.LOGIN_CODE, encodeURIComponent(loginCode))
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空用户敏感信息
|
||||
* @description 用于用户登出或会话过期时
|
||||
*/
|
||||
const clearUserInfo = () => {
|
||||
face_img.value = ""
|
||||
localStorage.removeItem(STORAGE_KEYS.FACE)
|
||||
sex.value = ""
|
||||
localStorage.removeItem(STORAGE_KEYS.SEX)
|
||||
name.value = ""
|
||||
localStorage.removeItem(STORAGE_KEYS.NAME)
|
||||
userid.value = ""
|
||||
localStorage.removeItem(STORAGE_KEYS.USERID)
|
||||
registered.value = false
|
||||
localStorage.removeItem(STORAGE_KEYS.REGISTERED)
|
||||
tel.value = ""
|
||||
localStorage.removeItem(STORAGE_KEYS.TEL)
|
||||
localStorage.removeItem(STORAGE_KEYS.TOKEN)
|
||||
localStorage.removeItem(STORAGE_KEYS.LOGIN_CODE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置认证令牌
|
||||
* @param value - JWT格式的认证令牌
|
||||
*/
|
||||
const setToken = (value: string) => {
|
||||
localStorage.setItem(STORAGE_KEYS.TOKEN, encodeURIComponent(value))
|
||||
token.value = value
|
||||
}
|
||||
|
||||
const setTel = (value: string) => {
|
||||
localStorage.setItem(STORAGE_KEYS.TEL, btoa(value))
|
||||
tel.value = value
|
||||
}
|
||||
|
||||
const setIsLogin = (value: boolean) => {
|
||||
isLogin.value = value;
|
||||
}
|
||||
|
||||
const setTokenLogin = (value: string) => {
|
||||
tokenLogin.value = value;
|
||||
}
|
||||
|
||||
return {
|
||||
face_img,
|
||||
sex,
|
||||
name,
|
||||
userid,
|
||||
registered,
|
||||
tel,
|
||||
token,
|
||||
isLogin,
|
||||
tokenLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
setTel,
|
||||
setIsLogin,
|
||||
clearUserInfo,
|
||||
setTokenLogin
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 在非setup上下文或SSR场景中使用store
|
||||
*/
|
||||
export function useAb98UserStoreOutside() {
|
||||
return useAb98UserStore(pinia)
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { ReturnApprovalAssetDTO } from '@/api/approval/types'
|
||||
|
||||
export interface ApprovalDetail extends ReturnApprovalAssetDTO {
|
||||
goodsName: string
|
||||
coverImg: string
|
||||
}
|
||||
|
||||
export const useApprovalStore = defineStore('approval', () => {
|
||||
const currentApproval = ref<ApprovalDetail | null>(null)
|
||||
|
||||
const setCurrentApproval = (approval: ApprovalDetail) => {
|
||||
currentApproval.value = approval
|
||||
}
|
||||
|
||||
return {
|
||||
currentApproval,
|
||||
setCurrentApproval
|
||||
}
|
||||
})
|
||||
|
||||
export function useApprovalStoreOutside() {
|
||||
return useApprovalStore()
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import type { Product } from './product'
|
||||
|
||||
export const useCartStore = defineStore('cart', () => {
|
||||
// 购物车商品列表
|
||||
const cartItems = ref<Array<{ product: Product; quantity: number }>>([])
|
||||
|
||||
// 添加商品到购物车
|
||||
const addToCart = (product: Product, quantity: number = 1): boolean => {
|
||||
if (quantity > product.stock && product.stock !== -1) {
|
||||
return false;
|
||||
}
|
||||
const existingItem = cartItems.value.find(item => item.product.cellId === product.cellId)
|
||||
if (existingItem) {
|
||||
if (existingItem.quantity + quantity > product.stock && product.stock !== -1) {
|
||||
return false;
|
||||
}
|
||||
existingItem.quantity += quantity
|
||||
} else {
|
||||
cartItems.value.push({ product, quantity })
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 移除商品
|
||||
const removeFromCart = (cellId: number, quantity: number = 1) => {
|
||||
const index = cartItems.value.findIndex(item => item.product.cellId === cellId)
|
||||
if (index !== -1) {
|
||||
if (cartItems.value[index].quantity <= quantity) {
|
||||
cartItems.value.splice(index, 1)
|
||||
} else {
|
||||
cartItems.value[index].quantity -= quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总价
|
||||
const totalPrice = computed(() => {
|
||||
return cartItems.value.reduce((sum, item) => {
|
||||
return sum + (item.product.price || 0) * item.quantity
|
||||
}, 0)
|
||||
})
|
||||
|
||||
//计算总数
|
||||
const totalQuantity = computed(() => {
|
||||
return cartItems.value.reduce((sum, item) => {
|
||||
return sum + item.quantity
|
||||
}, 0)
|
||||
})
|
||||
|
||||
// 清空购物车
|
||||
const clearCart = () => {
|
||||
cartItems.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
cartItems,
|
||||
addToCart,
|
||||
removeFromCart,
|
||||
totalPrice,
|
||||
totalQuantity,
|
||||
clearCart
|
||||
}
|
||||
})
|
||||
|
||||
// 支持在setup外使用
|
||||
export function useCartStoreOutside() {
|
||||
return useCartStore()
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import type { RouteLocationNormalizedGeneric } from "vue-router"
|
||||
import { pinia } from "@/pinia"
|
||||
import { defineStore } from "pinia"
|
||||
import { isString } from "@/utils/validate"
|
||||
|
||||
export const useKeepAliveStore = defineStore("keep-alive", () => {
|
||||
const cachedRoutes = ref<string[]>([])
|
||||
|
||||
const addCachedRoute = (route: RouteLocationNormalizedGeneric) => {
|
||||
const keepAlive = route.meta.keepAlive
|
||||
const name = route.name
|
||||
if (keepAlive && name && isString(name) && !cachedRoutes.value.includes(name)) {
|
||||
cachedRoutes.value.push(name)
|
||||
}
|
||||
}
|
||||
|
||||
const delAllCachedRoutes = () => {
|
||||
cachedRoutes.value = []
|
||||
}
|
||||
|
||||
return { cachedRoutes, addCachedRoute, delAllCachedRoutes }
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 在 SPA 应用中可用于在 pinia 实例被激活前使用 store
|
||||
* @description 在 SSR 应用中可用于在 setup 外使用 store
|
||||
*/
|
||||
export function useKeepAliveStoreOutside() {
|
||||
return useKeepAliveStore(pinia)
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import { pinia } from "@/pinia"
|
||||
import { getOrdersByOpenIdApi, getOrdersByQyUserIdApi } from "@/api/shop"
|
||||
import type { ShopOrderEntity, ShopOrderGoodsEntity, Goods } from "@/api/shop/types"
|
||||
import { defineStore } from "pinia"
|
||||
|
||||
export interface Order extends ShopOrderEntity {
|
||||
goodsList: Array<OrderGoods>
|
||||
}
|
||||
|
||||
export interface OrderGoods {
|
||||
goodsInfo: Goods
|
||||
orderGoods: ShopOrderGoodsEntity
|
||||
}
|
||||
|
||||
export const useOrderStore = defineStore("order", () => {
|
||||
const orders = ref<Order[]>([])
|
||||
const orderGoods = ref<ShopOrderGoodsEntity[]>([])
|
||||
const goods = ref<Goods[]>([])
|
||||
|
||||
const getOrders = async (corpid: string, openid: string, qyUserId: number, hasReturn: number) => {
|
||||
try {
|
||||
const { data } = qyUserId ?
|
||||
await getOrdersByQyUserIdApi(qyUserId, hasReturn)
|
||||
: await getOrdersByOpenIdApi(corpid, openid, hasReturn);
|
||||
|
||||
// 重组订单结构
|
||||
orders.value = data.orders.map(order => ({
|
||||
...order,
|
||||
goodsList: data.orderGoods
|
||||
.filter(og => og.orderId === order.orderId)
|
||||
.map(og => ({
|
||||
orderGoods: og,
|
||||
goodsInfo: data.goods.find(g => g.goodsId === og.goodsId)!
|
||||
}))
|
||||
}))
|
||||
.sort((a, b) => b.orderId - a.orderId)
|
||||
|
||||
// 保留原始数据结构
|
||||
orderGoods.value = data.orderGoods
|
||||
goods.value = data.goods
|
||||
} catch (error) {
|
||||
console.error("获取订单数据失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
return { orders, orderGoods, goods, getOrders }
|
||||
})
|
||||
|
||||
export function useOrderStoreOutside() {
|
||||
return useOrderStore(pinia)
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import { ShopEntity } from "@/api/shop/types"
|
||||
import { pinia } from "@/pinia"
|
||||
import { getShopGoodsApi } from "@/api/shop"
|
||||
import { defineStore } from "pinia"
|
||||
|
||||
export interface Product {
|
||||
id: number // 商品ID
|
||||
name: string // 商品名称
|
||||
price: number // 商品价格
|
||||
stock: number // 商品库存
|
||||
description: string // 商品描述
|
||||
image: string // 商品图片URL
|
||||
label: number // 商品标签
|
||||
cellId: number // 商品所在的格子ID
|
||||
usageInstruction: string // 商品使用说明
|
||||
belongType: number // 商品所属类型 0: 智借还 1: 固资通
|
||||
}
|
||||
|
||||
export const useProductStore = defineStore("product", () => {
|
||||
// 商品数据
|
||||
const labels = ref<{ id: number, name: string }[]>([]);
|
||||
const categories = ref<Product[]>([]);
|
||||
const shopId = ref<number|null>(null);
|
||||
const selectedShop = ref<ShopEntity|null>(null);
|
||||
|
||||
const setSelectedShop = (shop: ShopEntity) => {
|
||||
selectedShop.value = shop;
|
||||
}
|
||||
|
||||
const getGoods = async (shopId_?: number) => {
|
||||
if (shopId_ && shopId_ > 0) {
|
||||
shopId.value = shopId_;
|
||||
} else {
|
||||
shopId.value = null;
|
||||
/* const urlParams = new URLSearchParams(window.location.search);
|
||||
const shopIdParams = urlParams.get('shopId') || undefined;
|
||||
if (shopIdParams) {
|
||||
shopId.value = Number(shopIdParams);
|
||||
} */
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await getShopGoodsApi(shopId.value);
|
||||
|
||||
// 转换分类标签
|
||||
labels.value = data.categoryList.map(c => ({
|
||||
id: c.categoryId, // 使用分类名称生成ID
|
||||
name: c.categoryName
|
||||
}))
|
||||
|
||||
// 转换商品数据
|
||||
categories.value = data.goodsList.map(g => ({
|
||||
id: g.goodsId,
|
||||
name: g.goodsName,
|
||||
price: g.price,
|
||||
stock: g.stock,
|
||||
description: g.goodsDetail || "暂无描述",
|
||||
image: g.coverImg,
|
||||
label: g.categoryId,
|
||||
cellId: g.cellId,
|
||||
usageInstruction: g.usageInstruction || "",
|
||||
belongType: g.belongType
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error("获取商品数据失败:", error)
|
||||
}
|
||||
}
|
||||
return { labels, categories, shopId, selectedShop,
|
||||
getGoods, setSelectedShop }
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 在 SPA 应用中可用于在 pinia 实例被激活前使用 store
|
||||
* @description 在 SSR 应用中可用于在 setup 外使用 store
|
||||
*/
|
||||
export function useProductStoreOutside() {
|
||||
return useProductStore(pinia)
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { computed } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { getRentingCabinetDetailApi } from '@/api/cabinet'
|
||||
import type { CabinetCellEntity, RentingCabinetDetailDTO } from '@/api/cabinet/types'
|
||||
|
||||
export const useRentingCabinetStore = defineStore('rentingCabinet', () => {
|
||||
const rentingCabinets = ref<RentingCabinetDetailDTO[]>([])
|
||||
|
||||
// 租用购物车列表
|
||||
const rentingCartItems = ref<Array<{ cabinetCell: CabinetCellEntity; quantity: number }>>([])
|
||||
|
||||
const fetchRentingCabinetDetail = async (shopId: number) => {
|
||||
const res = await getRentingCabinetDetailApi(shopId);
|
||||
console.log(res)
|
||||
if (res.code === 0 && res.data) {
|
||||
rentingCabinets.value = res.data
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到租用购物车
|
||||
const addToRentingCart = (cabinetCell: CabinetCellEntity, quantity: number = 1): boolean => {
|
||||
if (quantity <= 0) return false
|
||||
const existingItem = rentingCartItems.value.find(item => item.cabinetCell.cellId === cabinetCell.cellId)
|
||||
if (existingItem) {
|
||||
existingItem.quantity += quantity
|
||||
} else {
|
||||
rentingCartItems.value.push({ cabinetCell, quantity })
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 从购物车移除
|
||||
const removeFromRentingCart = (cellId: number, quantity: number = 1) => {
|
||||
const index = rentingCartItems.value.findIndex(item => item.cabinetCell.cellId === cellId)
|
||||
if (index !== -1) {
|
||||
if (rentingCartItems.value[index].quantity <= quantity) {
|
||||
rentingCartItems.value.splice(index, 1)
|
||||
} else {
|
||||
rentingCartItems.value[index].quantity -= quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算租用总数
|
||||
const rentingCartTotalQuantity = computed(() => {
|
||||
return rentingCartItems.value.reduce((total, item) => total + item.quantity, 0)
|
||||
})
|
||||
|
||||
// 计算租用总价
|
||||
const rentingCartTotalPrice = computed(() => {
|
||||
return rentingCartItems.value.reduce((sum, item) => {
|
||||
return sum + (item.cabinetCell.cellPrice || 0) * item.quantity
|
||||
}, 0)
|
||||
})
|
||||
|
||||
// 清空租用购物车
|
||||
const clearRentingCart = () => {
|
||||
rentingCartItems.value = []
|
||||
}
|
||||
|
||||
return {
|
||||
rentingCabinets,
|
||||
rentingCartItems,
|
||||
addToRentingCart,
|
||||
removeFromRentingCart,
|
||||
rentingCartTotalQuantity,
|
||||
rentingCartTotalPrice,
|
||||
clearRentingCart,
|
||||
fetchRentingCabinetDetail
|
||||
}
|
||||
})
|
||||
|
||||
export function useRentingCabinetStoreOutside() {
|
||||
return useRentingCabinetStore()
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
import { pinia } from "@/pinia"
|
||||
import { getOpenIdApi, getBalanceApi, qyLogin, getBalanceByQyUserid, fakeQyLoginApi } from "@/api/shop"
|
||||
import { ab98UserDTO, GetBalanceResponse } from "@/api/shop/types"
|
||||
import { useAb98UserStore } from "./ab98-user"
|
||||
import { defineStore } from "pinia"
|
||||
|
||||
|
||||
export const useWxStore = defineStore("wx", () => {
|
||||
// 微信授权 code
|
||||
const code = ref<string>("")
|
||||
// 防止 CSRF 攻击的 state 参数
|
||||
const state = ref<string>("")
|
||||
// 用户 openid
|
||||
const openid = ref<string>("")
|
||||
// 用户 userid
|
||||
const userid = ref<string>("");
|
||||
// 剩余借呗
|
||||
const balance = ref<number>(0);
|
||||
// 已用借呗
|
||||
const useBalance = ref<number>(0);
|
||||
// 借呗总额
|
||||
const balanceLimit = ref<number>(0);
|
||||
// 企业id
|
||||
const corpid = ref<string>("");
|
||||
// 是否企业微信登录
|
||||
const corpidLogin = ref<boolean>(false);
|
||||
// 是否是柜子管理员
|
||||
const isCabinetAdmin = ref<boolean>(false);
|
||||
// 企业微信用户姓名
|
||||
const name = ref<string>("");
|
||||
// 企业微信用户id
|
||||
const qyUserId = ref<number>(0);
|
||||
// 汇邦云用户信息
|
||||
const ab98User = ref<ab98UserDTO | null>(null);
|
||||
|
||||
const isFakeQyLogin = ref<boolean>(false);
|
||||
// handleWxCallback 是否完成
|
||||
const isHandleWxCallbackComplete = ref<boolean>(false);
|
||||
|
||||
// 设置 openid
|
||||
const setOpenid = (id: string) => {
|
||||
openid.value = id
|
||||
}
|
||||
|
||||
const setBalance = (amount: number) => {
|
||||
balance.value = amount;
|
||||
}
|
||||
|
||||
const setIsCabinetAdmin = (isAdmin: boolean) => {
|
||||
isCabinetAdmin.value = isAdmin;
|
||||
}
|
||||
|
||||
const setAb98User = (user: ab98UserDTO) => {
|
||||
ab98User.value = user;
|
||||
const ab98UserStore = useAb98UserStore();
|
||||
ab98UserStore.setUserInfo({
|
||||
face_img: ab98User.value.faceImg || "",
|
||||
success: true,
|
||||
sex: ab98User.value.sex || "",
|
||||
name: ab98User.value.name || "",
|
||||
userid: ab98User.value.userid || "",
|
||||
registered: true,
|
||||
tel: ab98User.value.tel || "",
|
||||
});
|
||||
}
|
||||
|
||||
const refreshBalance = async () => {
|
||||
if (corpid.value && userid.value) {
|
||||
const res = await getBalanceByQyUserid(corpid.value, userid.value);
|
||||
if (res && res.code == 0) {
|
||||
balance.value = res.data.balance;
|
||||
useBalance.value = res.data.useBalance;
|
||||
balanceLimit.value = res.data.balanceLimit;
|
||||
|
||||
if (res.data.ab98User) {
|
||||
setAb98User(res.data.ab98User);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleWxCallback = async (params: { corpid?: string; code?: string; state?: string; }) => {
|
||||
console.log('handleWxCallback:', params)
|
||||
isHandleWxCallbackComplete.value = false; // 开始处理,标记为未完成
|
||||
if (params.code) {
|
||||
code.value = params.code
|
||||
state.value = params.state || state.value
|
||||
corpid.value = params.corpid || corpid.value
|
||||
corpidLogin.value = !!corpid.value;
|
||||
console.log('corpid:', corpid.value)
|
||||
console.log('corpidLogin:', corpidLogin.value)
|
||||
|
||||
try {
|
||||
if (!corpid.value) {
|
||||
// 调用获取 openid 的接口
|
||||
const res = await getOpenIdApi({ code: params.code });
|
||||
if (res && res.code == 0) {
|
||||
openid.value = res.data
|
||||
}
|
||||
} else {
|
||||
// 企业微信登录
|
||||
const res = await qyLogin({ corpid: corpid.value, code: params.code, state: params.state})
|
||||
if (res && res.code == 0) {
|
||||
userid.value = res.data.userid;
|
||||
openid.value = res.data.openid;
|
||||
isCabinetAdmin.value = res.data.isCabinetAdmin === 1;
|
||||
name.value = res.data.name;
|
||||
qyUserId.value = res.data.qyUserId;
|
||||
setAb98User(res.data.ab98User);
|
||||
}
|
||||
}
|
||||
|
||||
if (openid.value) {
|
||||
// 获取用户余额
|
||||
let balanceRes: ApiResponseData<GetBalanceResponse> | null = null;
|
||||
|
||||
if(corpid.value) {
|
||||
balanceRes = await getBalanceByQyUserid(corpid.value, userid.value);
|
||||
} else {
|
||||
corpid.value = "wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw";
|
||||
balanceRes = await getBalanceApi(corpid.value, openid.value)
|
||||
}
|
||||
console.log('获取余额成功:', balanceRes)
|
||||
if (balanceRes && balanceRes.code == 0) {
|
||||
balance.value = balanceRes.data.balance;
|
||||
useBalance.value = balanceRes.data.useBalance;
|
||||
balanceLimit.value = balanceRes.data.balanceLimit;
|
||||
if (!userid.value) {
|
||||
userid.value = balanceRes.data.userid;
|
||||
}
|
||||
if (!ab98User.value && balanceRes.data.ab98User) {
|
||||
setAb98User(balanceRes.data.ab98User);
|
||||
}
|
||||
/* if (!corpid.value) {
|
||||
corpid.value = balanceRes.data.corpid;
|
||||
} */
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取 openid 失败:', err)
|
||||
} finally {
|
||||
isHandleWxCallbackComplete.value = true; // 处理完成,标记为已完成
|
||||
}
|
||||
|
||||
} else {
|
||||
isHandleWxCallbackComplete.value = true; // 没有code参数,直接标记为已完成
|
||||
}
|
||||
}
|
||||
|
||||
const fakeQyLogin = async () => {
|
||||
isFakeQyLogin.value = true;
|
||||
corpid.value = "wpZ1ZrEgAA2QTxIRcB4cMtY7hQbTcPAw";
|
||||
corpidLogin.value = true;
|
||||
userid.value = "woZ1ZrEgAAV9AEdRt1MGQxSg-KDJrDlA";
|
||||
const res = await fakeQyLoginApi({ corpid: corpid.value, userid: userid.value})
|
||||
if (res && res.code == 0) {
|
||||
userid.value = res.data.userid;
|
||||
openid.value = "oMRxw6Eum0DB1IjI_pEX_yrawBHw";
|
||||
isCabinetAdmin.value = res.data.isCabinetAdmin === 1;
|
||||
name.value = res.data.name;
|
||||
qyUserId.value = res.data.qyUserId;
|
||||
setAb98User(res.data.ab98User);
|
||||
}
|
||||
}
|
||||
|
||||
// 检测 handleWxCallback 是否完成的异步函数
|
||||
const waitForHandleWxCallbackComplete = async (timeout = 30000): Promise<boolean> => {
|
||||
const startTime = Date.now();
|
||||
while (!isHandleWxCallbackComplete.value) {
|
||||
if (Date.now() - startTime > timeout) {
|
||||
console.warn('等待 handleWxCallback 完成超时');
|
||||
return false;
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 100)); // 每100ms检查一次
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return { code, state, openid, corpid, userid, balance, useBalance,
|
||||
balanceLimit, isCabinetAdmin, corpidLogin, name, ab98User, qyUserId, isFakeQyLogin,
|
||||
isHandleWxCallbackComplete, setOpenid, setBalance, handleWxCallback, setIsCabinetAdmin,
|
||||
refreshBalance, setAb98User, fakeQyLogin, waitForHandleWxCallbackComplete }
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 用于在 setup 外使用 store
|
||||
*/
|
||||
export function useWxStoreOutside() {
|
||||
return useWxStore(pinia)
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
import {defineStore} from 'pinia';
|
||||
import {ref} from 'vue';
|
||||
import {addCart, listCart, operateAmount} from '@/api/goods/shopping-cart';
|
||||
|
||||
// 商品类型定义
|
||||
interface CartItem {
|
||||
cartId: string; // 购物车唯一标识
|
||||
id: string; // 商品原始id
|
||||
name: string;
|
||||
image?: string;
|
||||
pictures?: string[];
|
||||
price: number;
|
||||
spec: string;
|
||||
quantity: number;
|
||||
selected: boolean;
|
||||
originalPrice?: number;
|
||||
listId?: string;
|
||||
specs?: string;
|
||||
type: string;
|
||||
specification?: any;
|
||||
status: number;
|
||||
amount: number;
|
||||
}
|
||||
|
||||
export const useCartStore = defineStore('cart', () => {
|
||||
// 购物车商品列表
|
||||
const items = ref<CartItem[]>([]);
|
||||
// 购物车详情
|
||||
const itemsDetail = ref<CartItem>()
|
||||
|
||||
//订单详情
|
||||
const orderDetail = ref<CartItem[]>()
|
||||
|
||||
// 查询购物车列表
|
||||
const getListCart = async () => {
|
||||
return listCart().then((res: any) => {
|
||||
if (res.data?.products){
|
||||
items.value = res.data.products.map((item: any) => {
|
||||
return {
|
||||
...item,
|
||||
...item.product,
|
||||
title: item.name,
|
||||
price: item.price,
|
||||
type: item.type === 1 ? 'product' : 'service',
|
||||
status: item.status,
|
||||
specs: selectSpecs(item.product.specificationPrices),
|
||||
specsIds: selectSpecsIds(item.product.specificationPrices),
|
||||
id: item.id,
|
||||
selected:true,
|
||||
listId: `${item.productId}_${item.type === 1 ? 'product' : 'service'}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// 添加商品到购物车 type 用于区分是添加购物车还是恢复购物车
|
||||
const addItem = (product: Omit<CartItem, 'selected'>) => {
|
||||
//把一下数据异步返回
|
||||
return new Promise((resolve, reject) => {
|
||||
//加入购物车
|
||||
addCart({
|
||||
productId: product.id,
|
||||
type: product.type === 'product' ? 1 : 0,
|
||||
price:product.price,
|
||||
amount:product.amount || 1,
|
||||
selected:true,
|
||||
specification:product.specs ? product.specification : undefined,
|
||||
formId: new Date().getTime()
|
||||
}).then(res => {
|
||||
uni.showToast({ title: '加入购物车', icon: 'none' })
|
||||
resolve(res)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
// 从购物车移除商品
|
||||
const removeItem = (cartId: string) => {
|
||||
items.value = items.value.filter(item => item.id !== cartId);
|
||||
};
|
||||
|
||||
// 更新商品数量
|
||||
const updateQuantity = (cartId: string, amount: number) => {
|
||||
operateAmount({
|
||||
id:cartId,
|
||||
amount
|
||||
}).then(res => {
|
||||
if (res.data.operator === 'add') {
|
||||
uni.showToast({ title: '加入购物车', icon: 'none' })
|
||||
}
|
||||
}).catch(err => {
|
||||
uni.showToast({ title: err, icon: 'none' })
|
||||
})
|
||||
};
|
||||
|
||||
// 全选/取消全选
|
||||
const toggleAllItems = (checked: boolean) => {
|
||||
items.value = items.value.map(item => ({ ...item, selected: checked }));
|
||||
};
|
||||
|
||||
// 切换商品选中状态
|
||||
const toggleItemSelection = (cartId: string, selected?: boolean, item?: CartItem) => {
|
||||
items.value = items.value.map(item =>
|
||||
item.id === cartId ? { ...item, selected: selected ?? !item.selected } : item
|
||||
)
|
||||
};
|
||||
|
||||
// 清空购物车
|
||||
const clearCart = () => {
|
||||
items.value = [];
|
||||
};
|
||||
|
||||
return {
|
||||
items,
|
||||
itemsDetail,
|
||||
orderDetail,
|
||||
getListCart,
|
||||
addItem,
|
||||
removeItem,
|
||||
updateQuantity,
|
||||
toggleAllItems,
|
||||
toggleItemSelection,
|
||||
clearCart
|
||||
};
|
||||
});
|
||||
//取层级数据
|
||||
//规格名称
|
||||
const selectSpecs = ((row:any) => {
|
||||
let result = '';
|
||||
if (row) {
|
||||
let row1 = row[0]
|
||||
result = result + row1.value;
|
||||
if(row1.child){
|
||||
let row2 = row1.child[0]
|
||||
result = result + ';' + row1.child[0].value;
|
||||
if(row2.child){
|
||||
result = result + ';' + row2.child[0].value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
//取规格id
|
||||
const selectSpecsIds = ((row:any)=>{
|
||||
let result = '';
|
||||
if (row) {
|
||||
let row1 = row[0]
|
||||
result = result + row1.id;
|
||||
if(row1.child){
|
||||
let row2 = row1.child[0]
|
||||
result = result + ',' + row1.child[0].id;
|
||||
if(row2.child){
|
||||
result = result + ',' + row2.child[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import { createPinia } from 'pinia'
|
||||
import { createPersistedState } from 'pinia-plugin-persistedstate' // 数据持久化
|
||||
|
||||
const store = createPinia()
|
||||
store.use(
|
||||
createPersistedState({
|
||||
storage: {
|
||||
getItem: uni.getStorageSync,
|
||||
setItem: uni.setStorageSync,
|
||||
},
|
||||
}),
|
||||
)
|
||||
|
||||
export default store
|
||||
|
||||
export * from './theme'
|
||||
// 模块统一导出
|
||||
export * from './user'
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
import type { ConfigProviderThemeVars } from 'wot-design-uni'
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useThemeStore = defineStore(
|
||||
'theme-store',
|
||||
() => {
|
||||
/** 主题 */
|
||||
const theme = ref<'light' | 'dark'>('light')
|
||||
|
||||
/** 主题变量 */
|
||||
const themeVars = ref<ConfigProviderThemeVars>({
|
||||
// colorTheme: 'red',
|
||||
// buttonPrimaryBgColor: '#07c160',
|
||||
// buttonPrimaryColor: '#07c160',
|
||||
})
|
||||
|
||||
/** 设置主题变量 */
|
||||
const setThemeVars = (partialVars: Partial<ConfigProviderThemeVars>) => {
|
||||
themeVars.value = { ...themeVars.value, ...partialVars }
|
||||
}
|
||||
|
||||
/** 切换主题 */
|
||||
const toggleTheme = () => {
|
||||
theme.value = theme.value === 'light' ? 'dark' : 'light'
|
||||
}
|
||||
|
||||
return {
|
||||
/** 设置主题变量 */
|
||||
setThemeVars,
|
||||
/** 切换主题 */
|
||||
toggleTheme,
|
||||
/** 主题变量 */
|
||||
themeVars,
|
||||
/** 主题 */
|
||||
theme,
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: true,
|
||||
},
|
||||
)
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
/**
|
||||
* 登录用户状态管理
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import type { User } from '@/api/system/user/model/';
|
||||
import type { Menu } from '@/api/system/menu/model';
|
||||
import { toTree,mapTree, isExternalLink } from '@/utils/dataTree';
|
||||
import { getInfoByMember,getStoreSettings,getShopList } from '@/api/layout';
|
||||
import store from "@/store/index";
|
||||
import bus, {EVENT_KEY} from "@/utils/bus";
|
||||
/**
|
||||
* 菜单数据
|
||||
*/
|
||||
export interface MenuItem {
|
||||
/** 路由名称 */
|
||||
name?: string;
|
||||
/** 菜单地址 */
|
||||
path: string;
|
||||
/** 路由组件 */
|
||||
component?: string;
|
||||
/** 路由重定向 */
|
||||
redirect?: string;
|
||||
/** 路由元数据 */
|
||||
meta?: any;
|
||||
/** 子路由 */
|
||||
children?: Array<MenuItem>;
|
||||
/** 临时子路由数据, 内部属性 */
|
||||
tempChildren?: Array<MenuItem>;
|
||||
/** 用户位置信息 */
|
||||
location?: { latitude: number; longitude: number };
|
||||
/** 是否弹出 门店选择 */
|
||||
showPosition?: boolean;
|
||||
|
||||
}
|
||||
/** 直接指定菜单数据 */
|
||||
const USER_MENUS: Menu[] | null = null;
|
||||
|
||||
export interface UserState {
|
||||
info: User | null;
|
||||
menus: any[] | null;
|
||||
authorities: (string | undefined)[];
|
||||
roles: (string | undefined)[];
|
||||
loginPromise: null ,// 初始化Promise为null
|
||||
}
|
||||
export const useUserStore = defineStore('user', {
|
||||
state: (): {
|
||||
settings: null;
|
||||
loginPromise: null;
|
||||
roles: any[];
|
||||
location: {longitude:0,latitude:0};
|
||||
menus: null;
|
||||
authorities: any[];
|
||||
info: null,
|
||||
showPosition: boolean,
|
||||
} => ({
|
||||
/** 当前登录用户的信息 */
|
||||
info: null,
|
||||
/** 当前登录用户的菜单 */
|
||||
menus: null,
|
||||
/** 当前登录用户的权限 */
|
||||
authorities: [],
|
||||
/** 当前登录用户的角色 */
|
||||
roles: [],
|
||||
/** 登录用户信息请求Promise */
|
||||
loginPromise: null ,//
|
||||
/** 用户位置信息 */
|
||||
location: null,
|
||||
/** 当前登录用户的门店配置 */
|
||||
settings: null,
|
||||
/** 是否弹出 门店选择 */
|
||||
showPosition:false
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
* 请求登录用户的个人信息/权限/角色/菜单
|
||||
*/
|
||||
async fetchUserInfo() {
|
||||
const result = await getInfoByMember().catch((e) => console.error(e));
|
||||
//门店配置
|
||||
const storeSettings = await getStoreSettings().catch((e) => console.error(e));
|
||||
//更新门店配置
|
||||
this.setSettings(storeSettings);
|
||||
const store = uni.getStorageSync('store')
|
||||
if (!store) {
|
||||
//门店配置为0时, 表示不选择门店, 则获取用户位置。
|
||||
if (storeSettings.choiceShopWay === 0){
|
||||
await getLocationOnce().then((res:any) => {
|
||||
console.log(res,'获取到用户位置信息')
|
||||
this.setLocation({ latitude: res.latitude, longitude: res.longitude })
|
||||
getShopList({longitude:res.longitude,latitude:res.latitude}).then((res:any) => {
|
||||
if (res.shops.length > 0) {
|
||||
//循环筛选出最近,并不是休息的门店
|
||||
const validStores = res.shops.filter((shop: any) => shop.isBusinessTime);
|
||||
if (validStores.length > 0) {
|
||||
uni.setStorageSync('store', validStores[0] || null)
|
||||
}
|
||||
bus.emit(EVENT_KEY.REFRESH_SHOPPING_CART);
|
||||
}else {
|
||||
uni.showToast({
|
||||
title: '未查询到门店,请稍后再试',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}).catch((e)=>{
|
||||
console.log(e,'获取用户位置失败')
|
||||
this.showPosition = true
|
||||
})
|
||||
}else if (storeSettings.choiceShopWay === 1){
|
||||
this.showPosition = true
|
||||
}
|
||||
}
|
||||
if (!result) {
|
||||
return {};
|
||||
}
|
||||
// 用户信息
|
||||
this.setInfo(result.user);
|
||||
// 用户权限
|
||||
if (result.menus) {
|
||||
this.authorities = result.menus.map((d) => d.authority).filter((a) => !!a) ?? [];
|
||||
}
|
||||
// 用户角色
|
||||
this.roles = result.roles?.map?.((d) => d.roleCode) ?? [];
|
||||
// 用户菜单, 过滤掉按钮类型并转为children形式
|
||||
const { menus, homePath } = formatMenus(
|
||||
USER_MENUS ??
|
||||
toTree({
|
||||
data: result.menus?.filter?.((d) => d.menuType !== 1),
|
||||
idField: 'menuId',
|
||||
parentIdField: 'parentId'
|
||||
})
|
||||
);
|
||||
this.setMenus(menus);
|
||||
return { menus, homePath };
|
||||
},
|
||||
/**
|
||||
* 更新用户信息
|
||||
*/
|
||||
setInfo(value: User) {
|
||||
this.info = value;
|
||||
},
|
||||
/**
|
||||
* 更新菜单数据
|
||||
*/
|
||||
setMenus(menus: any[] | null) {
|
||||
this.menus = menus;
|
||||
},
|
||||
/**
|
||||
* 更新用户位置信息
|
||||
*/
|
||||
setLocation(value: any) {
|
||||
this.location = value;
|
||||
},
|
||||
/**
|
||||
* 更新门店配置
|
||||
*/
|
||||
setSettings(settings: any) {
|
||||
this.settings = settings;
|
||||
}
|
||||
}
|
||||
});
|
||||
/**
|
||||
* 菜单数据处理为EleProLayout所需要的格式
|
||||
* @param data 菜单数据
|
||||
* @param childField 子级的字段名称
|
||||
*/
|
||||
function formatMenus(data: Menu[], childField = 'children') {
|
||||
let homePath: string | undefined;
|
||||
let homeTitle: string | undefined;
|
||||
const menus = mapTree(
|
||||
data,
|
||||
(item) => {
|
||||
const meta: MenuItem['meta'] = typeof item.meta === 'string' ? JSON.parse(item.meta || '{}') : item.meta;
|
||||
const menu: MenuItem = {
|
||||
path: item.path,
|
||||
component: item.component,
|
||||
meta: { title: item.title, icon: item.icon, hide: !!item.hide, ...meta }
|
||||
};
|
||||
const children = item[childField] ? item[childField].filter((d: any) => !(d.meta?.hide ?? d.hide)) : void 0;
|
||||
if (!children?.length) {
|
||||
if (!homePath && menu.path && !isExternalLink(menu.path)) {
|
||||
homePath = menu.path;
|
||||
homeTitle = menu.meta?.title;
|
||||
}
|
||||
} else {
|
||||
const childPath = children[0].path;
|
||||
if (childPath) {
|
||||
if (!menu.redirect) {
|
||||
menu.redirect = childPath;
|
||||
}
|
||||
if (!menu.path) {
|
||||
menu.path = childPath.substring(0, childPath.lastIndexOf('/'));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!menu.path) {
|
||||
console.error('菜单path不能为空且要唯一:', item);
|
||||
return;
|
||||
}
|
||||
return menu;
|
||||
},
|
||||
childField
|
||||
);
|
||||
return { menus, homePath, homeTitle };
|
||||
}
|
||||
// 获取一次位置
|
||||
function getLocationOnce() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getLocation({
|
||||
type: 'wgs84',
|
||||
altitude: true,
|
||||
success: function (res) {
|
||||
resolve(res)
|
||||
},
|
||||
fail: function (err) {
|
||||
reject(err)
|
||||
},
|
||||
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
export const paymentMethodOptions = [
|
||||
{ label: '微信支付', value: 0, type: 'primary' },
|
||||
{ label: '借呗支付', value: 1, type: 'success' },
|
||||
{ label: '要呗支付', value: 2, type: 'info' },
|
||||
{ label: '余额支付', value: 3, type: 'warning' },
|
||||
];
|
||||
|
||||
export const modeToPaymentMethodMap: Record<number, number[]> = {
|
||||
0: [0],
|
||||
1: [0, 1],
|
||||
2: [0, 1],
|
||||
3: [0],
|
||||
4: [2],
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/** 判断是否为数组 */
|
||||
export function isArray<T>(arg: T) {
|
||||
return Array.isArray ? Array.isArray(arg) : Object.prototype.toString.call(arg) === "[object Array]"
|
||||
}
|
||||
|
||||
/** 判断是否为字符串 */
|
||||
export function isString(str: unknown) {
|
||||
return typeof str === "string" || str instanceof String
|
||||
}
|
||||
|
||||
/** 判断是否为外链 */
|
||||
export function isExternal(path: string) {
|
||||
const reg = /^(https?:|mailto:|tel:)/
|
||||
return reg.test(path)
|
||||
}
|
||||
Loading…
Reference in New Issue