购物车逻辑完善

个人信息存储优化
团购支付
This commit is contained in:
2024-03-31 17:22:14 +08:00
parent 1fc0aa432b
commit b502385272
19 changed files with 859 additions and 141 deletions

View File

@@ -24,3 +24,4 @@ export const getOrderList = (data: {
payStatus: number
}
}) => post({ url: URL.orderList, data });

View File

@@ -26,6 +26,7 @@ export interface GoodsBean {
price: number;
send_num: number;
price_ext: number;
payPrice: number;
profile: string;
remark: string;
remark1: string;
@@ -44,6 +45,15 @@ export interface GoodsBean {
update_time: string;
years_id: string;
years_name: string;
/*extra params*/
goodsNum: number;
salePrice: number;
originPrice: number;
consumePrice: number;
stockStock: StockBean
checkedStock: StockBean;
checked: boolean | false;
}
export interface StockBean {

View File

@@ -1,4 +1,6 @@
import { get, post } from '@/utils/request';
import { GroupBuyBean, RecordBean } from '@/api/groupbuy/types';
import { GoodsBean } from '@/api/goods/types';
export const getGroupBuyList = (data: {
pageNum: number,
@@ -9,11 +11,14 @@ export const getGroupBuyList = (data: {
}) => post({ url: '/group/coupons/wx_query', data });
export const getGroupBuyDetail = (
id: string) => get({ url: `wechat/coupons/group/get/${id}` });
id: string) => get<GroupBuyBean>({ url: `wechat/coupons/group/get/${id}` });
export const getGroupBuyRecordList = (groupId: string, pageNum: number, pageSize: number) => post({
export const getGroupBuyRecordList = (groupId: string, pageNum: number, pageSize: number) => post<RecordBean[]>({
url: `wechat/coupons/group/order/list?groupId=${groupId}&pageNum=${pageNum}&pageSize=${pageSize}`
});
export const preOrder = (data: any) => post({ url: 'wechat/coupons/group/pre_v2', data });
export const preOrder = (data: any) => post<GoodsBean>({ url: 'wechat/coupons/group/pre_v2', data });
export const progress = (data: any) => post<any>({ url: 'wechat/coupons/group/pay/progress', data });
export const pay = (data: any) => post<any>({ url: 'wechat/coupons/group/pay', data });

125
src/api/groupbuy/types.ts Normal file
View File

@@ -0,0 +1,125 @@
import { GoodsBean, StockBean } from '@/api/goods/types';
import { CouponBean } from '@/api/user/types';
export interface GroupBuyBean {
companyId: string;
content: string;
couponsList: CouponBean[];
createTime: string;
creatorId: string;
creatorName: string;
endDate: string;
fake: number;
goods: GoodsBean;
id: string;
name: string;
offsetPrice: number;
payPrice: number;
goodsPrice: number;
periodDay: number;
price: number;
publicNum: number;
sendNum: number;
startDate: string;
status: number;
threshold: number;
title: string;
totalNum: number;
}
export interface RecordBean {
bizId: string;
createTime: string;
goodsCode: string;
goodsNum: number;
groupId: string;
memberImage: string;
}
export interface OrderBean {
allowIntegral: boolean;
bizId: string;
classify: number;
companyId: string;
consignTime: string;
consumerId: string;
consumerName: string;
coupon: string;
couponIds: string;
couponsStatus: number;
createTime: string;
creatorId: number;
creatorName: string;
device: string;
discount: number;
discountPrice: number;
finishTime: string;
freePrice: number;
goodsNum: number;
id: string;
integral: number;
itemNum: number;
marketingPrice: number;
marketingText: string;
modifierIds: string;
needPaid: number;
ogList: any[];
orderGoods: GoodsBean[];
changeStockNum: number;
companyId: string;
consumePrice: number;
consumerId: string;
consumerName: string;
createTime: string;
creatorId: number;
creatorName: string;
discount: number;
discountOriginPrice: number;
discountPrice: number;
goodsCode: string;
goodsId: string;
goodsName: string;
goodsNum: number;
goodsPriceModify: string;
goodsTypeName: string;
id: string;
images: string;
offset: string;
orderId: string;
orderNo: string;
originPrice: number;
originStockNum: number;
priceModify: [];
produceIntegral: number;
remark: string;
salePrice: number;
stockId: string;
stockStock: StockBean[];
storeId: string;
type: number;
typeName: string;
updateTime: string;
payStatus: number;
payTypeIds: string;
printed: string;
produceIntegralNumber: number;
profit: string;
reducePrice: number;
relatedId: string;
remark: string;
replacementOrder: string;
saleNum: number;
salers: [];
status: number;
storeId: string;
storeName: string;
storeOrder: string;
totalPrice: number;
transactionPrice: number;
type: number;
typeName: string;
updateTime: string;
updated: boolean;
useGold: number;
wholePrice: number;
}

View File

@@ -1,7 +1,7 @@
/**
* 用户信息相关接口
*/
import type { LoginParams, LoginResult, RegisterParams } from './types';
import type { CouponBean, LoginParams, LoginResult, RegisterParams, TerminalBean } from './types';
import { get, post } from '@/utils/request';
import type { UserState } from '@/store/modules/user/types';
@@ -25,7 +25,8 @@ enum URL {
rechargeList = '/ext/recharge/rule_config',
recharge = '/memberIncoming/wx_save',
couponList = '/wechat/coupons/coupons/pageList',
integralList = '/ext/member/integral_query'
integralList = '/ext/member/integral_query',
terminal = 'wechat/coupons/terminal?companyId='
}
export const getUserProfile = () => get<UserState>({ url: URL.profile });
@@ -49,6 +50,8 @@ export const getRechargeList = () => get<any>({ url: URL.rechargeList });
export const recharge = (data: any) => post({ url: URL.recharge, data });
export const getCouponList = (data: any) => post<any>({ url: URL.couponList, data });
export const getCouponList = (data: any) => post<CouponBean[]>({ url: URL.couponList, data });
export const getIntegralList = (data: any) => get({ url: URL.integralList, data });
export const getTerminal = (companyId: string) => get<TerminalBean>({ url: URL.terminal + companyId });

View File

@@ -1,4 +1,4 @@
import { UserState } from '@/store/modules/user/types';
import { UserBean } from '@/store/modules/user/types';
export interface LoginParams {
// phone: string;
@@ -27,7 +27,7 @@ export interface LoginByCodeParams {
export interface LoginResult {
sessionKey: string;
user: UserState;
user: UserBean;
userInfo: any;
token: string;
}
@@ -40,3 +40,77 @@ export interface IntegralBean {
type: number;
value: number;
}
export interface CouponBean {
companyId: string;
couponsId: string;
createTime: string;
creatorId: number;
creatorName: string;
dayType: number;
endTime: string;
id: string;
images: string;
imcomingId: string;
memberId: string;
memberName: string;
name: string;
notice: number;
offsetPrice: number;
orderId: null;
periodDay: number;
reduce: number;
remark: string;
startTime: string;
status: number;
strategyType: string;
telephone: string;
threshold: string;
type: number;
userTime: string;
}
export interface TerminalBean {
applyId: string;
bindId: string;
bindName: string;
bindStatus: number;
company: string;
companyId: string;
companyName: string;
contact: string;
createTime: string;
creatorId: string;
creatorName: string;
deviceRole: string;
employee: string;
expireTime: string;
id: string;
managerPermission: string;
name: string;
permission: {
deletablePer: boolean,
profitPer: boolean,
exportTypes: any[],
checkoutPer: boolean,
showPhoneNumber: boolean,
storePer: any[]
};
checkoutPer: boolean;
deletablePer: boolean;
exportPer: boolean;
exportTypes: any[];
profitPer: boolean;
showPhoneNumber: boolean;
storePer: any[];
signDate: string;
status: number;
store: string;
storeId: string;
storeName: string;
storeOrder: number;
terminalKey: string;
terminalSn: string;
token: string;
type: number;
}

View File

@@ -2,10 +2,10 @@
<uni-popup ref='popupRef' type='bottom' :mask-click='false' @touchmove.stop.prevent=''>
<view class='content'>
<view class='c-flex-row' style='align-items: flex-start'>
<image class='goods-image' :src='bean?.images' />
<image class='goods-image' :src='goodsDetailBean?.images' />
<view class='c-flex-column' style='flex: 1'>
<text class='goods-name'>{{ bean?.name }}</text>
<text class='goods-price'>{{ bean?.price || 0 }}</text>
<text class='goods-name'>{{ goodsDetailBean?.name }}</text>
<text class='goods-price'>{{ flashPrice > 0 ? `${flashPrice}` : `${goodsDetailBean?.price}` || 0 }}</text>
</view>
<image class='close-image' :src='assetsUrl("ic_close.png")' @click.stop='close' />
</view>
@@ -50,46 +50,40 @@
<script lang='ts' setup>
import { PropType, ref } from 'vue';
import { assetsUrl } from '@/utils/assets';
import {showToast} from '@/utils';
import { showToast } from '@/utils';
import { getGoodsDetail } from '@/api/goods';
import { GoodsBean, StockBean } from '@/api/goods/types';
const props = defineProps({
bean: Object as PropType<GoodsBean>,
flashPrice: {
type: Number,
default: -1
},
exists: Object as PropType<StockBean>
});
const popupRef = ref();
const goodsDetailBean = ref<GoodsBean>();
const skuColorList = ref<string[]>([]);
const currentColorIndex = ref(0);
const currentSizeIndex = ref(0);
const goodsCount = ref(1);
let callback: Function;
const show = (fn: Function) => {
const show = async (goodsId: string, fn: Function) => {
callback = fn;
// new Promise((resolve, reject) => {
// props.bean?.stocks?.map((res: { colorName: string }) => res.colorName).forEach((colorName: string) => {
// if(!skuColorList.value.includes(colorName)) {
// skuColorList.value.push(colorName);
// }
// });
// resolve(skuColorList.value);
// }).then(res => {
// popupRef.value.open();
// });
if(props?.bean?.stocks?.length == 0) {
showToast('暂无库存')
goodsDetailBean.value = await getGoodsDetail(goodsId);
if(goodsDetailBean.value?.stocks?.length == 0) {
showToast('暂无库存');
return;
}
props.bean?.stocks?.map((res: { colorName: string }) => res.colorName).forEach((colorName: string) => {
goodsDetailBean.value?.stocks?.map((res: { colorName: string }) => res.colorName).forEach((colorName: string) => {
if(!skuColorList.value.includes(colorName)) {
skuColorList.value.push(colorName);
}
});
setTimeout(() => {
popupRef.value.open();
}, 200);
@@ -106,7 +100,7 @@ const show = (fn: Function) => {
const skuSizeList = computed(() => {
const currentColorName = skuColorList.value[currentColorIndex.value];
return props.bean?.stocks?.filter((res: { colorName: string }) => {
return goodsDetailBean.value?.stocks?.filter((res: { colorName: string }) => {
return res.colorName === currentColorName;
});
});
@@ -134,10 +128,15 @@ const countChange = (isPlus: boolean) => {
};
const confirm = () => {
callback({
...skuSizeList.value![currentSizeIndex.value],
count: goodsCount.value
});
if(callback instanceof Function) {
callback({
...goodsDetailBean.value,
checkedStock: {
...skuSizeList.value![currentSizeIndex.value],
count: goodsCount.value
}
});
}
popupRef.value.close();
};
@@ -262,7 +261,7 @@ defineExpose({
.primary-button {
width: 100%;
margin-top: 100rpx;
margin-top: 50rpx;
}
}
}

View File

@@ -16,8 +16,8 @@
超级\n秒杀
</view>
<view class='price c-flex-column' style='flex: 1'>
<text>{{ detailBean?.payPrice || 0 }}</text>
<text>{{ detailBean?.goodsPrice || 0 }}</text>
<text>{{ groupBuyBean?.payPrice || 0 }}</text>
<text>{{ groupBuyBean?.price || 0 }}</text>
</view>
<view class='countdown-time c-flex-column'>
<view class='c-flex-row'>{{ countdownTime?.days || 0 }}
@@ -33,12 +33,12 @@
<view class='goods-info-view c-flex-column'>
<view class='c-flex-row'>
<text class='goods-price accent-text-color'>{{ detailBean?.payPrice || 0 }}</text>
<text>销量{{ detailBean?.totalNum || 0 }}</text>
<text class='goods-price accent-text-color'>{{ groupBuyBean?.payPrice || 0 }}</text>
<text>销量{{ groupBuyBean?.totalNum || 0 }}</text>
</view>
<view class='c-flex-row'>
<text style='flex: 1'>{{ detailBean?.name }}</text>
<text style='flex: 1'>{{ groupBuyBean?.name }}</text>
<view class='share-button c-flex-column'>
<image :src='assetsUrl("ic_share.png")'></image>
<button class='btn' plain open-type='share'>分享</button>
@@ -51,7 +51,7 @@
<text>选择</text>
<text>规格 颜色/尺码</text>
<image :src='assetsUrl("ic_arrow_right_gray.png")' />
<text>1种颜色可选</text>
<text>{{ getStockColorCount }}种颜色可选</text>
</view>
</view>
@@ -70,27 +70,27 @@
</view>
<!-- 商品详情图片 -->
<view class='card-view image-container' v-if='detailBean?.content'>
<view class='card-view image-container' v-if='groupBuyBean?.content'>
<text class='card-view-title'>商品详情</text>
<image
v-for='(item,index) in JSON.parse(detailBean?.content).filter((a: any) => a.type === 2).map((b: any) => b.images)'
v-for='(item,index) in JSON.parse(groupBuyBean?.content).filter((a: any) => a.type === 2).map((b: any) => b.images)'
:src='item' mode='aspectFill' :key='index' />
</view>
<!-- 商品详情 -->
<view class='card-view goods-container'>
<view class='c-flex-row'>
<image :src='detailBean?.goods?.images' />
<image :src='groupBuyBean?.goods?.images' />
<view class='c-flex-column'>
<text class='goods-name'>{{ detailBean?.goods?.name }}</text>
<text class='goods-name'>{{ groupBuyBean?.goods?.name }}</text>
<text style='color: #a6a6a6'>精选</text>
<view class='tag-view c-flex-row'>
<text>团长推荐</text>
<text>今日必买</text>
</view>
<text style='color: #a6a6a6'>不参与会员折扣</text>
<text style='color: #F32B2B'>¥{{ detailBean?.goods?.payPrice }}</text>
<text class='bought_count'>已团{{ detailBean?.sendNum }}</text>
<text style='color: #F32B2B'>¥{{ groupBuyBean?.goods?.payPrice }}</text>
<text class='bought_count'>已团{{ groupBuyBean?.sendNum }}</text>
</view>
</view>
</view>
@@ -99,7 +99,7 @@
<view class='card-view coupon-container'>
<text class='card-view-title'>赠送优惠券</text>
<view class='c-flex-column'>
<coupon-item v-for='(item,index) in detailBean?.couponsList' :key='index' :item='item' />
<coupon-item v-for='(item,index) in groupBuyBean?.couponsList' :key='index' :item='item' />
</view>
</view>
@@ -125,13 +125,13 @@
<view class='place-order-button' @click.stop='placeOrder'>跟团购买</view>
</view>
</view>
<sku-dialog ref='skuDialogRef' :bean='detailBean?.goods' @confirm='confirmGoodsSku' />
<sku-dialog ref='skuDialogRef' :flash-price='groupBuyBean?.payPrice || 0' />
</template>
<script lang='ts' setup>
import { assetsUrl } from '@/utils/assets';
import dayjs from 'dayjs';
import { goPath } from '@/utils';
import { formatTimeWithZeroPad, goPath } from '@/utils';
import SkuDialog from '@/components/sku-dialog.vue';
import CouponItem from './components/coupon-item.vue';
@@ -139,33 +139,33 @@ import { getGroupBuyDetail, getGroupBuyRecordList, preOrder } from '@/api/groupb
import { getGoodsList } from '@/api/goods';
import { GoodsBean } from '@/api/goods/types';
import { useUserStore } from '@/store';
import { GroupBuyBean, RecordBean } from '@/api/groupbuy/types';
const userStore = useUserStore();
const { userInfo } = storeToRefs(userStore);
const skuDialogRef = ref();
const detailBean = ref();
const groupBuyBean = ref<GroupBuyBean>();
const recommendList = ref<GoodsBean[]>();
const skuBean = ref();
const bannerList = ref([]);
const swiperIndex = ref(0);
let interval: number;
const countdownTime = ref<{
days: number,
hours: number,
minutes: number,
seconds: number
days: string,
hours: string,
minutes: string,
seconds: string
}>();
const recordList = ref([]);
const recordList = ref<RecordBean[]>([]);
const currentPageNum = ref(1);
onLoad(async (e: any) => {
detailBean.value = await getGroupBuyDetail(e.id);
detailBean.value.goods.price = detailBean.value.price;
bannerList.value = JSON.parse(detailBean.value.content).filter((item: any) => item.type === 2).map((item: any) => item.images);
groupBuyBean.value = await getGroupBuyDetail(e.id);
groupBuyBean.value.goods.price = groupBuyBean.value.price;
bannerList.value = JSON.parse(groupBuyBean.value.content).filter((item: any) => item.type === 2).map((item: any) => item.images);
countdown();
await fetchRecommendList();
await fetchBuyRecordList();
@@ -177,6 +177,12 @@ onUnload(() => {
}
});
const getStockColorCount = computed(() => {
const list = Array.from(new Set(groupBuyBean.value?.goods?.stocks?.map(item => item.colorName)))
.map(colorName => groupBuyBean.value?.goods?.stocks?.find(item => item.colorName === colorName)!);
return list.length;
});
const fetchRecommendList = async () => {
const { rows } = await getGoodsList({
page: {
@@ -195,7 +201,7 @@ const fetchBuyRecordList = async (refresh: boolean = true) => {
if(!refresh) {
currentPageNum.value += 1;
}
const { list } = await getGroupBuyRecordList(detailBean.value.id, currentPageNum.value, 20);
const { list } = await getGroupBuyRecordList(groupBuyBean?.value?.id || '', currentPageNum.value, 20);
recordList.value = recordList.value.concat(list);
};
@@ -209,16 +215,16 @@ const swiperChange = (e: any) => {
const countdown = () => {
interval = setInterval(() => {
if(detailBean.value?.endDate) {
if(groupBuyBean.value?.endDate) {
let now = new Date();
let end = dayjs(detailBean.value?.endDate).toDate().getTime();
let end = dayjs(groupBuyBean.value?.endDate).toDate().getTime();
let remaining = Math.floor((end - now.getTime()) / 1000);
if(remaining > 0) {
countdownTime.value = {
days: Math.floor(remaining / 60 / 60 / 24),
hours: Math.floor(remaining / 60 / 60 % 24),
minutes: Math.floor(remaining / 60 % 60),
seconds: Math.floor(remaining % 60)
days: formatTimeWithZeroPad(Math.floor(remaining / 60 / 60 / 24)),
hours: formatTimeWithZeroPad(Math.floor(remaining / 60 / 60 % 24)),
minutes: formatTimeWithZeroPad(Math.floor(remaining / 60 % 60)),
seconds: formatTimeWithZeroPad(Math.floor(remaining % 60))
};
} else {
clearInterval(interval);
@@ -228,31 +234,27 @@ const countdown = () => {
};
const showSkuDialog = (fn: Function) => {
skuDialogRef.value.show(fn);
};
const confirmGoodsSku = (e: any) => {
skuBean.value = e;
skuDialogRef.value.show(groupBuyBean?.value?.goods?.goodsId, fn);
};
const placeOrder = async () => {
async function create() {
async function create(bean: GoodsBean) {
const params = {
'colorId': '1725029269178814466',
'sizeId': '0',
'goodsId': detailBean.value.goods.goodsId,
'groupId': detailBean.value.id,
'colorId': bean.checkedStock.colorId,
'sizeId': bean.checkedStock.sizeId,
'goodsId': groupBuyBean?.value?.goods.goodsId,
'groupId': groupBuyBean?.value?.id,
'memberId': userInfo.value.id,
'shareId': '123456'
};
const result = await preOrder(params);
goPath('/pages/common/groupbuy/order');
goPath(`/pages/common/groupbuy/order?orderBean=${encodeURIComponent(JSON.stringify(result))}`);
}
showSkuDialog(() => {
create();
showSkuDialog((e: GoodsBean) => {
create(e);
});
};
</script>
@@ -360,7 +362,7 @@ const placeOrder = async () => {
margin-left: 12rpx;
margin-right: 12rpx;
background: #FFFFFF;
border-radius: 7rpx 7rpx 7rpx 7rpx;
border-radius: 7rpx;
color: #F32B2B;
}
@@ -467,7 +469,7 @@ const placeOrder = async () => {
font-size: 24rpx;
color: #999999;
position: absolute;
top: 40rpx;
top: 50rpx;
left: 100rpx;
}
}
@@ -527,7 +529,7 @@ const placeOrder = async () => {
.bottom-view {
background: #FFFFFF;
padding: 20rpx 30rpx 78rpx 33rpx;
padding: 20rpx 30rpx 78rpx 30rpx;
position: fixed;
left: 0;
right: 0;

View File

@@ -2,21 +2,27 @@
<view class='content'>
<view class='card-view'>
<image class='goods-image' :src='assetsUrl("test_bg.png")' />
<view class='c-flex-column' style='flex: 1'>
<text class='goods-name'>商品名称</text>
<text class='goods-sku'>颜色尺码</text>
</view>
<template v-for='(item,index) in orderBean?.orderGoods' :key='index'>
<image class='goods-image' :src='item?.images' />
<view class='c-flex-column' style='flex: 1'>
<text class='goods-name'>{{ item?.goodsName }}</text>
<text class='goods-sku'>{{ item?.stockStock?.colorName }} {{ item?.stockStock?.sizeName }}</text>
</view>
<view class='c-flex-column'>
<text class='goods-num'>x1</text>
<text class='goods-price'>¥100</text>
</view>
<view class='c-flex-column'>
<text class='goods-num'>x{{ item?.goodsNum }}</text>
<view class='c-flex-row'>
<text class='goods-price' style='margin-right: 10rpx;text-decoration: line-through'>¥{{ item?.originPrice }}
</text>
<text class='goods-price'>¥{{ item?.consumePrice }}</text>
</view>
</view>
</template>
</view>
<view class='card-view'>
<text style='flex: 1'>实付</text>
<text style='color: #F32B2B'>¥100</text>
<text style='color: #F32B2B'>¥{{ orderBean?.totalPrice || 0 }}</text>
</view>
<view class='bottom-view c-flex-row'>
@@ -27,12 +33,57 @@
</template>
<script lang='ts' setup>
import { pay, progress } from '@/api/groupbuy';
import { OrderBean } from '@/api/groupbuy/types';
import { parseParameter, sortASCII } from '@/utils';
import { hexMD5 } from '@/utils/common/md5';
import { useUserStore } from '@/store';
import { assetsUrl } from '@/utils/assets';
const userState = useUserStore();
const { terminalInfo } = storeToRefs(userState);
const orderBean = ref<OrderBean>();
onLoad((e: any) => {
orderBean.value = JSON.parse(decodeURIComponent(e?.orderBean));
});
const payment = () => {
let signParams = {
return_url: 'return_url',
total_amount: orderBean.value?.totalPrice,
client_sn: orderBean.value?.id,
terminal_sn: terminalInfo.value.terminalSn,
subject: 'subject',
subject_img: 'subject_img',
merchant_name: 'merchant_name',
notify_url: 'https://www.baidu.com'
};
// signParams = util.sortASCII(signParams, true);
sortASCII(signParams, true);
//参数拼接
// const signStr = util.pars(signParams) + '&key=' + terminalInfo.value.terminalKey;
const signStr = parseParameter(signParams) + '&key=' + terminalInfo.value.terminalKey;
console.log('签名字符串', signStr);
// const sign = utilMd5.hexMD5(signStr).toUpperCase();
const sign = hexMD5(signStr);
console.log('签名结果', sign);
const params = {
'id': orderBean.value?.id,
'orderSn': signParams.client_sn,
'terminal_key': terminalInfo.value.terminalKey,
'terminal_sn': terminalInfo.value.terminalSn
};
progress(params);
pay({
'orderId': orderBean.value?.id,
'result': JSON.stringify('{payResult:xxx}')
});
};
</script>
<style lang='scss' scoped>

View File

@@ -106,7 +106,7 @@ onShow(async () => {
getCompanyList(userInfo.value.maOpenId).then(res => {
const companyList = res.map((res: { company: any }) => res.company);
const userList = res.map((res: { user: any }) => res.user);
if(getCompanyId() == undefined) {
// if(!getCompanyId()) {
uni.showActionSheet({
itemList: companyList.map((res: { companyName: string }) => res.companyName),
success: (res) => {
@@ -114,7 +114,7 @@ onShow(async () => {
store.setUserInfo(userList[res.tapIndex]);
}
});
}
// }
});
}
});

View File

@@ -31,21 +31,24 @@
<view class='shopping-cart' @click.stop='goPath("/pages/mall/subs/shoppingcart/index")'>
<image :src='assetsUrl("ic_shopping_cart.png")' />
<text v-if='shoppingCartList.length>0'>{{ shoppingCartList.length }}</text>
<text v-if='shoppingCartList.length>0'>{{ shoppingCartList?.length }}</text>
</view>
</view>
<sku-dialog ref='skuDialogRef' />
</template>
<script setup lang='ts'>
import SkuDialog from '@/components/sku-dialog.vue';
import { assetsUrl } from '@/utils/assets';
import { goPath } from '@/utils';
import { getCategoryList, getGoodsList } from '@/api/goods';
import { CategoryBean, GoodsBean } from '@/api/goods/types';
import useShoppingCartStore from '@/store/modules/shoppingcart';
const shoppingCart = useShoppingCartStore();
const { shoppingCartList } = storeToRefs(shoppingCart);
const shoppingCartStore = useShoppingCartStore();
const { shoppingCartList } = storeToRefs(shoppingCartStore);
const skuDialogRef = ref();
const categoryList = ref<CategoryBean[]>([]);
const categorySelectedIndex = ref<number>(0);
const goodsList = ref<GoodsBean[]>([]);
@@ -89,10 +92,8 @@ const getRandomFloatInRange = (a: number, b: number) => {
};
const addShoppingCart = (goodsBean: GoodsBean) => {
shoppingCart.save({
...goodsBean,
name: goodsBean.goodsName,
count: 1
skuDialogRef.value.show(goodsBean.goodsId, (e: GoodsBean) => {
shoppingCartStore.save(e);
});
};
</script>

View File

@@ -32,7 +32,7 @@
<text>选择</text>
<text>规格 颜色/尺码</text>
<image :src='assetsUrl("ic_arrow_right_gray.png")' />
<text>1种颜色可选</text>
<text>{{ getStockColorCount }}种颜色可选</text>
</view>
<view class='divider' style='width:auto;height: 0.5rpx;margin-left: 100rpx;display: none' />
<view class='c-flex-row' style='display: none' @click.stop='showSkuDialog'>
@@ -127,6 +127,12 @@ onLoad(async (e: any) => {
recommendList.value = rows;
});
const getStockColorCount = computed(() => {
const list = Array.from(new Set(goodsBean.value?.stocks?.map(item => item.colorName)))
.map(colorName => goodsBean.value?.stocks?.find(item => item.colorName === colorName)!);
return list.length;
});
const swiperChange = (e: any) => {
swiperIndex.value = e.detail.current;
};
@@ -136,21 +142,16 @@ const goBack = () => {
};
const showSkuDialog = (fn: Function) => {
skuDialogRef.value.show(fn);
skuDialogRef.value.show(goodsBean.value?.id, fn);
};
const addShoppingCart = () => {
showSkuDialog((e: StockBean) => {
const index = shoppingCartStore.getSameGoodsIndex(goodsBean.value?.id || '', e.colorId, e.sizeId);
showSkuDialog((e: GoodsBean) => {
const index = shoppingCartStore.getSameGoodsIndex(goodsBean.value?.id || '', e.checkedStock?.colorId, e.checkedStock?.sizeId);
if(index >= 0) {
shoppingCartStore.updateCount(index, e.count);
shoppingCartStore.updateCount(index, e.checkedStock?.count);
} else {
shoppingCartStore.save(
{
...goodsBean.value,
checkedStock: e
}
);
shoppingCartStore.save(e);
}
showToast('加入购物车成功');
});
@@ -260,13 +261,13 @@ const placeOrder = () => {
.goods-sku-view {
background: #FFFFFF;
padding: 0 30rpx;
padding: 20rpx 30rpx;
margin-top: 20rpx;
view:nth-of-type(n+1) {
position: relative;
align-items: center;
height: 105rpx;
height: 85rpx;
text:nth-of-type(1) {
width: 100rpx;
@@ -292,14 +293,14 @@ const placeOrder = () => {
font-size: 24rpx;
color: #999999;
position: absolute;
top: 80rpx;
top: 50rpx;
left: 100rpx;
}
}
view:nth-of-type(1) {
align-items: flex-start;
padding-top: 20rpx;
//padding-top: 20rpx;
}
}

View File

@@ -10,7 +10,7 @@
<scroll-view>
<view class='c-flex-column' v-for='(item, index) in shoppingCartList' :key='index'>
<view class='c-flex-row'>
<image v-show='isEditMode' class='checkbox'
<image class='checkbox'
:src='assetsUrl(item.checked?"ic_checkbox_active_red.png":"ic_checkbox_normal.png")'
@click.stop='item.checked=!item.checked' />
<image class='goods-image' :src='item.images' />
@@ -18,8 +18,8 @@
<text>{{ item.name }}</text>
<view class='c-flex-row'>
<view class='sku-view' @click.stop='showSkuDialog(index)'>
<text>{{ item?.checkedStock.colorName }}{{ item?.checkedStock.sizeName }}
x{{ item?.checkedStock.count }}
<text>{{ item?.checkedStock?.colorName }}{{ item?.checkedStock?.sizeName }}
x{{ item?.checkedStock?.count }}
</text>
<image :src='assetsUrl("ic_arrow_down_gray.png")' />
</view>
@@ -29,7 +29,7 @@
<view class='count-image' @click.stop='countChange(index,false)'>
<image :src='assetsUrl("ic_reduce.png")' />
</view>
<text>{{ item?.checkedStock.count || 0 }}</text>
<text>{{ item?.checkedStock?.count || 0 }}</text>
<view class='count-image' @click.stop='countChange(index,true)'>
<image :src='assetsUrl("ic_plus.png")' />
</view>
@@ -85,7 +85,7 @@ watch(checkedAll, (newValue) => {
});
const totalPayPrice = computed(() => {
return shoppingCartList.value.reduce((a, b) => a + b.price * b.checkedStock.count, 0);
return shoppingCartList.value.filter(res => res.checked).reduce((a, b) => a + b.price * b.checkedStock.count, 0);
});
const deleteShoppingCart = () => {
@@ -93,21 +93,21 @@ const deleteShoppingCart = () => {
};
const showSkuDialog = (index: number) => {
new Promise((resolve, reject) => {
new Promise((resolve, _) => {
temporaryGoodsBean.value = shoppingCartList.value[index];
temporaryStockBean.value = shoppingCartList.value[index].checkedStock;
resolve(temporaryGoodsBean.value);
}).then(res => {
skuDialogRef.value.show((e: StockBean) => {
}).then((res: any) => {
skuDialogRef.value.show(res.id, (e: GoodsBean) => {
//重新选择sku后判断当前购物中是否存在相同sku的商品
const existsIndex = shoppingCartStore.getSameGoodsIndex(temporaryGoodsBean.value?.id || '', e.colorId, e.sizeId);
const existsIndex = shoppingCartStore.getSameGoodsIndex(res?.id || '', e.checkedStock.colorId, e.checkedStock.sizeId);
//不存在则更新当前商品sku
if(existsIndex < 0) {
shoppingCartStore.updateStock(index, e);
shoppingCartStore.updateStock(index, e.checkedStock);
}
//如果已存在,则更新已存在商品数量,并删除当前商品
else {
shoppingCartStore.updateCount(existsIndex, e.count);
shoppingCartStore.updateCount(existsIndex, e.checkedStock.count - (temporaryGoodsBean?.value?.checkedStock?.count || 0));
if(existsIndex != index) {
shoppingCartStore.delete(index);
}

View File

@@ -11,11 +11,12 @@ import CouponItem from './components/coupon-item.vue';
import { getCouponList } from '@/api/user';
import { useUserStore } from '@/store';
import { getCompanyId } from '@/utils';
import { GroupBuyBean } from '@/api/groupbuy/types';
const store = useUserStore();
const { userInfo } = storeToRefs(store);
const coupons = ref([]);
const coupons = ref<GroupBuyBean[]>([]);
onLoad(async () => {
fetchData(0);

View File

@@ -1,8 +1,8 @@
import { defineStore } from 'pinia';
import { StockBean } from '@/api/goods/types';
import { GoodsBean, StockBean } from '@/api/goods/types';
const useShoppingCartStore = defineStore('shoppingCart', {
state: (): { shoppingCartList: any[] } => ({
state: (): { shoppingCartList: GoodsBean[] } => ({
shoppingCartList: []
}),
@@ -36,8 +36,8 @@ const useShoppingCartStore = defineStore('shoppingCart', {
},
actions: {
save(partial: Partial<any>) {
this.shoppingCartList.push(partial);
save(partial: Partial<GoodsBean>) {
this.shoppingCartList.push(partial as GoodsBean);
},
updateCount(index: number, count: number) {

View File

@@ -1,12 +1,13 @@
import { defineStore } from 'pinia';
import type { providerType, UserState } from './types';
import { getUserProfile, login, logout as userLogout, register } from '@/api/user/index';
import type { providerType, UserBean } from './types';
import { getTerminal, getUserProfile, login, logout as userLogout, register } from '@/api/user/index';
import { clearToken, setCompanyId, setToken } from '@/utils/auth';
import type { LoginResult, RegisterParams } from '@/api/user/types';
import type { LoginResult, RegisterParams, TerminalBean } from '@/api/user/types';
const useUserStore = defineStore('user', {
state: () => ({
userInfo: {} as UserState
userInfo: {} as UserBean,
terminalInfo: {} as TerminalBean
}),
persist: {
@@ -32,8 +33,8 @@ const useUserStore = defineStore('user', {
actions: {
// 设置用户的信息
setUserInfo(partial: Partial<UserState>) {
this.userInfo = partial as UserState;
setUserInfo(partial: Partial<UserBean>) {
this.userInfo = partial as UserBean;
},
// 重置用户信息
resetInfo() {
@@ -43,6 +44,7 @@ const useUserStore = defineStore('user', {
async getProfile() {
const result = await getUserProfile();
this.setUserInfo(result);
await this.fetchTerminal();
},
userRegister(registerForm: RegisterParams) {
@@ -119,6 +121,10 @@ const useUserStore = defineStore('user', {
}
});
});
},
async fetchTerminal() {
this.terminalInfo = await getTerminal(this.userInfo.companyId);
}
}
});

View File

@@ -1,6 +1,6 @@
export type RoleType = '' | '*' | 'user';
export interface UserState {
export interface UserBean {
token?: string;
address: string;
balance: number;

View File

@@ -41,3 +41,41 @@ export function goPath(path: string) {
}).then(r => {
});
}
export function formatTimeWithZeroPad(num: number): string {
return num < 10 ? '0' + num : num + '';
}
export function sortASCII(obj: any, isSort = true) {
let arr: any[] = [];
Object.keys(obj).forEach(item => arr.push(item));
let sortArr = isSort ? arr.sort() : arr.sort().reverse();
let sortObj: any = {};
for (let i in sortArr) {
sortObj[sortArr[i]] = obj[sortArr[i]];
}
return sortObj;
}
Array.prototype.contains = function(obj: any) {
let i = this.length;
while (i--) {
if(this[i] === obj) {
return true;
}
}
return false;
};
export function parseParameter(obj: any) {
if(obj === null || obj === undefined) return '';
const arr = [];
const keys: string[] = Object.keys(obj);
const entries: any[] = Object.entries(obj);
for (const [key, value] of entries) {
if(keys.contains(key) && !key.startsWith('function')) {
arr.push(key + '=' + value);
}
}
return arr.join('&');
}

401
src/utils/common/md5.js Normal file
View File

@@ -0,0 +1,401 @@
/*
* JavaScript MD5
* https://github.com/blueimp/JavaScript-MD5
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*
* Based on
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/* global define */
/* eslint-disable strict */
'use strict'
/**
* Add integers, wrapping at 2^32.
* This uses 16-bit operations internally to work around bugs in interpreters.
*
* @param {number} x First integer
* @param {number} y Second integer
* @returns {number} Sum
*/
function safeAdd(x, y) {
var lsw = (x & 0xffff) + (y & 0xffff)
var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
return (msw << 16) | (lsw & 0xffff)
}
/**
* Bitwise rotate a 32-bit number to the left.
*
* @param {number} num 32-bit number
* @param {number} cnt Rotation count
* @returns {number} Rotated number
*/
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt))
}
/**
* Basic operation the algorithm uses.
*
* @param {number} q q
* @param {number} a a
* @param {number} b b
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | (~b & d), a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t)
}
/**
* Basic operation the algorithm uses.
*
* @param {number} a a
* @param {number} b b
* @param {number} c c
* @param {number} d d
* @param {number} x x
* @param {number} s s
* @param {number} t t
* @returns {number} Result
*/
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | ~d), a, b, x, s, t)
}
/**
* Calculate the MD5 of an array of little-endian words, and a bit length.
*
* @param {Array} x Array of little-endian words
* @param {number} len Bit length
* @returns {Array<number>} MD5 Array
*/
function binlMD5(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << len % 32
x[(((len + 64) >>> 9) << 4) + 14] = len
var i
var olda
var oldb
var oldc
var oldd
var a = 1732584193
var b = -271733879
var c = -1732584194
var d = 271733878
for (i = 0; i < x.length; i += 16) {
olda = a
oldb = b
oldc = c
oldd = d
a = md5ff(a, b, c, d, x[i], 7, -680876936)
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
b = md5gg(b, c, d, a, x[i], 20, -373897302)
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
d = md5hh(d, a, b, c, x[i], 11, -358537222)
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
a = md5ii(a, b, c, d, x[i], 6, -198630844)
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
a = safeAdd(a, olda)
b = safeAdd(b, oldb)
c = safeAdd(c, oldc)
d = safeAdd(d, oldd)
}
return [a, b, c, d]
}
/**
* Convert an array of little-endian words to a string
*
* @param {Array<number>} input MD5 Array
* @returns {string} MD5 string
*/
function binl2rstr(input) {
var i
var output = ''
var length32 = input.length * 32
for (i = 0; i < length32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff)
}
return output
}
/**
* Convert a raw string to an array of little-endian words
* Characters >255 have their high-byte silently ignored.
*
* @param {string} input Raw input string
* @returns {Array<number>} Array of little-endian words
*/
function rstr2binl(input) {
var i
var output = []
output[(input.length >> 2) - 1] = undefined
for (i = 0; i < output.length; i += 1) {
output[i] = 0
}
var length8 = input.length * 8
for (i = 0; i < length8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32
}
return output
}
/**
* Calculate the MD5 of a raw string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rstrMD5(s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
}
/**
* Calculates the HMAC-MD5 of a key and some data (raw strings)
*
* @param {string} key HMAC key
* @param {string} data Raw input string
* @returns {string} Raw MD5 string
*/
function rstrHMACMD5(key, data) {
var i
var bkey = rstr2binl(key)
var ipad = []
var opad = []
var hash
ipad[15] = opad[15] = undefined
if (bkey.length > 16) {
bkey = binlMD5(bkey, key.length * 8)
}
for (i = 0; i < 16; i += 1) {
ipad[i] = bkey[i] ^ 0x36363636
opad[i] = bkey[i] ^ 0x5c5c5c5c
}
hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
}
/**
* Convert a raw string to a hex string
*
* @param {string} input Raw input string
* @returns {string} Hex encoded string
*/
function rstr2hex(input) {
var hexTab = '0123456789abcdef'
var output = ''
var x
var i
for (i = 0; i < input.length; i += 1) {
x = input.charCodeAt(i)
output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
}
return output
}
/**
* Encode a string as UTF-8
*
* @param {string} input Input string
* @returns {string} UTF8 string
*/
function str2rstrUTF8(input) {
return unescape(encodeURIComponent(input))
}
/**
* Encodes input string as raw MD5 string
*
* @param {string} s Input string
* @returns {string} Raw MD5 string
*/
function rawMD5(s) {
return rstrMD5(str2rstrUTF8(s))
}
/**
* Encodes input string as Hex encoded string
*
* @param {string} s Input string
* @returns {string} Hex encoded string
*/
export function hexMD5(s) {
return rstr2hex(rawMD5(s))
}
/**
* Calculates the raw HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function rawHMACMD5(k, d) {
return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
}
/**
* Calculates the Hex encoded HMAC-MD5 for the given key and data
*
* @param {string} k HMAC key
* @param {string} d Input string
* @returns {string} Raw MD5 string
*/
function hexHMACMD5(k, d) {
return rstr2hex(rawHMACMD5(k, d))
}
/**
* Calculates MD5 value for a given string.
* If a key is provided, calculates the HMAC-MD5 value.
* Returns a Hex encoded string unless the raw argument is given.
*
* @param {string} string Input string
* @param {string} [key] HMAC key
* @param {boolean} [raw] Raw output switch
* @returns {string} MD5 output
*/
function md5(string, key, raw) {
if (!key) {
if (!raw) {
return hexMD5(string)
}
return rawMD5(string)
}
if (!raw) {
return hexHMACMD5(key, string)
}
return rawHMACMD5(key, string)
}
module.exports = {
hexMD5: hexMD5,
}