feat(pages): 添加多个页面配置和功能模块
- 新增 .editorconfig、.eslintrc、.gitignore 配置文件 - 添加管理员文章管理页面配置和功能实现 - 添加经销商申请注册页面配置和功能实现 - 添加经销商银行卡管理页面配置和功能实现 - 添加经销商客户管理页面配置和功能实现 - 添加用户地址管理页面配置和功能实现 - 添加用户聊天消息页面配置和功能实现 - 添加用户礼品管理页面配置和功能实现
This commit is contained in:
171
src/utils/common.ts
Normal file
171
src/utils/common.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
export default function navTo(url: string, isLogin = false) {
|
||||
if (isLogin) {
|
||||
if (!Taro.getStorageSync('access_token') || !Taro.getStorageSync('UserId')) {
|
||||
Taro.showToast({
|
||||
title: '请先登录',
|
||||
icon: 'none',
|
||||
duration: 500
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Taro.navigateTo({
|
||||
url: url
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 转base64
|
||||
export function fileToBase64(filePath: string) {
|
||||
return new Promise((resolve) => {
|
||||
let fileManager = Taro.getFileSystemManager();
|
||||
fileManager.readFile({
|
||||
filePath,
|
||||
encoding: 'base64',
|
||||
success: (e: any) => {
|
||||
resolve(`data:image/jpg;base64,${e.data}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 转义微信富文本图片样式
|
||||
* @param htmlText
|
||||
*/
|
||||
export function wxParse(htmlText: string) {
|
||||
// Replace <img> tags with max-width, height and margin styles to remove spacing
|
||||
htmlText = htmlText.replace(/\<img/gi, '<img style="max-width:100%;height:auto;margin:0;padding:0;display:block;"');
|
||||
|
||||
// Replace style attributes that do not contain text-align, add margin:0 to remove spacing
|
||||
htmlText = htmlText.replace(/style\s*?=\s*?(['"])(?!.*?text-align)[\s\S]*?\1/ig, 'style="max-width:100%;height:auto;margin:0;padding:0;display:block;"');
|
||||
|
||||
return htmlText;
|
||||
}
|
||||
|
||||
|
||||
export function copyText(text: string) {
|
||||
Taro.setClipboardData({
|
||||
data: text,
|
||||
success: function () {
|
||||
Taro.showToast({
|
||||
title: '复制成功',
|
||||
icon: 'success',
|
||||
duration: 2000
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
Taro.showToast({
|
||||
title: '复制失败',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享商品链接
|
||||
* @param goodsId 商品ID
|
||||
*/
|
||||
export function shareGoodsLink(goodsId: string | number) {
|
||||
// 构建分享链接,这里需要根据你的实际域名调整
|
||||
const baseUrl = 'https://your-domain.com'; // 请替换为你的实际域名
|
||||
const shareUrl = `${baseUrl}/shop/goodsDetail/index?id=${goodsId}`;
|
||||
|
||||
copyText(shareUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示分享引导提示
|
||||
*/
|
||||
export function showShareGuide() {
|
||||
Taro.showModal({
|
||||
title: '分享提示',
|
||||
content: '请点击右上角的"..."按钮,然后选择"转发"来分享给好友,或选择"分享到朋友圈"',
|
||||
showCancel: false,
|
||||
confirmText: '知道了'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 截取字符串,确保不超过指定的汉字长度
|
||||
* @param text 原始文本
|
||||
* @param maxLength 最大汉字长度,默认30
|
||||
* @returns 截取后的文本
|
||||
*/
|
||||
export function truncateText(text: string, maxLength: number = 30): string {
|
||||
if (!text) return '';
|
||||
|
||||
// 如果长度不超过限制,直接返回
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// 超过长度则截取
|
||||
return text.substring(0, maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化货币
|
||||
* @param amount
|
||||
* @param currency
|
||||
*/
|
||||
export function formatCurrency(amount: number, currency: string = 'CNY'): string {
|
||||
return new Intl.NumberFormat('zh-CN', {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
}).format(amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成订单标题
|
||||
* @param goodsNames 商品名称数组
|
||||
* @param maxLength 最大长度,默认30
|
||||
* @returns 订单标题
|
||||
*/
|
||||
export function generateOrderTitle(goodsNames: string[], maxLength: number = 30): string {
|
||||
if (!goodsNames || goodsNames.length === 0) {
|
||||
return '商品订单';
|
||||
}
|
||||
|
||||
let title = '';
|
||||
if (goodsNames.length === 1) {
|
||||
title = goodsNames[0];
|
||||
} else {
|
||||
title = `${goodsNames[0]}等${goodsNames.length}件商品`;
|
||||
}
|
||||
|
||||
return truncateText(title, maxLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转驼峰命名
|
||||
*/
|
||||
export function toCamelCase(str: string): string {
|
||||
return str.replace(/_([a-z])/g, function (_, letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下划线转大驼峰命名
|
||||
*/
|
||||
export function toCamelCaseUpper(str: string): string {
|
||||
return toCamelCase(str).replace(/^[a-z]/, function (letter) {
|
||||
return letter.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为短下划线
|
||||
*/
|
||||
export function toShortUnderline(str: string): string {
|
||||
return str.replace(/[A-Z]/g, function (letter) {
|
||||
return '_' + letter.toLowerCase();
|
||||
}).replace(/^_/, '');
|
||||
}
|
||||
200
src/utils/couponUtils.ts
Normal file
200
src/utils/couponUtils.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import { ShopUserCoupon } from '@/api/shop/shopUserCoupon/model'
|
||||
import { CouponCardProps } from '@/components/CouponCard'
|
||||
|
||||
/**
|
||||
* 将后端优惠券数据转换为前端组件所需格式
|
||||
*/
|
||||
export const transformCouponData = (coupon: ShopUserCoupon): CouponCardProps => {
|
||||
// 解析金额
|
||||
let amount = 0
|
||||
if (coupon.type === 10) {
|
||||
// 满减券:使用reducePrice
|
||||
amount = parseFloat(coupon.reducePrice || '0')
|
||||
} else if (coupon.type === 20) {
|
||||
// 折扣券:使用discount
|
||||
amount = coupon.discount || 0
|
||||
} else if (coupon.type === 30) {
|
||||
// 免费券:金额为0
|
||||
amount = 0
|
||||
}
|
||||
|
||||
// 解析最低消费金额
|
||||
const minAmount = parseFloat(coupon.minPrice || '0')
|
||||
|
||||
// 确定主题颜色
|
||||
const getTheme = (type?: number): CouponCardProps['theme'] => {
|
||||
switch (type) {
|
||||
case 10: return 'red' // 满减券-红色
|
||||
case 20: return 'orange' // 折扣券-橙色
|
||||
case 30: return 'green' // 免费券-绿色
|
||||
default: return 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: coupon.id,
|
||||
amount,
|
||||
minAmount: minAmount > 0 ? minAmount : undefined,
|
||||
type: coupon.type as 10 | 20 | 30,
|
||||
status: coupon.status as 0 | 1 | 2,
|
||||
statusText: coupon.statusText,
|
||||
title: coupon.name || coupon.description || '优惠券',
|
||||
description: coupon.description,
|
||||
startTime: coupon.startTime,
|
||||
endTime: coupon.endTime,
|
||||
isExpiringSoon: coupon.isExpiringSoon,
|
||||
daysRemaining: coupon.daysRemaining,
|
||||
hoursRemaining: coupon.hoursRemaining,
|
||||
theme: getTheme(coupon.type)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算优惠券折扣金额
|
||||
*/
|
||||
export const calculateCouponDiscount = (
|
||||
coupon: CouponCardProps,
|
||||
totalAmount: number
|
||||
): number => {
|
||||
// 检查是否满足使用条件
|
||||
if (coupon.minAmount && totalAmount < coupon.minAmount) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 检查优惠券状态
|
||||
if (coupon.status !== 0) {
|
||||
return 0
|
||||
}
|
||||
|
||||
switch (coupon.type) {
|
||||
case 10: // 满减券
|
||||
return coupon.amount
|
||||
case 20: // 折扣券
|
||||
return totalAmount * (1 - coupon.amount / 10)
|
||||
case 30: // 免费券
|
||||
return totalAmount
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查优惠券是否可用
|
||||
*/
|
||||
export const isCouponUsable = (
|
||||
coupon: CouponCardProps,
|
||||
totalAmount: number
|
||||
): boolean => {
|
||||
// 状态检查
|
||||
if (coupon.status !== 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 金额条件检查
|
||||
if (coupon.minAmount && totalAmount < coupon.minAmount) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取优惠券不可用原因
|
||||
*/
|
||||
export const getCouponUnusableReason = (
|
||||
coupon: CouponCardProps,
|
||||
totalAmount: number
|
||||
): string => {
|
||||
if (coupon.status === 1) {
|
||||
return '优惠券已使用'
|
||||
}
|
||||
|
||||
if (coupon.status === 2) {
|
||||
return '优惠券已过期'
|
||||
}
|
||||
|
||||
if (coupon.minAmount && totalAmount < coupon.minAmount) {
|
||||
return `需满${coupon.minAmount}元才能使用`
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化优惠券标题
|
||||
*/
|
||||
export const formatCouponTitle = (coupon: CouponCardProps): string => {
|
||||
if (coupon.title) {
|
||||
return coupon.title
|
||||
}
|
||||
|
||||
switch (coupon.type) {
|
||||
case 10: // 满减券
|
||||
if (coupon.minAmount && coupon.minAmount > 0) {
|
||||
return `满${coupon.minAmount}减${coupon.amount}`
|
||||
}
|
||||
return `立减${coupon.amount}元`
|
||||
case 20: // 折扣券
|
||||
if (coupon.minAmount && coupon.minAmount > 0) {
|
||||
return `满${coupon.minAmount}享${coupon.amount}折`
|
||||
}
|
||||
return `${coupon.amount}折优惠`
|
||||
case 30: // 免费券
|
||||
return '免费券'
|
||||
default:
|
||||
return '优惠券'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排序优惠券列表
|
||||
* 按照优惠金额从大到小排序,同等优惠金额按过期时间排序
|
||||
*/
|
||||
export const sortCoupons = (
|
||||
coupons: CouponCardProps[],
|
||||
totalAmount: number
|
||||
): CouponCardProps[] => {
|
||||
return [...coupons].sort((a, b) => {
|
||||
// 先按可用性排序
|
||||
const aUsable = isCouponUsable(a, totalAmount)
|
||||
const bUsable = isCouponUsable(b, totalAmount)
|
||||
|
||||
if (aUsable && !bUsable) return -1
|
||||
if (!aUsable && bUsable) return 1
|
||||
|
||||
// 都可用或都不可用时,按优惠金额排序
|
||||
const aDiscount = calculateCouponDiscount(a, totalAmount)
|
||||
const bDiscount = calculateCouponDiscount(b, totalAmount)
|
||||
|
||||
if (aDiscount !== bDiscount) {
|
||||
return bDiscount - aDiscount // 优惠金额大的在前
|
||||
}
|
||||
|
||||
// 优惠金额相同时,按过期时间排序(即将过期的在前)
|
||||
if (a.endTime && b.endTime) {
|
||||
return new Date(a.endTime).getTime() - new Date(b.endTime).getTime()
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤可用优惠券
|
||||
*/
|
||||
export const filterUsableCoupons = (
|
||||
coupons: CouponCardProps[],
|
||||
totalAmount: number
|
||||
): CouponCardProps[] => {
|
||||
return coupons.filter(coupon => isCouponUsable(coupon, totalAmount))
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤不可用优惠券
|
||||
*/
|
||||
export const filterUnusableCoupons = (
|
||||
coupons: CouponCardProps[],
|
||||
totalAmount: number
|
||||
): CouponCardProps[] => {
|
||||
return coupons.filter(coupon => !isCouponUsable(coupon, totalAmount))
|
||||
}
|
||||
103
src/utils/customerStatus.ts
Normal file
103
src/utils/customerStatus.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 客户状态管理工具函数
|
||||
*/
|
||||
|
||||
// 客户状态类型定义
|
||||
export type CustomerStatus = 'all' | 'pending' | 'signed' | 'cancelled';
|
||||
|
||||
// 客户状态配置
|
||||
export const CUSTOMER_STATUS_CONFIG = {
|
||||
all: {
|
||||
label: '全部',
|
||||
color: '#666666',
|
||||
tagType: 'default' as const
|
||||
},
|
||||
pending: {
|
||||
label: '跟进中',
|
||||
color: '#ff8800',
|
||||
tagType: 'warning' as const
|
||||
},
|
||||
signed: {
|
||||
label: '已签约',
|
||||
color: '#52c41a',
|
||||
tagType: 'success' as const
|
||||
},
|
||||
cancelled: {
|
||||
label: '已取消',
|
||||
color: '#999999',
|
||||
tagType: 'default' as const
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取状态文本
|
||||
*/
|
||||
export const getStatusText = (status: CustomerStatus): string => {
|
||||
return CUSTOMER_STATUS_CONFIG[status]?.label || '';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取状态标签类型
|
||||
*/
|
||||
export const getStatusTagType = (status: CustomerStatus) => {
|
||||
return CUSTOMER_STATUS_CONFIG[status]?.tagType || 'default';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取状态颜色
|
||||
*/
|
||||
export const getStatusColor = (status: CustomerStatus): string => {
|
||||
return CUSTOMER_STATUS_CONFIG[status]?.color || '#666666';
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有状态选项
|
||||
*/
|
||||
export const getStatusOptions = () => {
|
||||
return Object.entries(CUSTOMER_STATUS_CONFIG).map(([value, config]) => ({
|
||||
value: value as CustomerStatus,
|
||||
label: config.label
|
||||
}));
|
||||
};
|
||||
|
||||
/**
|
||||
* 将数字状态映射为字符串状态
|
||||
*/
|
||||
export const mapApplyStatusToCustomerStatus = (applyStatus: number): CustomerStatus => {
|
||||
switch (applyStatus) {
|
||||
case 10:
|
||||
return 'pending'; // 跟进中
|
||||
case 20:
|
||||
return 'signed'; // 已签约
|
||||
case 30:
|
||||
return 'cancelled'; // 已取消
|
||||
default:
|
||||
return 'pending'; // 默认为跟进中
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 将字符串状态映射为数字状态
|
||||
*/
|
||||
export const mapCustomerStatusToApplyStatus = (customerStatus: CustomerStatus): number | undefined => {
|
||||
switch (customerStatus) {
|
||||
case 'pending':
|
||||
return 10; // 跟进中
|
||||
case 'signed':
|
||||
return 20; // 已签约
|
||||
case 'cancelled':
|
||||
return 30; // 已取消
|
||||
case 'all':
|
||||
return undefined; // 全部,不筛选
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 临时函数:生成随机状态(实际项目中应该删除,从数据库获取真实状态)
|
||||
*/
|
||||
export const getRandomStatus = (): CustomerStatus => {
|
||||
const statuses: CustomerStatus[] = ['pending', 'signed', 'cancelled'];
|
||||
return statuses[Math.floor(Math.random() * statuses.length)];
|
||||
};
|
||||
126
src/utils/dateUtils.ts
Normal file
126
src/utils/dateUtils.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 日期格式化工具函数
|
||||
* 用于处理各种日期格式转换
|
||||
*/
|
||||
|
||||
/**
|
||||
* 格式化日期为数据库格式 YYYY-MM-DD HH:mm:ss
|
||||
* @param dateStr 输入的日期字符串,支持多种格式
|
||||
* @returns 数据库格式的日期字符串
|
||||
*/
|
||||
export const formatDateForDatabase = (dateStr: string): string => {
|
||||
if (!dateStr) return ''
|
||||
|
||||
let parts: string[] = []
|
||||
|
||||
// 处理不同的日期格式
|
||||
if (dateStr.includes('/')) {
|
||||
// 处理 YYYY/MM/DD 或 YYYY/M/D 格式
|
||||
parts = dateStr.split('/')
|
||||
} else if (dateStr.includes('-')) {
|
||||
// 处理 YYYY-MM-DD 或 YYYY-M-D 格式
|
||||
parts = dateStr.split('-')
|
||||
} else {
|
||||
return dateStr
|
||||
}
|
||||
|
||||
if (parts.length !== 3) return dateStr
|
||||
|
||||
const year = parts[0]
|
||||
const month = parts[1].padStart(2, '0')
|
||||
const day = parts[2].padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} 00:00:00`
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库格式提取日期部分用于Calendar组件显示
|
||||
* @param dateTimeStr 数据库格式的日期时间字符串
|
||||
* @returns Calendar组件需要的格式 (YYYY-M-D)
|
||||
*/
|
||||
export const extractDateForCalendar = (dateTimeStr: string): string => {
|
||||
if (!dateTimeStr) return ''
|
||||
|
||||
// 处理不同的输入格式
|
||||
let dateStr = ''
|
||||
if (dateTimeStr.includes(' ')) {
|
||||
// 从 "YYYY-MM-DD HH:mm:ss" 格式中提取日期部分
|
||||
dateStr = dateTimeStr.split(' ')[0]
|
||||
} else {
|
||||
dateStr = dateTimeStr
|
||||
}
|
||||
|
||||
// 转换为Calendar组件需要的格式 (YYYY-M-D)
|
||||
if (dateStr.includes('-')) {
|
||||
const parts = dateStr.split('-')
|
||||
if (parts.length === 3) {
|
||||
const year = parts[0]
|
||||
const month = parseInt(parts[1]).toString() // 去掉前导0
|
||||
const day = parseInt(parts[2]).toString() // 去掉前导0
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
}
|
||||
|
||||
return dateStr
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化日期为用户友好的显示格式 YYYY-MM-DD
|
||||
* @param dateStr 输入的日期字符串
|
||||
* @returns 用户友好的日期格式
|
||||
*/
|
||||
export const formatDateForDisplay = (dateStr: string): string => {
|
||||
if (!dateStr) return ''
|
||||
|
||||
// 如果是数据库格式,先提取日期部分
|
||||
let dateOnly = dateStr
|
||||
if (dateStr.includes(' ')) {
|
||||
dateOnly = dateStr.split(' ')[0]
|
||||
}
|
||||
|
||||
// 如果已经是标准格式,直接返回
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(dateOnly)) {
|
||||
return dateOnly
|
||||
}
|
||||
|
||||
// 处理其他格式
|
||||
let parts: string[] = []
|
||||
if (dateOnly.includes('/')) {
|
||||
parts = dateOnly.split('/')
|
||||
} else if (dateOnly.includes('-')) {
|
||||
parts = dateOnly.split('-')
|
||||
} else {
|
||||
return dateStr
|
||||
}
|
||||
|
||||
if (parts.length !== 3) return dateStr
|
||||
|
||||
const year = parts[0]
|
||||
const month = parts[1].padStart(2, '0')
|
||||
const day = parts[2].padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期的字符串格式
|
||||
* @param format 'database' | 'display' | 'calendar'
|
||||
* @returns 格式化的当前日期
|
||||
*/
|
||||
export const getCurrentDate = (format: 'database' | 'display' | 'calendar' = 'display'): string => {
|
||||
const now = new Date()
|
||||
const year = now.getFullYear()
|
||||
const month = now.getMonth() + 1
|
||||
const day = now.getDate()
|
||||
|
||||
switch (format) {
|
||||
case 'database':
|
||||
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} 00:00:00`
|
||||
case 'display':
|
||||
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
||||
case 'calendar':
|
||||
return `${year}-${month}-${day}`
|
||||
default:
|
||||
return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
|
||||
}
|
||||
}
|
||||
110
src/utils/domain.ts
Normal file
110
src/utils/domain.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// 解析域名结构
|
||||
export function getHost(): any {
|
||||
const host = window.location.host;
|
||||
return host.split('.');
|
||||
}
|
||||
|
||||
// 是否https
|
||||
export function isHttps() {
|
||||
const protocol = window.location.protocol;
|
||||
if (protocol == 'https:') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取原始域名
|
||||
* @return http://www.domain.com
|
||||
*/
|
||||
export function getOriginDomain(): string {
|
||||
return window.origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 域名的第一部分
|
||||
* 获取tenantId
|
||||
* @return 10140
|
||||
*/
|
||||
export function getDomainPart1(): any {
|
||||
const split = getHost();
|
||||
if (split[0] == '127') {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof (split[0])) {
|
||||
return split[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过解析泛域名获取租户ID
|
||||
* https://10140.wsdns.cn
|
||||
* @return 10140
|
||||
*/
|
||||
export function getTenantId() {
|
||||
let tenantId = localStorage.getItem('TenantId');
|
||||
if(getDomainPart1()){
|
||||
tenantId = getDomainPart1();
|
||||
return tenantId;
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根域名
|
||||
* hostname
|
||||
*/
|
||||
export function getHostname(): string {
|
||||
return window.location.hostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取域名
|
||||
* @return https://www.domain.com
|
||||
*/
|
||||
export function getDomain(): string {
|
||||
return window.location.protocol + '//www.' + getRootDomain();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根域名
|
||||
* abc.com
|
||||
*/
|
||||
export function getRootDomain(): string {
|
||||
const split = getHost();
|
||||
return split[split.length - 2] + '.' + split[split.length - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取二级域名
|
||||
* @return abc.com
|
||||
*/
|
||||
export function getSubDomainPath(): string {
|
||||
const split = getHost();
|
||||
if (split.length == 2) {
|
||||
return '';
|
||||
}
|
||||
return split[split.length - 3];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品标识
|
||||
* @return 10048
|
||||
*/
|
||||
export function getProductCode(): string | null {
|
||||
const subDomain = getSubDomainPath();
|
||||
if (subDomain == undefined) {
|
||||
return null;
|
||||
}
|
||||
const split = subDomain.split('-');
|
||||
return split[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制台域名
|
||||
*/
|
||||
export function navSubDomain(path: string): string {
|
||||
return `${window.location.protocol}//${path}.${getRootDomain()}`;
|
||||
}
|
||||
|
||||
302
src/utils/errorHandler.ts
Normal file
302
src/utils/errorHandler.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
|
||||
// 定义本地的RequestError类,避免循环依赖
|
||||
export class RequestError extends Error {
|
||||
public type: string;
|
||||
public code?: number;
|
||||
public data?: any;
|
||||
|
||||
constructor(message: string, type: string, code?: number, data?: any) {
|
||||
super(message);
|
||||
this.name = 'RequestError';
|
||||
this.type = type;
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
// 错误类型枚举
|
||||
export enum ErrorType {
|
||||
NETWORK_ERROR = 'NETWORK_ERROR',
|
||||
TIMEOUT_ERROR = 'TIMEOUT_ERROR',
|
||||
BUSINESS_ERROR = 'BUSINESS_ERROR',
|
||||
AUTH_ERROR = 'AUTH_ERROR',
|
||||
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
|
||||
}
|
||||
|
||||
// 错误级别枚举
|
||||
export enum ErrorLevel {
|
||||
INFO = 'info',
|
||||
WARNING = 'warning',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal'
|
||||
}
|
||||
|
||||
// 错误信息接口
|
||||
export interface ErrorInfo {
|
||||
message: string;
|
||||
level: ErrorLevel;
|
||||
type: string;
|
||||
stack?: string;
|
||||
extra?: any;
|
||||
timestamp: number;
|
||||
userId?: string;
|
||||
page?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局错误处理器
|
||||
*/
|
||||
class GlobalErrorHandler {
|
||||
private static instance: GlobalErrorHandler;
|
||||
private errorQueue: ErrorInfo[] = [];
|
||||
private maxQueueSize = 50;
|
||||
|
||||
private constructor() {
|
||||
this.setupGlobalErrorHandlers();
|
||||
}
|
||||
|
||||
public static getInstance(): GlobalErrorHandler {
|
||||
if (!GlobalErrorHandler.instance) {
|
||||
GlobalErrorHandler.instance = new GlobalErrorHandler();
|
||||
}
|
||||
return GlobalErrorHandler.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置全局错误处理器
|
||||
*/
|
||||
private setupGlobalErrorHandlers() {
|
||||
// 捕获未处理的Promise rejection
|
||||
if (typeof window !== 'undefined') {
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
this.handleError(event.reason, ErrorLevel.ERROR, 'UnhandledPromiseRejection');
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理错误
|
||||
*/
|
||||
public handleError(
|
||||
error: any,
|
||||
level: ErrorLevel = ErrorLevel.ERROR,
|
||||
type: string = 'Unknown',
|
||||
extra?: any
|
||||
) {
|
||||
const errorInfo = this.createErrorInfo(error, level, type, extra);
|
||||
|
||||
// 添加到错误队列
|
||||
this.addToQueue(errorInfo);
|
||||
|
||||
// 根据错误级别决定处理方式
|
||||
switch (level) {
|
||||
case ErrorLevel.FATAL:
|
||||
this.handleFatalError(errorInfo);
|
||||
break;
|
||||
case ErrorLevel.ERROR:
|
||||
this.handleNormalError(errorInfo);
|
||||
break;
|
||||
case ErrorLevel.WARNING:
|
||||
this.handleWarning(errorInfo);
|
||||
break;
|
||||
case ErrorLevel.INFO:
|
||||
this.handleInfo(errorInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
// 上报错误
|
||||
this.reportError(errorInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误信息对象
|
||||
*/
|
||||
private createErrorInfo(
|
||||
error: any,
|
||||
level: ErrorLevel,
|
||||
type: string,
|
||||
extra?: any
|
||||
): ErrorInfo {
|
||||
let message = '未知错误';
|
||||
let stack: string | undefined;
|
||||
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
stack = error.stack;
|
||||
} else if (error instanceof RequestError) {
|
||||
message = error.message;
|
||||
type = error.type;
|
||||
extra = { ...extra, code: error.code, data: error.data };
|
||||
} else if (typeof error === 'string') {
|
||||
message = error;
|
||||
} else if (error && typeof error === 'object') {
|
||||
message = error.message || error.errMsg || JSON.stringify(error);
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
level,
|
||||
type,
|
||||
stack,
|
||||
extra,
|
||||
timestamp: Date.now(),
|
||||
userId: Taro.getStorageSync('UserId') || undefined,
|
||||
page: this.getCurrentPage()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前页面路径
|
||||
*/
|
||||
private getCurrentPage(): string {
|
||||
try {
|
||||
const pages = Taro.getCurrentPages();
|
||||
const currentPage = pages[pages.length - 1];
|
||||
return currentPage?.route || 'unknown';
|
||||
} catch {
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加到错误队列
|
||||
*/
|
||||
private addToQueue(errorInfo: ErrorInfo) {
|
||||
this.errorQueue.push(errorInfo);
|
||||
|
||||
// 保持队列大小
|
||||
if (this.errorQueue.length > this.maxQueueSize) {
|
||||
this.errorQueue.shift();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理致命错误
|
||||
*/
|
||||
private handleFatalError(errorInfo: ErrorInfo) {
|
||||
console.error('Fatal Error:', errorInfo);
|
||||
|
||||
Taro.showModal({
|
||||
title: '严重错误',
|
||||
content: '应用遇到严重错误,需要重启',
|
||||
showCancel: false,
|
||||
confirmText: '重启应用',
|
||||
success: () => {
|
||||
Taro.reLaunch({ url: '/pages/index/index' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通错误
|
||||
*/
|
||||
private handleNormalError(errorInfo: ErrorInfo) {
|
||||
console.error('Error:', errorInfo);
|
||||
|
||||
// 根据错误类型显示不同的提示
|
||||
let title = '操作失败';
|
||||
|
||||
if (errorInfo.type === ErrorType.NETWORK_ERROR) {
|
||||
title = '网络连接失败';
|
||||
} else if (errorInfo.type === ErrorType.TIMEOUT_ERROR) {
|
||||
title = '请求超时';
|
||||
} else if (errorInfo.type === ErrorType.AUTH_ERROR) {
|
||||
title = '认证失败';
|
||||
}
|
||||
|
||||
Taro.showToast({
|
||||
title: errorInfo.message || title,
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理警告
|
||||
*/
|
||||
private handleWarning(errorInfo: ErrorInfo) {
|
||||
console.warn('Warning:', errorInfo);
|
||||
|
||||
// 警告通常不需要用户交互,只记录日志
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理信息
|
||||
*/
|
||||
private handleInfo(errorInfo: ErrorInfo) {
|
||||
console.info('Info:', errorInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上报错误到服务器
|
||||
*/
|
||||
private reportError(errorInfo: ErrorInfo) {
|
||||
try {
|
||||
// 这里可以实现错误上报逻辑
|
||||
// 例如发送到后端日志系统、第三方监控服务等
|
||||
|
||||
// 示例:发送到后端
|
||||
// request.post('/api/error/report', errorInfo).catch(() => {
|
||||
// // 上报失败也不要影响用户体验
|
||||
// });
|
||||
|
||||
// 开发环境下打印详细信息
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.group('🚨 Error Report');
|
||||
console.log('Message:', errorInfo.message);
|
||||
console.log('Level:', errorInfo.level);
|
||||
console.log('Type:', errorInfo.type);
|
||||
console.log('Page:', errorInfo.page);
|
||||
console.log('UserId:', errorInfo.userId);
|
||||
console.log('Timestamp:', new Date(errorInfo.timestamp).toLocaleString());
|
||||
if (errorInfo.stack) {
|
||||
console.log('Stack:', errorInfo.stack);
|
||||
}
|
||||
if (errorInfo.extra) {
|
||||
console.log('Extra:', errorInfo.extra);
|
||||
}
|
||||
console.groupEnd();
|
||||
}
|
||||
} catch (reportError) {
|
||||
console.error('Failed to report error:', reportError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误队列
|
||||
*/
|
||||
public getErrorQueue(): ErrorInfo[] {
|
||||
return [...this.errorQueue];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空错误队列
|
||||
*/
|
||||
public clearErrorQueue() {
|
||||
this.errorQueue = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例实例
|
||||
export const errorHandler = GlobalErrorHandler.getInstance();
|
||||
|
||||
// 便捷方法
|
||||
export const handleError = (error: any, level?: ErrorLevel, type?: string, extra?: any) => {
|
||||
errorHandler.handleError(error, level, type, extra);
|
||||
};
|
||||
|
||||
export const handleFatalError = (error: any, extra?: any) => {
|
||||
errorHandler.handleError(error, ErrorLevel.FATAL, 'FatalError', extra);
|
||||
};
|
||||
|
||||
export const handleWarning = (error: any, extra?: any) => {
|
||||
errorHandler.handleError(error, ErrorLevel.WARNING, 'Warning', extra);
|
||||
};
|
||||
|
||||
export const handleInfo = (message: string, extra?: any) => {
|
||||
errorHandler.handleError(message, ErrorLevel.INFO, 'Info', extra);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
296
src/utils/invite.ts
Normal file
296
src/utils/invite.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import {bindRefereeRelation} from "@/api/invite";
|
||||
|
||||
/**
|
||||
* 邀请参数接口
|
||||
*/
|
||||
export interface InviteParams {
|
||||
inviter?: string;
|
||||
source?: string;
|
||||
t?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析小程序启动参数中的邀请信息
|
||||
*/
|
||||
export function parseInviteParams(options: any): InviteParams | null {
|
||||
try {
|
||||
console.log('解析邀请参数:', options)
|
||||
|
||||
// 优先从 query.scene 中解析邀请信息
|
||||
if (options.query && options.query.scene) {
|
||||
const sceneStr = typeof options.query.scene === 'string' ? options.query.scene : String(options.query.scene)
|
||||
console.log('从 query.scene 解析:', sceneStr)
|
||||
|
||||
// 处理 uid_xxxxx 格式的参数
|
||||
if (sceneStr.startsWith('uid_')) {
|
||||
const uid = sceneStr.replace('uid_', '')
|
||||
if (uid) {
|
||||
return {
|
||||
inviter: uid,
|
||||
source: 'qrcode',
|
||||
t: String(Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 key=value&key=value 格式的参数
|
||||
if (sceneStr.includes('=')) {
|
||||
const params: InviteParams = {}
|
||||
const pairs = sceneStr.split('&')
|
||||
|
||||
pairs.forEach((pair: string) => {
|
||||
const [key, value] = pair.split('=')
|
||||
if (key && value) {
|
||||
switch (key) {
|
||||
case 'inviter':
|
||||
case 'uid':
|
||||
params.inviter = decodeURIComponent(value)
|
||||
break
|
||||
case 'source':
|
||||
params.source = decodeURIComponent(value)
|
||||
break
|
||||
case 't':
|
||||
params.t = decodeURIComponent(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (params.inviter) {
|
||||
return params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从 scene 参数中解析邀请信息(兼容旧版本)
|
||||
if (options.scene) {
|
||||
const sceneStr = typeof options.scene === 'string' ? options.scene : String(options.scene)
|
||||
console.log('从 scene 解析:', sceneStr)
|
||||
|
||||
// 处理 uid_xxxxx 格式的参数
|
||||
if (sceneStr.startsWith('uid_')) {
|
||||
const uid = sceneStr.replace('uid_', '')
|
||||
if (uid) {
|
||||
return {
|
||||
inviter: uid,
|
||||
source: 'qrcode',
|
||||
t: String(Date.now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 key=value&key=value 格式的参数
|
||||
if (sceneStr.includes('=')) {
|
||||
const params: InviteParams = {}
|
||||
const pairs = sceneStr.split('&')
|
||||
|
||||
pairs.forEach((pair: string) => {
|
||||
const [key, value] = pair.split('=')
|
||||
if (key && value) {
|
||||
switch (key) {
|
||||
case 'inviter':
|
||||
case 'uid':
|
||||
params.inviter = decodeURIComponent(value)
|
||||
break
|
||||
case 'source':
|
||||
params.source = decodeURIComponent(value)
|
||||
break
|
||||
case 't':
|
||||
params.t = decodeURIComponent(value)
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (params.inviter) {
|
||||
return params
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 从 query 参数中解析邀请信息(兼容旧版本)
|
||||
if (options.referrer) {
|
||||
return {
|
||||
inviter: options.referrer,
|
||||
source: 'link'
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('解析邀请参数失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存邀请信息到本地存储
|
||||
*/
|
||||
export function saveInviteParams(params: InviteParams) {
|
||||
try {
|
||||
Taro.setStorageSync('invite_params', {
|
||||
...params,
|
||||
timestamp: Date.now()
|
||||
})
|
||||
console.log('邀请参数已保存:', params)
|
||||
} catch (error) {
|
||||
console.error('保存邀请参数失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地存储的邀请信息
|
||||
*/
|
||||
export function getStoredInviteParams(): InviteParams | null {
|
||||
try {
|
||||
const stored = Taro.getStorageSync('invite_params')
|
||||
if (stored && stored.inviter) {
|
||||
// 检查是否过期(24小时)
|
||||
const now = Date.now()
|
||||
const expireTime = 24 * 60 * 60 * 1000 // 24小时
|
||||
|
||||
if (now - stored.timestamp < expireTime) {
|
||||
return {
|
||||
inviter: stored.inviter,
|
||||
source: stored.source || 'unknown',
|
||||
t: stored.t
|
||||
}
|
||||
} else {
|
||||
// 过期则清除
|
||||
clearInviteParams()
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('获取邀请参数失败:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除本地存储的邀请信息
|
||||
*/
|
||||
export function clearInviteParams() {
|
||||
try {
|
||||
Taro.removeStorageSync('invite_params')
|
||||
console.log('邀请参数已清除')
|
||||
} catch (error) {
|
||||
console.error('清除邀请参数失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理邀请关系建立
|
||||
*/
|
||||
export async function handleInviteRelation(userId: number): Promise<boolean> {
|
||||
try {
|
||||
const inviteParams = getStoredInviteParams()
|
||||
if (!inviteParams || !inviteParams.inviter) {
|
||||
return false
|
||||
}
|
||||
|
||||
const inviterId = parseInt(inviteParams.inviter)
|
||||
if (isNaN(inviterId) || inviterId === userId) {
|
||||
// 邀请人ID无效或自己邀请自己
|
||||
clearInviteParams()
|
||||
return false
|
||||
}
|
||||
|
||||
// 建立邀请关系
|
||||
await bindRefereeRelation({
|
||||
dealerId: inviterId,
|
||||
userId: userId,
|
||||
source: inviteParams.source || 'unknown',
|
||||
scene: `inviter=${inviterId}&source=${inviteParams.source}&t=${inviteParams.t}`
|
||||
})
|
||||
|
||||
// 清除本地存储的邀请参数
|
||||
clearInviteParams()
|
||||
|
||||
console.log(`邀请关系建立成功: ${inviterId} -> ${userId}`)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('建立邀请关系失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有待处理的邀请
|
||||
*/
|
||||
export function hasPendingInvite(): boolean {
|
||||
const params = getStoredInviteParams()
|
||||
return !!(params && params.inviter)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请来源的显示名称
|
||||
*/
|
||||
export function getSourceDisplayName(source: string): string {
|
||||
const sourceMap: Record<string, string> = {
|
||||
'qrcode': '小程序码',
|
||||
'link': '分享链接',
|
||||
'share': '好友分享',
|
||||
'poster': '海报分享',
|
||||
'unknown': '未知来源'
|
||||
}
|
||||
|
||||
return sourceMap[source] || source
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证邀请码格式
|
||||
*/
|
||||
export function validateInviteCode(scene: string): boolean {
|
||||
try {
|
||||
if (!scene) return false
|
||||
|
||||
// 检查是否包含必要的参数
|
||||
const hasInviter = scene.includes('inviter=')
|
||||
const hasSource = scene.includes('source=')
|
||||
|
||||
return hasInviter && hasSource
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成邀请场景值
|
||||
*/
|
||||
export function generateInviteScene(inviterId: number, source: string): string {
|
||||
const timestamp = Date.now()
|
||||
return `inviter=${inviterId}&source=${source}&t=${timestamp}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计邀请来源
|
||||
*/
|
||||
export function trackInviteSource(source: string, inviterId?: number) {
|
||||
try {
|
||||
// 记录邀请来源统计
|
||||
const trackData = {
|
||||
source,
|
||||
inviterId,
|
||||
timestamp: Date.now(),
|
||||
userAgent: Taro.getSystemInfoSync()
|
||||
}
|
||||
|
||||
// 可以发送到统计服务
|
||||
console.log('邀请来源统计:', trackData)
|
||||
|
||||
// 暂存到本地,后续可批量上报
|
||||
const existingTracks = Taro.getStorageSync('invite_tracks') || []
|
||||
existingTracks.push(trackData)
|
||||
|
||||
// 只保留最近100条记录
|
||||
if (existingTracks.length > 100) {
|
||||
existingTracks.splice(0, existingTracks.length - 100)
|
||||
}
|
||||
|
||||
Taro.setStorageSync('invite_tracks', existingTracks)
|
||||
} catch (error) {
|
||||
console.error('统计邀请来源失败:', error)
|
||||
}
|
||||
}
|
||||
31
src/utils/jsonUtils.ts
Normal file
31
src/utils/jsonUtils.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 判断字符串是否为有效的JSON格式
|
||||
* @param str 要检测的字符串
|
||||
* @returns boolean
|
||||
*/
|
||||
export function isValidJSON(str: string): boolean {
|
||||
if (typeof str !== 'string' || str.trim() === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
JSON.parse(str);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全解析JSON,失败时返回默认值
|
||||
* @param str JSON字符串
|
||||
* @param defaultValue 默认值
|
||||
* @returns 解析结果或默认值
|
||||
*/
|
||||
export function safeJSONParse<T>(str: string, defaultValue: T): T {
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (error) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
344
src/utils/payment.ts
Normal file
344
src/utils/payment.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
import { createOrder, WxPayResult } from '@/api/shop/shopOrder';
|
||||
import { OrderCreateRequest } from '@/api/shop/shopOrder/model';
|
||||
|
||||
/**
|
||||
* 支付类型枚举
|
||||
*/
|
||||
export enum PaymentType {
|
||||
BALANCE = 0, // 余额支付
|
||||
WECHAT = 1, // 微信支付
|
||||
ALIPAY = 3, // 支付宝支付
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付结果回调
|
||||
*/
|
||||
export interface PaymentCallback {
|
||||
onSuccess?: () => void;
|
||||
onError?: (error: string) => void;
|
||||
onComplete?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一支付处理类
|
||||
*/
|
||||
export class PaymentHandler {
|
||||
|
||||
/**
|
||||
* 执行支付
|
||||
* @param orderData 订单数据
|
||||
* @param paymentType 支付类型
|
||||
* @param callback 回调函数
|
||||
*/
|
||||
static async pay(
|
||||
orderData: OrderCreateRequest,
|
||||
paymentType: PaymentType,
|
||||
callback?: PaymentCallback
|
||||
): Promise<void> {
|
||||
Taro.showLoading({ title: '支付中...' });
|
||||
|
||||
try {
|
||||
// 设置支付类型
|
||||
orderData.payType = paymentType;
|
||||
|
||||
console.log('创建订单请求:', orderData);
|
||||
|
||||
// 创建订单
|
||||
const result = await createOrder(orderData);
|
||||
|
||||
console.log('订单创建结果:', result);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('创建订单失败');
|
||||
}
|
||||
|
||||
// 验证订单创建结果
|
||||
if (!result.orderNo) {
|
||||
throw new Error('订单号获取失败');
|
||||
}
|
||||
|
||||
let paymentSuccess = false;
|
||||
|
||||
// 根据支付类型处理
|
||||
switch (paymentType) {
|
||||
case PaymentType.WECHAT:
|
||||
await this.handleWechatPay(result);
|
||||
paymentSuccess = true;
|
||||
break;
|
||||
case PaymentType.BALANCE:
|
||||
paymentSuccess = await this.handleBalancePay(result);
|
||||
break;
|
||||
case PaymentType.ALIPAY:
|
||||
await this.handleAlipay(result);
|
||||
paymentSuccess = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error('不支持的支付方式');
|
||||
}
|
||||
|
||||
// 只有确认支付成功才显示成功提示和跳转
|
||||
if (paymentSuccess) {
|
||||
console.log('支付成功,订单号:', result.orderNo);
|
||||
|
||||
Taro.showToast({
|
||||
title: '支付成功',
|
||||
icon: 'success'
|
||||
});
|
||||
|
||||
callback?.onSuccess?.();
|
||||
|
||||
// 跳转到订单页面
|
||||
setTimeout(() => {
|
||||
Taro.navigateTo({ url: '/user/order/order' });
|
||||
}, 2000);
|
||||
} else {
|
||||
throw new Error('支付未完成');
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('支付失败:', error);
|
||||
|
||||
// 获取详细错误信息
|
||||
const errorMessage = this.getErrorMessage(error);
|
||||
|
||||
Taro.showToast({
|
||||
title: errorMessage,
|
||||
icon: 'error'
|
||||
});
|
||||
|
||||
// 标记错误已处理,避免上层重复处理
|
||||
error.handled = true;
|
||||
callback?.onError?.(errorMessage);
|
||||
|
||||
// 重新抛出错误,让上层知道支付失败
|
||||
throw error;
|
||||
} finally {
|
||||
Taro.hideLoading();
|
||||
callback?.onComplete?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理微信支付
|
||||
*/
|
||||
private static async handleWechatPay(result: WxPayResult): Promise<void> {
|
||||
console.log('处理微信支付:', result);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('微信支付参数错误');
|
||||
}
|
||||
|
||||
// 验证微信支付必要参数
|
||||
if (!result.timeStamp || !result.nonceStr || !result.package || !result.paySign) {
|
||||
throw new Error('微信支付参数不完整');
|
||||
}
|
||||
|
||||
try {
|
||||
await Taro.requestPayment({
|
||||
timeStamp: result.timeStamp,
|
||||
nonceStr: result.nonceStr,
|
||||
package: result.package,
|
||||
signType: result.signType as any, // 类型转换,因为微信支付的signType是字符串
|
||||
paySign: result.paySign,
|
||||
});
|
||||
|
||||
console.log('微信支付成功');
|
||||
} catch (payError: any) {
|
||||
console.error('微信支付失败:', payError);
|
||||
|
||||
// 处理微信支付特定错误
|
||||
if (payError.errMsg) {
|
||||
if (payError.errMsg.includes('cancel')) {
|
||||
throw new Error('用户取消支付');
|
||||
} else if (payError.errMsg.includes('fail')) {
|
||||
throw new Error('微信支付失败,请重试');
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('微信支付失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理余额支付
|
||||
*/
|
||||
private static async handleBalancePay(result: any): Promise<boolean> {
|
||||
console.log('处理余额支付:', result);
|
||||
|
||||
if (!result || !result.orderNo) {
|
||||
throw new Error('余额支付参数错误');
|
||||
}
|
||||
|
||||
// 检查支付状态 - 根据后端返回的字段调整
|
||||
if (result.payStatus === false || result.payStatus === 0 || result.payStatus === '0') {
|
||||
throw new Error('余额不足或支付失败');
|
||||
}
|
||||
|
||||
// 检查订单状态 - 1表示已付款
|
||||
if (result.orderStatus !== undefined && result.orderStatus !== 1) {
|
||||
throw new Error('订单状态异常,支付可能未成功');
|
||||
}
|
||||
|
||||
// 验证实际扣款金额
|
||||
if (result.payPrice !== undefined) {
|
||||
const payPrice = parseFloat(result.payPrice);
|
||||
if (payPrice <= 0) {
|
||||
throw new Error('支付金额异常');
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有错误信息字段,检查是否有错误
|
||||
if (result.error || result.errorMsg) {
|
||||
throw new Error(result.error || result.errorMsg);
|
||||
}
|
||||
|
||||
console.log('余额支付验证通过');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理支付宝支付
|
||||
*/
|
||||
private static async handleAlipay(_result: any): Promise<void> {
|
||||
// 支付宝支付逻辑,根据实际情况实现
|
||||
throw new Error('支付宝支付暂未实现');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详细错误信息
|
||||
*/
|
||||
private static getErrorMessage(error: any): string {
|
||||
if (!error.message) {
|
||||
return '支付失败,请重试';
|
||||
}
|
||||
|
||||
const message = error.message;
|
||||
|
||||
// 余额相关错误
|
||||
if (message.includes('余额不足') || message.includes('balance')) {
|
||||
return '账户余额不足,请充值后重试';
|
||||
}
|
||||
|
||||
// 优惠券相关错误
|
||||
if (message.includes('优惠券') || message.includes('coupon')) {
|
||||
return '优惠券使用失败,请重新选择';
|
||||
}
|
||||
|
||||
// 库存相关错误
|
||||
if (message.includes('库存') || message.includes('stock')) {
|
||||
return '商品库存不足,请减少购买数量';
|
||||
}
|
||||
|
||||
// 地址相关错误
|
||||
if (message.includes('地址') || message.includes('address')) {
|
||||
return '收货地址信息有误,请重新选择';
|
||||
}
|
||||
|
||||
// 订单相关错误
|
||||
if (message.includes('订单') || message.includes('order')) {
|
||||
return '订单创建失败,请重试';
|
||||
}
|
||||
|
||||
// 网络相关错误
|
||||
if (message.includes('网络') || message.includes('network') || message.includes('timeout')) {
|
||||
return '网络连接异常,请检查网络后重试';
|
||||
}
|
||||
|
||||
// 微信支付相关错误
|
||||
if (message.includes('微信') || message.includes('wechat') || message.includes('wx')) {
|
||||
return '微信支付失败,请重试';
|
||||
}
|
||||
|
||||
// 返回原始错误信息
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 快捷支付方法
|
||||
*/
|
||||
export const quickPay = {
|
||||
/**
|
||||
* 微信支付
|
||||
*/
|
||||
wechat: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||
return PaymentHandler.pay(orderData, PaymentType.WECHAT, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 余额支付
|
||||
*/
|
||||
balance: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||
return PaymentHandler.pay(orderData, PaymentType.BALANCE, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 支付宝支付
|
||||
*/
|
||||
alipay: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||
return PaymentHandler.pay(orderData, PaymentType.ALIPAY, callback);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建单商品订单数据
|
||||
*/
|
||||
export function buildSingleGoodsOrder(
|
||||
goodsId: number,
|
||||
quantity: number = 1,
|
||||
addressId?: number,
|
||||
options?: {
|
||||
comments?: string;
|
||||
deliveryType?: number;
|
||||
couponId?: any;
|
||||
selfTakeMerchantId?: number;
|
||||
skuId?: number;
|
||||
specInfo?: string;
|
||||
buyerRemarks?: string;
|
||||
}
|
||||
): OrderCreateRequest {
|
||||
return {
|
||||
goodsItems: [
|
||||
{
|
||||
goodsId,
|
||||
quantity,
|
||||
skuId: options?.skuId,
|
||||
specInfo: options?.specInfo
|
||||
}
|
||||
],
|
||||
addressId,
|
||||
payType: PaymentType.WECHAT, // 默认微信支付,会被PaymentHandler覆盖
|
||||
comments: options?.buyerRemarks || options?.comments || '',
|
||||
deliveryType: options?.deliveryType || 0,
|
||||
couponId: options?.couponId,
|
||||
selfTakeMerchantId: options?.selfTakeMerchantId
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建购物车订单数据
|
||||
*/
|
||||
export function buildCartOrder(
|
||||
cartItems: Array<{ goodsId: number; quantity: number }>,
|
||||
addressId?: number,
|
||||
options?: {
|
||||
comments?: string;
|
||||
deliveryType?: number;
|
||||
couponId?: number;
|
||||
selfTakeMerchantId?: number;
|
||||
}
|
||||
): OrderCreateRequest {
|
||||
return {
|
||||
goodsItems: cartItems.map(item => ({
|
||||
goodsId: item.goodsId,
|
||||
quantity: item.quantity
|
||||
})),
|
||||
addressId,
|
||||
payType: PaymentType.WECHAT, // 默认微信支付,会被PaymentHandler覆盖
|
||||
comments: options?.comments || '购物车下单',
|
||||
deliveryType: options?.deliveryType || 0,
|
||||
couponId: options?.couponId,
|
||||
selfTakeMerchantId: options?.selfTakeMerchantId
|
||||
};
|
||||
}
|
||||
425
src/utils/request.ts
Normal file
425
src/utils/request.ts
Normal file
@@ -0,0 +1,425 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { BaseUrl, TenantId } from "@/config/app";
|
||||
|
||||
// 请求配置接口
|
||||
interface RequestConfig {
|
||||
url: string;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
||||
data?: any;
|
||||
header?: Record<string, string>;
|
||||
timeout?: number;
|
||||
retry?: number;
|
||||
showLoading?: boolean;
|
||||
showError?: boolean;
|
||||
returnRaw?: boolean; // 是否返回原始响应数据
|
||||
}
|
||||
|
||||
// API响应接口
|
||||
interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
message?: string;
|
||||
data?: T;
|
||||
}
|
||||
|
||||
// 错误类型枚举
|
||||
enum ErrorType {
|
||||
NETWORK_ERROR = 'NETWORK_ERROR',
|
||||
TIMEOUT_ERROR = 'TIMEOUT_ERROR',
|
||||
BUSINESS_ERROR = 'BUSINESS_ERROR',
|
||||
AUTH_ERROR = 'AUTH_ERROR',
|
||||
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
|
||||
}
|
||||
|
||||
// 自定义错误类
|
||||
class RequestError extends Error {
|
||||
public type: ErrorType;
|
||||
public code?: number;
|
||||
public data?: any;
|
||||
|
||||
constructor(message: string, type: ErrorType, code?: number, data?: any) {
|
||||
super(message);
|
||||
this.name = 'RequestError';
|
||||
this.type = type;
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求配置
|
||||
const DEFAULT_CONFIG = {
|
||||
timeout: 10000, // 10秒超时
|
||||
retry: 2, // 重试2次
|
||||
showLoading: false,
|
||||
showError: true
|
||||
};
|
||||
|
||||
let baseUrl = Taro.getStorageSync('ApiUrl') || BaseUrl;
|
||||
|
||||
// 开发环境配置
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// baseUrl = 'http://localhost:9200/api'
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
const requestInterceptor = (config: RequestConfig): RequestConfig => {
|
||||
// 添加认证token
|
||||
const token = Taro.getStorageSync('access_token');
|
||||
const tenantId = Taro.getStorageSync('TenantId') || TenantId;
|
||||
|
||||
const defaultHeaders: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'TenantId': tenantId
|
||||
};
|
||||
|
||||
if (token) {
|
||||
defaultHeaders['Authorization'] = token;
|
||||
}
|
||||
|
||||
config.header = { ...defaultHeaders, ...config.header };
|
||||
|
||||
// 显示加载提示
|
||||
if (config.showLoading) {
|
||||
Taro.showLoading({ title: '加载中...' });
|
||||
}
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
// 响应拦截器
|
||||
const responseInterceptor = <T>(response: any, config: RequestConfig): T => {
|
||||
// 隐藏加载提示
|
||||
if (config.showLoading) {
|
||||
Taro.hideLoading();
|
||||
}
|
||||
|
||||
const { statusCode, data } = response;
|
||||
|
||||
// HTTP状态码检查
|
||||
if (statusCode !== 200) {
|
||||
throw new RequestError(
|
||||
`HTTP错误: ${statusCode}`,
|
||||
ErrorType.NETWORK_ERROR,
|
||||
statusCode,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
// 如果没有数据,抛出错误
|
||||
if (!data) {
|
||||
throw new RequestError(
|
||||
'响应数据为空',
|
||||
ErrorType.NETWORK_ERROR,
|
||||
statusCode,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
// 业务状态码检查
|
||||
if (typeof data === 'object' && 'code' in data) {
|
||||
const apiResponse = data as ApiResponse<T>;
|
||||
|
||||
// 成功响应
|
||||
if (apiResponse.code === 0) {
|
||||
// 如果配置了返回原始响应,则返回完整响应
|
||||
if (config.returnRaw) {
|
||||
return data as T;
|
||||
}
|
||||
// 否则返回data部分
|
||||
return apiResponse.data as T;
|
||||
}
|
||||
|
||||
// 认证错误
|
||||
if (apiResponse.code === 401 || apiResponse.code === 403) {
|
||||
handleAuthError();
|
||||
throw new RequestError(
|
||||
apiResponse.message || '认证失败',
|
||||
ErrorType.AUTH_ERROR,
|
||||
apiResponse.code,
|
||||
apiResponse.data
|
||||
);
|
||||
}
|
||||
|
||||
// 业务错误
|
||||
throw new RequestError(
|
||||
apiResponse.message || '请求失败',
|
||||
ErrorType.BUSINESS_ERROR,
|
||||
apiResponse.code,
|
||||
apiResponse.data
|
||||
);
|
||||
}
|
||||
|
||||
// 如果不是标准的API响应格式,直接返回数据
|
||||
return data as T;
|
||||
};
|
||||
|
||||
// 处理认证错误
|
||||
const handleAuthError = () => {
|
||||
// 清除本地存储的认证信息
|
||||
try {
|
||||
Taro.removeStorageSync('access_token');
|
||||
Taro.removeStorageSync('User');
|
||||
Taro.removeStorageSync('UserId');
|
||||
Taro.removeStorageSync('TenantId');
|
||||
Taro.removeStorageSync('Phone');
|
||||
} catch (error) {
|
||||
console.error('清除认证信息失败:', error);
|
||||
}
|
||||
|
||||
// 显示提示并跳转到登录页
|
||||
Taro.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
});
|
||||
|
||||
// setTimeout(() => {
|
||||
// Taro.reLaunch({ url: '/passport/login' });
|
||||
// }, 2000);
|
||||
};
|
||||
|
||||
// 错误处理
|
||||
const handleError = (error: RequestError, config: RequestConfig) => {
|
||||
console.error('请求错误:', error);
|
||||
|
||||
if (config.showLoading) {
|
||||
Taro.hideLoading();
|
||||
}
|
||||
|
||||
if (config.showError) {
|
||||
let title = '请求失败';
|
||||
|
||||
switch (error.type) {
|
||||
case ErrorType.NETWORK_ERROR:
|
||||
title = '网络连接失败';
|
||||
break;
|
||||
case ErrorType.TIMEOUT_ERROR:
|
||||
title = '请求超时';
|
||||
break;
|
||||
case ErrorType.BUSINESS_ERROR:
|
||||
title = error.message || '操作失败';
|
||||
break;
|
||||
case ErrorType.AUTH_ERROR:
|
||||
title = '认证失败';
|
||||
break;
|
||||
default:
|
||||
title = '未知错误';
|
||||
}
|
||||
|
||||
Taro.showToast({
|
||||
title,
|
||||
icon: 'error',
|
||||
duration: 2000
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 重试机制
|
||||
const retryRequest = async <T>(
|
||||
config: RequestConfig,
|
||||
retryCount: number = 0
|
||||
): Promise<T> => {
|
||||
try {
|
||||
return await executeRequest<T>(config);
|
||||
} catch (error) {
|
||||
const requestError = error as RequestError;
|
||||
|
||||
// 如果是认证错误或业务错误,不重试
|
||||
if (requestError.type === ErrorType.AUTH_ERROR ||
|
||||
requestError.type === ErrorType.BUSINESS_ERROR) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 如果还有重试次数
|
||||
if (retryCount < (config.retry || DEFAULT_CONFIG.retry)) {
|
||||
console.log(`请求失败,正在重试 ${retryCount + 1}/${config.retry || DEFAULT_CONFIG.retry}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (retryCount + 1))); // 递增延迟
|
||||
return retryRequest<T>(config, retryCount + 1);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 执行请求
|
||||
const executeRequest = <T>(config: RequestConfig): Promise<T> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(new RequestError('请求超时', ErrorType.TIMEOUT_ERROR));
|
||||
}, config.timeout || DEFAULT_CONFIG.timeout);
|
||||
|
||||
Taro.request({
|
||||
url: config.url,
|
||||
method: config.method || 'GET',
|
||||
data: config.data || {},
|
||||
header: config.header || {},
|
||||
success: (res) => {
|
||||
clearTimeout(timer);
|
||||
try {
|
||||
const result = responseInterceptor<T>(res, config);
|
||||
resolve(result);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
clearTimeout(timer);
|
||||
reject(new RequestError(
|
||||
err.errMsg || '网络请求失败',
|
||||
ErrorType.NETWORK_ERROR,
|
||||
undefined,
|
||||
err
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 主请求函数
|
||||
export async function request<T>(options: RequestConfig): Promise<T> {
|
||||
try {
|
||||
// 请求拦截
|
||||
const config = requestInterceptor({ ...DEFAULT_CONFIG, ...options });
|
||||
|
||||
// 执行请求(带重试)
|
||||
const result = await retryRequest<T>(config);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const requestError = error as RequestError;
|
||||
handleError(requestError, options);
|
||||
throw requestError;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建完整URL
|
||||
const buildUrl = (url: string): string => {
|
||||
if (url.indexOf('http') === -1) {
|
||||
return baseUrl + url;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
// 构建查询参数
|
||||
const buildQueryString = (params: Record<string, any>): string => {
|
||||
const queryString = Object.keys(params)
|
||||
.filter(key => params[key] !== undefined && params[key] !== null)
|
||||
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
||||
.join('&');
|
||||
return queryString ? `?${queryString}` : '';
|
||||
};
|
||||
|
||||
// GET请求 - 返回完整的ApiResult响应(适配后台生成的代码)
|
||||
export function get<T>(url: string, params?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
const fullUrl = buildUrl(url) + (params ? buildQueryString(params) : '');
|
||||
return request<T>({
|
||||
url: fullUrl,
|
||||
method: 'GET',
|
||||
returnRaw: true,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// POST请求 - 返回完整的ApiResult响应(适配后台生成的代码)
|
||||
export function post<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'POST',
|
||||
data,
|
||||
returnRaw: true,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// PUT请求 - 返回完整的ApiResult响应(适配后台生成的代码)
|
||||
export function put<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'PUT',
|
||||
data,
|
||||
returnRaw: true,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// PATCH请求 - 返回完整的ApiResult响应(适配后台生成的代码)
|
||||
export function patch<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'PATCH',
|
||||
data,
|
||||
returnRaw: true,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// DELETE请求 - 返回完整的ApiResult响应(适配后台生成的代码)
|
||||
export function del<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'DELETE',
|
||||
data,
|
||||
returnRaw: true,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// 便捷方法 - 自动提取data字段(用于不需要处理完整ApiResult的场景)
|
||||
export function getData<T>(url: string, params?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
const fullUrl = buildUrl(url) + (params ? buildQueryString(params) : '');
|
||||
return request<T>({
|
||||
url: fullUrl,
|
||||
method: 'GET',
|
||||
returnRaw: false,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
export function postData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'POST',
|
||||
data,
|
||||
returnRaw: false,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
export function putData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'PUT',
|
||||
data,
|
||||
returnRaw: false,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
export function delData<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url: buildUrl(url),
|
||||
method: 'DELETE',
|
||||
data,
|
||||
returnRaw: false,
|
||||
...config
|
||||
});
|
||||
}
|
||||
|
||||
// 导出错误类型和错误类,供外部使用
|
||||
export { ErrorType, RequestError };
|
||||
|
||||
// 默认导出
|
||||
export default {
|
||||
request,
|
||||
// 主要方法 - 返回完整ApiResult(适配后台生成代码)
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
patch,
|
||||
del,
|
||||
// 便捷方法 - 自动提取data字段
|
||||
getData,
|
||||
postData,
|
||||
putData,
|
||||
delData,
|
||||
ErrorType,
|
||||
RequestError
|
||||
};
|
||||
20
src/utils/server.ts
Normal file
20
src/utils/server.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import Taro from '@tarojs/taro';
|
||||
import {User} from "@/api/system/user/model";
|
||||
|
||||
// 模版套餐ID - 请根据实际情况修改
|
||||
export const TEMPLATE_ID = '10582';
|
||||
// 服务接口 - 请根据实际情况修改
|
||||
export const SERVER_API_URL = 'https://server.websoft.top/api';
|
||||
// export const SERVER_API_URL = 'http://127.0.0.1:8000/api';
|
||||
/**
|
||||
* 保存用户信息到本地存储
|
||||
* @param token
|
||||
* @param user
|
||||
*/
|
||||
export function saveStorageByLoginUser(token: string, user: User) {
|
||||
Taro.setStorageSync('TenantId',user.tenantId)
|
||||
Taro.setStorageSync('access_token', token)
|
||||
Taro.setStorageSync('UserId', user.userId)
|
||||
Taro.setStorageSync('Phone', user.phone)
|
||||
Taro.setStorageSync('User', user)
|
||||
}
|
||||
39
src/utils/time.ts
Normal file
39
src/utils/time.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 获取当前时间
|
||||
*/
|
||||
export function formatCurrentDate() {
|
||||
// 创建一个Date对象,表示当前日期和时间
|
||||
const now = new Date();
|
||||
|
||||
// 获取年、月、日,并进行必要的格式化
|
||||
const day = String(now.getDate()).padStart(2, '0'); // 获取日,并确保是两位数
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0'); // 获取月,并确保是两位数,月份是从0开始的,所以要加1
|
||||
const year = String(now.getFullYear()).slice(-2); // 获取年份的最后两位数字
|
||||
|
||||
return `${day}${month}${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时分秒
|
||||
*/
|
||||
export function formatHhmmss(){
|
||||
// 创建一个Date对象,表示当前日期和时间
|
||||
const now = new Date();
|
||||
// 获取当前的小时
|
||||
const hour = String(now.getHours()).padStart(2, '0');
|
||||
// 获取当前的分钟
|
||||
const minute = String(now.getMinutes()).padStart(2, '0');
|
||||
// 获取当前的秒数
|
||||
const second = String(now.getSeconds()).padStart(2, '0');
|
||||
return `${String(Number(hour) - 8).padStart(2, '0')}${minute}${second}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前小时
|
||||
*/
|
||||
export function getCurrentHour() {
|
||||
const now = new Date();
|
||||
// 获取当前的小时
|
||||
const hour = String(now.getHours()).padStart(2, '0');
|
||||
return `${String(Number(hour) - 8).padStart(2, '0')}`;
|
||||
}
|
||||
Reference in New Issue
Block a user