购物车逻辑完善
个人信息存储优化 团购支付
This commit is contained in:
@@ -24,3 +24,4 @@ export const getOrderList = (data: {
|
||||
payStatus: number
|
||||
}
|
||||
}) => post({ url: URL.orderList, data });
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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
125
src/api/groupbuy/types.ts
Normal 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;
|
||||
}
|
@@ -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 });
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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]);
|
||||
}
|
||||
});
|
||||
}
|
||||
// }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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) {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export type RoleType = '' | '*' | 'user';
|
||||
|
||||
export interface UserState {
|
||||
export interface UserBean {
|
||||
token?: string;
|
||||
address: string;
|
||||
balance: number;
|
||||
|
@@ -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
401
src/utils/common/md5.js
Normal 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,
|
||||
}
|
Reference in New Issue
Block a user