2025-10-21
商品详情加入购物车与创建订单 支付页面70%
This commit is contained in:
@@ -7,7 +7,11 @@ const baseUrl = "http://192.168.0.26:3000"
|
|||||||
export const mallAPI = {
|
export const mallAPI = {
|
||||||
getMallList: (params) => http.get(baseUrl + '/api/products', params),
|
getMallList: (params) => http.get(baseUrl + '/api/products', params),
|
||||||
getCategory: () => http.get(baseUrl + '/api/category'),
|
getCategory: () => http.get(baseUrl + '/api/category'),
|
||||||
getMallDetail: (id) => http.get(baseUrl + '/api/products/' + id)
|
getMallDetail: (id) => http.get(baseUrl + '/api/products/' + id),
|
||||||
|
getRecommended: (id) => http.get(baseUrl + `/api/products/${id}/recommended`), // 推荐商品
|
||||||
|
addCart: (data) => http.post(baseUrl +"/api/cart/add", data), // 添加购物车
|
||||||
|
createOrder: (data) => http.post(baseUrl + `/api/orders/create-from-cart`,data), // 创建订单
|
||||||
|
getOrder: (orderId) => http.get(baseUrl + `/api/orders/pending-payment/${orderId}`), // 获取订单
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@@ -53,6 +53,38 @@
|
|||||||
"__platform__" : [ "android" ]
|
"__platform__" : [ "android" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"icons" : {
|
||||||
|
"android" : {
|
||||||
|
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||||
|
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||||
|
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||||
|
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||||
|
},
|
||||||
|
"ios" : {
|
||||||
|
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||||
|
"ipad" : {
|
||||||
|
"app" : "unpackage/res/icons/76x76.png",
|
||||||
|
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||||
|
"notification" : "unpackage/res/icons/20x20.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||||
|
"settings" : "unpackage/res/icons/29x29.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||||
|
},
|
||||||
|
"iphone" : {
|
||||||
|
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||||
|
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||||
|
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||||
|
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||||
|
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||||
|
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||||
|
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||||
|
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
11
pages.json
11
pages.json
@@ -136,13 +136,20 @@
|
|||||||
"navigationBarTitleText": "商品详情",
|
"navigationBarTitleText": "商品详情",
|
||||||
"navigationStyle": "custom"
|
"navigationStyle": "custom"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path" : "pages/home/pay",
|
||||||
|
"style" :
|
||||||
|
{
|
||||||
|
"navigationBarTitleText" : "确认订单",
|
||||||
|
"backgroundColor": "#E4ECFF"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"globalStyle": {
|
"globalStyle": {
|
||||||
"navigationBarTextStyle": "black",
|
"navigationBarTextStyle": "black",
|
||||||
"navigationBarTitleText": "uni-app",
|
"navigationBarTitleText": "uni-app",
|
||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
"backgroundColor": "#f5f8ff"
|
||||||
"backgroundColor": "#F8F8F8"
|
|
||||||
},
|
},
|
||||||
"uniIdRouter": {},
|
"uniIdRouter": {},
|
||||||
"tabBar": {
|
"tabBar": {
|
||||||
|
|||||||
@@ -203,7 +203,6 @@
|
|||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadMallData = () => {
|
const loadMallData = () => {
|
||||||
console.log(111);
|
|
||||||
if (loadStatus.value == 'nomore') return
|
if (loadStatus.value == 'nomore') return
|
||||||
mallAPI.getMallList(params.value).then((res) => {
|
mallAPI.getMallList(params.value).then((res) => {
|
||||||
mallList.value = mallList.value.concat(res.data.products)
|
mallList.value = mallList.value.concat(res.data.products)
|
||||||
|
|||||||
@@ -7,15 +7,101 @@
|
|||||||
</template>
|
</template>
|
||||||
</u-navbar>
|
</u-navbar>
|
||||||
<scroll-view scroll-y="true" :style="'height:'+scrollHeight+'px'">
|
<scroll-view scroll-y="true" :style="'height:'+scrollHeight+'px'">
|
||||||
111
|
<view class="img-banner">
|
||||||
|
<u-swiper height="400" img-mode="aspectFit" mode="none" bg-color="transparent" :list="dataInfo.banner"
|
||||||
|
@change="handleChangeBanner" :current="currentBanner"></u-swiper>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-x="true" class="img-sub-banner">
|
||||||
|
<image v-for="(item,index) in dataInfo.banner" :src="item" class="img-item"
|
||||||
|
:class="{active: currentBanner==index}" mode="aspectFit" @click="handleChangeBanner(index)">
|
||||||
|
</image>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="mall-info">
|
||||||
|
<view class="name">
|
||||||
|
{{dataInfo.name}}
|
||||||
|
</view>
|
||||||
|
<view class="description">
|
||||||
|
{{dataInfo.description}}
|
||||||
|
</view>
|
||||||
|
<view class="price">
|
||||||
|
<view>
|
||||||
|
<view class="mall-price">
|
||||||
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
||||||
|
{{dataInfo.rongdou_price}}
|
||||||
|
</view>
|
||||||
|
<view class="mall-price">
|
||||||
|
<u-icon name="integral"></u-icon>
|
||||||
|
{{dataInfo.points_price}}
|
||||||
|
</view>
|
||||||
|
<view v-if="dataInfo.discount" class="discount-info">
|
||||||
|
<u-tag type="error" text="1折优惠" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<view>已售{{dataInfo.sales}}</view>
|
||||||
|
<view style="color: red;">剩余{{dataInfo.stock}}</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="rating">
|
||||||
|
评分:<u-rate :disabled="true" :count="5" v-model="dataInfo.rating" active-color="#f7ba2a"></u-rate>
|
||||||
|
</view>
|
||||||
|
<view class="detail">
|
||||||
|
<h3>具体描述</h3>
|
||||||
|
<view v-html="dataInfo.details"></view>
|
||||||
|
</view>
|
||||||
|
<view class="recommend">
|
||||||
|
<h3>推荐商品</h3>
|
||||||
|
<view class="mall-list">
|
||||||
|
<u-waterfall v-model="recommendedList" ref="mallListRef">
|
||||||
|
<template v-slot:left="{leftList}">
|
||||||
|
<view class="mall-item u-m-r-10" v-for="(item, index) in leftList" :key="index"
|
||||||
|
@click="handleCheck(item)">
|
||||||
|
<u-lazy-load threshold="-450" border-radius="10" :image="getImageUrl(item.image)"
|
||||||
|
:index="index"></u-lazy-load>
|
||||||
|
<view class="mall-title u-m-l-5 u-m-r-5">
|
||||||
|
{{item.name}}
|
||||||
|
</view>
|
||||||
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
||||||
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
||||||
|
{{item.price}}
|
||||||
|
</view>
|
||||||
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
||||||
|
<u-icon name="integral"></u-icon>
|
||||||
|
{{item.points}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
<template v-slot:right="{rightList}">
|
||||||
|
<view class="mall-item u-m-l-10" v-for="(item, index) in rightList" :key="index"
|
||||||
|
@click="handleCheck(item)">
|
||||||
|
<u-lazy-load threshold="-450" border-radius="10" :image="getImageUrl(item.image)"
|
||||||
|
:index="index"></u-lazy-load>
|
||||||
|
<view class="mall-title u-m-l-5 u-m-r-5">
|
||||||
|
{{item.name}}
|
||||||
|
</view>
|
||||||
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
||||||
|
<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
||||||
|
{{item.price}}
|
||||||
|
</view>
|
||||||
|
<view class="mall-price u-m-l-5 u-m-r-5">
|
||||||
|
<u-icon name="integral"></u-icon>
|
||||||
|
{{item.points}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
</u-waterfall>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
<view class="bottom-view" id="bottomViewId">
|
<view class="bottom-view" id="bottomViewId">
|
||||||
<view class="icon-btn">
|
<view class="icon-btn">
|
||||||
<view class="item">
|
<!-- <view class="item">
|
||||||
<u-image width="100%" :fade="false" src="/static/mall/Home.png" mode="widthFix"></u-image>
|
<u-image width="100%" :fade="false" src="/static/mall/Home.png" mode="widthFix"></u-image>
|
||||||
店铺
|
店铺
|
||||||
</view>
|
</view> -->
|
||||||
<view class="item">
|
<view class="item">
|
||||||
<u-image width="100%" :fade="false" src="/static/mall/Twitch.png" mode="widthFix"></u-image>
|
<u-image width="100%" :fade="false" src="/static/mall/Twitch.png" mode="widthFix"></u-image>
|
||||||
客服
|
客服
|
||||||
@@ -26,19 +112,95 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<view class="text-btn">
|
<view class="text-btn">
|
||||||
<u-button class="add-car common" :hair-line="false" hover-class="none">加入购物车</u-button>
|
<u-button class="add-car common" :hair-line="false" hover-class="none"
|
||||||
<u-button class="buy common" :hair-line="false" hover-class="none">领券购买</u-button>
|
@click="handleBtn(0)">加入购物车</u-button>
|
||||||
|
<u-button class="buy common" :hair-line="false" hover-class="none" @click="handleBtn(1)">领券购买</u-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
|
<u-popup v-model="showSure" mode="bottom" length="70%" :closeable="true" blur="90%">
|
||||||
|
<scroll-view scroll-y="true" style="height: 100%;">
|
||||||
|
<view class="sure-popup">
|
||||||
|
<view class="title">{{popTitle}}</view>
|
||||||
|
<view class="address">
|
||||||
|
<view class="text">
|
||||||
|
<image style="width: 40rpx;height: 40rpx;" src="/static/icon/Map pin2.png" mode=""></image>
|
||||||
|
<view class="u-m-l-10">张三 | </view>
|
||||||
|
<view class="u-m-l-10">浙江省 宁波市 海曙区</view>
|
||||||
|
</view>
|
||||||
|
<view class="right-icon">
|
||||||
|
<image style="width: 100%;height: 100%;" src="/static/icon/Chevron right Menu.png" mode="">
|
||||||
|
</image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="count-select u-p-l-10 u-p-r-10">
|
||||||
|
<view class="pre-view">
|
||||||
|
<u-image :src="getImageUrl(dataInfo.image_url)" height="100%" width="100%">
|
||||||
|
<template v-slot:error>
|
||||||
|
<view style="font-size: 24rpx;">暂无图片</view>
|
||||||
|
</template>
|
||||||
|
</u-image>
|
||||||
|
</view>
|
||||||
|
<view class="text">
|
||||||
|
<view>
|
||||||
|
实付<image src="/static/icon/rongdou.png" class="icon" mode=""></image>
|
||||||
|
{{dataInfo.price * order.count}}
|
||||||
|
<view>
|
||||||
|
<u-icon name="integral"></u-icon>
|
||||||
|
{{dataInfo.points * order.count}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view>
|
||||||
|
<u-number-box v-model="order.count" :max="dataInfo.stock" :min="1"></u-number-box>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="spec-option" v-for="item in specNames">
|
||||||
|
<view class="title">
|
||||||
|
{{item + "("+specOptions.get(item).size+")"}}
|
||||||
|
</view>
|
||||||
|
<view class="option-list">
|
||||||
|
<view class="option-item" v-for="key in specOptions.get(item).keys()" :key="key"
|
||||||
|
:class="{active: isChose(key), unactive: !canChose(key)}"
|
||||||
|
@click="handleChangeSpec(key, item)">
|
||||||
|
{{specOptions.get(item).get(key)}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="spec-option">
|
||||||
|
<view class="title">
|
||||||
|
订单备注
|
||||||
|
</view>
|
||||||
|
<view class="">
|
||||||
|
<u-input type="textarea" :border="true" :height="100" v-model="order.orderNote"></u-input>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="spec-option">
|
||||||
|
<u-button @click="handleSubmit">{{popTitle}}</u-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
</u-popup>
|
||||||
|
|
||||||
|
<u-toast ref="msgRef" />
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { onMounted, ref, getCurrentInstance } from 'vue';
|
import {
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
getCurrentInstance
|
||||||
|
} from 'vue';
|
||||||
import {
|
import {
|
||||||
onLoad
|
onLoad
|
||||||
} from '@dcloudio/uni-app';
|
} from '@dcloudio/uni-app';
|
||||||
import { mallAPI } from '../../api/mall';
|
import {
|
||||||
|
mallAPI
|
||||||
|
} from '../../api/mall';
|
||||||
|
import {
|
||||||
|
getImageUrl
|
||||||
|
} from '../../util/common.js';
|
||||||
|
|
||||||
const instance = getCurrentInstance();
|
const instance = getCurrentInstance();
|
||||||
const scrollHeight = ref(0)
|
const scrollHeight = ref(0)
|
||||||
@@ -46,21 +208,255 @@
|
|||||||
uni.getSystemInfo({
|
uni.getSystemInfo({
|
||||||
success(res) {
|
success(res) {
|
||||||
let screenHeight = res.screenHeight
|
let screenHeight = res.screenHeight
|
||||||
uni.createSelectorQuery().in(instance.proxy).select("#uNavbarId").boundingClientRect((data : any) => {
|
uni.createSelectorQuery().in(instance.proxy).select("#uNavbarId").boundingClientRect((
|
||||||
|
data) => {
|
||||||
scrollHeight.value = screenHeight - data.height
|
scrollHeight.value = screenHeight - data.height
|
||||||
}).exec()
|
}).exec()
|
||||||
uni.createSelectorQuery().in(instance.proxy).select("#bottomViewId").boundingClientRect((data : any) => {
|
uni.createSelectorQuery().in(instance.proxy).select("#bottomViewId").boundingClientRect((
|
||||||
|
data) => {
|
||||||
scrollHeight.value = scrollHeight.value - data.height
|
scrollHeight.value = scrollHeight.value - data.height
|
||||||
}).exec()
|
}).exec()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const currentBanner = ref(0)
|
||||||
|
const handleChangeBanner = (index) => {
|
||||||
|
currentBanner.value = index
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCheck = (item) => {
|
||||||
|
uni.redirectTo({
|
||||||
|
url: '/pages/home/mallDetail?id=' + item.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const msgRef = ref()
|
||||||
|
|
||||||
|
// 切换 加入购物车/确认购买
|
||||||
|
const showSure = ref(false)
|
||||||
|
const popTitle = ref('')
|
||||||
|
const flag = ref(0)
|
||||||
|
|
||||||
|
const handleBtn = (sign) => {
|
||||||
|
if (sign == 0) {
|
||||||
|
popTitle.value = '加入购物车'
|
||||||
|
flag.value = 0
|
||||||
|
} else if (sign == 1) {
|
||||||
|
popTitle.value = '确认购买'
|
||||||
|
flag.value = 1
|
||||||
|
}
|
||||||
|
showSure.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = ref({
|
||||||
|
count: 1
|
||||||
|
})
|
||||||
|
const choseKeys = ref([])
|
||||||
|
|
||||||
|
const handleChangeSpec = (key, allKey) => {
|
||||||
|
if (!canChose(key)) return
|
||||||
|
if (choseKeys.value.includes(key)) {
|
||||||
|
// 取消选中
|
||||||
|
choseKeys.value = choseKeys.value.filter(item => item != key)
|
||||||
|
} else {
|
||||||
|
// console.log(choseKeys.value);
|
||||||
|
// console.log(specOptions.value.get(allKey));
|
||||||
|
// 清除选项
|
||||||
|
const keysArray = Array.from(specOptions.value.get(allKey).keys())
|
||||||
|
choseKeys.value = choseKeys.value.filter(item => {
|
||||||
|
if (keysArray.includes(item)) return false
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
// 选中
|
||||||
|
choseKeys.value.push(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断当前key是否可选
|
||||||
|
const canChose = (key) => {
|
||||||
|
// console.log("当前规格ID:", key);
|
||||||
|
// console.log("规格组合列表:", activeKeys.value);
|
||||||
|
// console.log("已选择的规格ID列表:", choseKeys.value);
|
||||||
|
// console.log("规格选项:", specOptions.value);
|
||||||
|
|
||||||
|
// 如果已经选中,肯定可选(因为可以取消选中)
|
||||||
|
if (isChose(key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建临时选择的key列表
|
||||||
|
let tempChoseKeys = [...choseKeys.value];
|
||||||
|
|
||||||
|
// 找到当前key所属的规格类别
|
||||||
|
let specCategory = '';
|
||||||
|
for (const [category, options] of specOptions.value.entries()) {
|
||||||
|
if (options.has(key)) {
|
||||||
|
specCategory = category;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到了所属规格类别,需要先移除同类别下已选的其他选项
|
||||||
|
if (specCategory) {
|
||||||
|
const sameCategoryKeys = Array.from(specOptions.value.get(specCategory).keys());
|
||||||
|
tempChoseKeys = tempChoseKeys.filter(item => !sameCategoryKeys.includes(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前要判断的key
|
||||||
|
tempChoseKeys.push(key);
|
||||||
|
|
||||||
|
// 检查是否存在包含所有临时选择key的有效规格组合
|
||||||
|
const hasValidCombination = activeKeys.value.some(combination => {
|
||||||
|
return arrayContainsAll(combination, tempChoseKeys);
|
||||||
|
});
|
||||||
|
|
||||||
|
return hasValidCombination;
|
||||||
|
}
|
||||||
|
|
||||||
|
// arr1是否包含arr2
|
||||||
|
const arrayContainsAll = (arr1, arr2) => {
|
||||||
|
return arr2.every(item => arr1.includes(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否被选中
|
||||||
|
const isChose = (key) => {
|
||||||
|
return choseKeys.value.includes(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
// 匹配规格
|
||||||
|
let specification = dataInfo.value.specifications.filter(item => {
|
||||||
|
let group = item.combination_key.split('-').map(Number)
|
||||||
|
if (arraysEqualUnordered(group, choseKeys.value)) return true
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
let specificationId = null
|
||||||
|
if (specification.length != 0) {
|
||||||
|
specificationId = specification[0].id
|
||||||
|
} else {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: '请选择规格',
|
||||||
|
type: 'warning'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 立即购买
|
||||||
|
const cartItem = {
|
||||||
|
productId: dataInfo.value.id,
|
||||||
|
quantity: order.value.count,
|
||||||
|
specificationId: specificationId,
|
||||||
|
points: dataInfo.value.points,
|
||||||
|
name: dataInfo.value.name,
|
||||||
|
image: dataInfo.value.image,
|
||||||
|
stock: dataInfo.value.stock
|
||||||
|
}
|
||||||
|
if (flag.value == 0) {
|
||||||
|
// 加入购物车
|
||||||
|
mallAPI.addCart(cartItem).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: '已加入购物车',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: res.data.message || '添加到购物车失败',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
order.value.count = 1
|
||||||
|
order.value.orderNote = ''
|
||||||
|
choseKeys.value = []
|
||||||
|
showSure.value = false
|
||||||
|
})
|
||||||
|
} else if (flag.value == 1) {
|
||||||
|
mallAPI.addCart(cartItem).then(res => {
|
||||||
|
if (res.success) {
|
||||||
|
const cartItemId = res.data?.cart_item_id || res.data?.id || res.data
|
||||||
|
?.cartItemId || res.id
|
||||||
|
if (!cartItemId) {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: '无法获取购物车项ID',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
mallAPI.createOrder({
|
||||||
|
cart_item_ids: [cartItemId]
|
||||||
|
}).then(response => {
|
||||||
|
if (response.success) {
|
||||||
|
// 进入支付页面
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/home/pay?preOrderId=' + response.data
|
||||||
|
.preOrderId
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: '操作失败',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msgRef.value.show({
|
||||||
|
title: res.data.message || '添加到购物车失败',
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断两个数组值是否相等
|
||||||
|
const arraysEqualUnordered = (arr1, arr2) => {
|
||||||
|
if (arr1.length !== arr2.length) return false;
|
||||||
|
|
||||||
|
const sorted1 = [...arr1].sort();
|
||||||
|
const sorted2 = [...arr2].sort();
|
||||||
|
|
||||||
|
return sorted1.every((item, index) => item === sorted2[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const dataId = ref()
|
const dataId = ref()
|
||||||
|
const dataInfo = ref({})
|
||||||
|
const recommendedList = ref([])
|
||||||
|
const specNames = ref(new Set())
|
||||||
|
const specOptions = ref(new Map())
|
||||||
|
const activeKeys = ref([])
|
||||||
|
|
||||||
const loadData = () => {
|
const loadData = () => {
|
||||||
mallAPI.getMallDetail(dataId.value).then(res => {
|
mallAPI.getMallDetail(dataId.value).then(res => {
|
||||||
console.log(res);
|
dataInfo.value = res.data.product
|
||||||
|
dataInfo.value.banner = []
|
||||||
|
if (dataInfo.value.images.length != 0) {
|
||||||
|
dataInfo.value.images.forEach(item => {
|
||||||
|
dataInfo.value.banner.push(getImageUrl(item))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
loadSpec()
|
||||||
|
})
|
||||||
|
mallAPI.getRecommended(dataId.value).then(res => {
|
||||||
|
recommendedList.value = res.data.products
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSpec = () => {
|
||||||
|
dataInfo.value.specifications.forEach(specification => {
|
||||||
|
activeKeys.value.push(specification.combination_key)
|
||||||
|
specification.spec_details.forEach(detail => {
|
||||||
|
specNames.value.add(detail.spec_display_name)
|
||||||
|
let data = specOptions.value.get(detail.spec_display_name)
|
||||||
|
if (data == undefined) {
|
||||||
|
data = new Map()
|
||||||
|
data.set(detail.id, detail.display_value)
|
||||||
|
specOptions.value.set(detail.spec_display_name, data)
|
||||||
|
} else {
|
||||||
|
data.set(detail.id, detail.display_value)
|
||||||
|
specOptions.value.set(detail.spec_display_name, data)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,13 +476,138 @@
|
|||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: linear-gradient(180deg, #2F75F9 0%, #F0F3FF 34.13%);
|
background: linear-gradient(180deg, #2F75F9 0%, #F0F3FF 34.13%);
|
||||||
|
|
||||||
|
|
||||||
.collection {
|
.collection {
|
||||||
width: 48rpx;
|
width: 48rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
margin-right: 24rpx;
|
margin-right: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img-sub-banner {
|
||||||
|
padding: 12rpx 0;
|
||||||
|
background: #fff;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.img-item {
|
||||||
|
border-radius: 20rpx;
|
||||||
|
width: 128rpx;
|
||||||
|
height: 128rpx;
|
||||||
|
margin: 0 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 1px solid #189eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mall-info {
|
||||||
|
padding: 20rpx 34rpx;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: 650;
|
||||||
|
font-size: 20px;
|
||||||
|
leading-trim: NONE;
|
||||||
|
line-height: 46rpx;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mall-price {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #305DEF;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 30rpx;
|
||||||
|
width: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
border: 1rpx solid #fff;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
box-shadow: 1px 1px 2px 2px rgba(0, 0, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.recommend {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
|
||||||
|
.mall-list {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
|
||||||
|
.mall-item {
|
||||||
|
border-radius: 16rpx;
|
||||||
|
background: #F0F5FF;
|
||||||
|
position: relative;
|
||||||
|
margin-top: 20rpx;
|
||||||
|
box-shadow: 0px 4px 4px 0px #00000040;
|
||||||
|
padding-bottom: 10rpx;
|
||||||
|
|
||||||
|
.mall-title {
|
||||||
|
font-size: 30rpx;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
color: $u-main-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mall-price {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: $u-type-error;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 30rpx;
|
||||||
|
width: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.mall-tag {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 5px;
|
||||||
|
|
||||||
|
.mall-tag-owner {
|
||||||
|
background-color: $u-type-error;
|
||||||
|
color: #FFFFFF;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4rpx 14rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mall-tag-text {
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid $u-type-primary;
|
||||||
|
color: $u-type-primary;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 4rpx 14rpx;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mall-shop {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: $u-tips-color;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.bottom-view {
|
.bottom-view {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -158,4 +679,93 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sure-popup {
|
||||||
|
background: #F5F8FF;
|
||||||
|
padding-bottom: 20rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 274;
|
||||||
|
font-style: Light;
|
||||||
|
font-size: 32rpx;
|
||||||
|
leading-trim: NONE;
|
||||||
|
line-height: 48rpx;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 30rpx;
|
||||||
|
padding: 20rpx 10rpx;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.count-select {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
|
||||||
|
.pre-view {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 30rpx;
|
||||||
|
width: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spec-option {
|
||||||
|
margin: 20rpx 0;
|
||||||
|
padding: 0 20rpx;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: Bold;
|
||||||
|
font-size: 32rpx;
|
||||||
|
leading-trim: NONE;
|
||||||
|
line-height: 48rpx;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-list {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.option-item {
|
||||||
|
background: #f2f3f5;
|
||||||
|
padding: 10rpx 20rpx;
|
||||||
|
margin: 20rpx 20rpx 20rpx 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
border: 1rpx solid #ff6b35;
|
||||||
|
color: #ff6b35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unactive {
|
||||||
|
background: #D9D9D9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
216
pages/home/pay.vue
Normal file
216
pages/home/pay.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<view class="pay-container">
|
||||||
|
<scroll-view scroll-y="true" style="height: 100%;">
|
||||||
|
<view class="scroll-container">
|
||||||
|
<view class="box-bg">
|
||||||
|
<view class="address">
|
||||||
|
<view class="text">
|
||||||
|
<image style="width: 40rpx;height: 40rpx;" src="/static/icon/Map pin2.png" mode=""></image>
|
||||||
|
<view class="u-m-l-10">张三 | </view>
|
||||||
|
<view class="u-m-l-10">浙江省 宁波市 海曙区</view>
|
||||||
|
</view>
|
||||||
|
<view class="right-icon">
|
||||||
|
<image style="width: 100%;height: 100%;" src="/static/icon/Chevron right Menu.png" mode="">
|
||||||
|
</image>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="count-select u-p-l-10 u-p-r-10" v-for="item in dataInfo.items">
|
||||||
|
<view class="pre-view">
|
||||||
|
<u-image :src="getImageUrl(item.image_url)" height="100%" width="100%">
|
||||||
|
<template v-slot:error>
|
||||||
|
<view style="font-size: 24rpx;">暂无图片</view>
|
||||||
|
</template>
|
||||||
|
</u-image>
|
||||||
|
</view>
|
||||||
|
<view class="text">
|
||||||
|
{{item.product_name}}
|
||||||
|
融豆:{{item.rongdou_price}}
|
||||||
|
积分:{{item.points_price}}
|
||||||
|
数量X{{item.quantity}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="box-bg description u-m-t-20">
|
||||||
|
<view class="item">
|
||||||
|
<view class="title">
|
||||||
|
订单编号
|
||||||
|
</view>
|
||||||
|
<view class="value">
|
||||||
|
{{dataInfo.order_no}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="item">
|
||||||
|
<view class="title">
|
||||||
|
创建时间
|
||||||
|
</view>
|
||||||
|
<view class="value">
|
||||||
|
{{dataInfo.created_at}}
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="box-bg pay-method u-m-t-20">
|
||||||
|
<view class="title">
|
||||||
|
支付方式
|
||||||
|
</view>
|
||||||
|
<view class="item">
|
||||||
|
<view class="title">
|
||||||
|
融豆
|
||||||
|
</view>
|
||||||
|
<view class="value">
|
||||||
|
<u-checkbox v-model="checked" shape="circle" active-color="#305def"></u-checkbox>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
<view class="box-bottom">
|
||||||
|
<view class="text">
|
||||||
|
实际支付:1231
|
||||||
|
</view>
|
||||||
|
<view class="btn">
|
||||||
|
<u-button>确认支付</u-button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<u-toast ref="msgRef" />
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
onMounted,
|
||||||
|
ref
|
||||||
|
} from 'vue';
|
||||||
|
import {
|
||||||
|
onLoad
|
||||||
|
} from '@dcloudio/uni-app'
|
||||||
|
import {
|
||||||
|
mallAPI
|
||||||
|
} from '../../api/mall';
|
||||||
|
import {
|
||||||
|
getImageUrl
|
||||||
|
} from '../../util/common.js'
|
||||||
|
|
||||||
|
const checked = ref(false)
|
||||||
|
|
||||||
|
const dataInfo = ref({})
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
mallAPI.getOrder(preOrderId.value).then(res => {
|
||||||
|
dataInfo.value = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const preOrderId = ref()
|
||||||
|
|
||||||
|
onLoad((val) => {
|
||||||
|
preOrderId.value = val.preOrderId
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.pay-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--Color, #E4ECFF);
|
||||||
|
|
||||||
|
.scroll-container {
|
||||||
|
|
||||||
|
.box-bg {
|
||||||
|
background: #F5F8FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx 10rpx;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-icon {
|
||||||
|
width: 40rpx;
|
||||||
|
height: 40rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.count-select {
|
||||||
|
display: flex;
|
||||||
|
margin-top: 10rpx;
|
||||||
|
|
||||||
|
.pre-view {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
margin-left: 20rpx;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 30rpx;
|
||||||
|
width: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pay-method {
|
||||||
|
padding: 30rpx 40rpx;
|
||||||
|
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: Regular;
|
||||||
|
font-size: 28rpx;
|
||||||
|
leading-trim: NONE;
|
||||||
|
line-height: 48rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.box-bottom {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20rpx 40rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
background: #F5F8FF;
|
||||||
|
|
||||||
|
|
||||||
|
.text {}
|
||||||
|
|
||||||
|
.btn {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
static/icon/Map pin2.png
Normal file
BIN
static/icon/Map pin2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Reference in New Issue
Block a user