Files
2026-03-09 09:07:58 +08:00

1734 lines
40 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="goods-container">
<!-- 商品价格 -->
<view class="price-section">
<view class="price-main">
<text class="price-value">{{ goodsInfo.price }}</text>
</view>
<view class="" style="display: flex;flex-direction: column;justify-content: space-around;">
<text class="price-unit">RMB</text>
<text class="price-per-unit">{{ $t('goods.perUnit') }}</text>
</view>
</view>
<!-- 商品图片轮播 -->
<view class="product-swiper-wrapper">
<swiper class="product-swiper" :indicator-dots="goodsInfo.imageList.length > 1" :autoplay="true"
:interval="3000" :duration="500" indicator-color="rgba(255, 255, 255, 0.5)"
indicator-active-color="#07c160">
<swiper-item v-for="(image, index) in goodsInfo.imageList" :key="index">
<view class="swiper-item-wrapper">
<image :src="image" mode="aspectFit" class="product-image" lazy-load="true"></image>
</view>
</swiper-item>
</swiper>
</view>
<!-- 商品名称 -->
<view class="product-name">
<text>{{ goodsInfo.name }}</text>
</view>
<!-- 商品特性 -->
<view class="features-section">
<view class="feature-item">
<view class="feature-icon">
<image src="@/static/battery-icon.png" mode="aspectFit" class="icon-img" lazy-load="true"></image>
</view>
<view class="" style="display: flex;flex-direction: column;align-items: center;">
<text class="feature-label">{{ $t('goods.features.battery') }}</text>
<text class="feature-desc">{{ $t('goods.features.batteryDesc') }}</text>
</view>
</view>
<view class="feature-item">
<view class="feature-icon">
<image src="@/static/wind-icon.png" mode="aspectFit" class="icon-img" lazy-load="true"></image>
</view>
<view class="" style="display: flex;flex-direction: column;align-items: center;">
<text class="feature-label">{{ $t('goods.features.wind') }}</text>
</view>
</view>
<view class="feature-item">
<view class="feature-icon">
<image src="@/static/temp-icon.png" mode="aspectFit" class="icon-img" lazy-load="true"></image>
</view>
<view class="" style="display: flex;flex-direction: column;align-items: center;">
<text class="feature-label">{{ $t('goods.features.temp') }}</text>
</view>
</view>
<view class="feature-item">
<view class="feature-icon">
<image src="@/static/charge-icon.png" mode="aspectFit" class="icon-img" lazy-load="true"></image>
</view>
<view class="" style="display: flex;flex-direction: column;align-items: center;">
<text class="feature-label">{{ $t('goods.features.charge') }}</text>
</view>
</view>
</view>
<!-- 商品详情描述 -->
<view class="description-section">
<view class="section-title">{{ $t('goods.productDetail') }}</view>
<view class="description-content">
<text class="description-text">
{{ $t('goods.description') }}
</text>
</view>
</view>
<!-- 底部购买按钮 -->
<view class="footer">
<view class="" style="display: flex;flex-direction: column;width: 140rpx;align-items: center;margin-right: 20rpx;" @click="GotoList">
<image src="/static/jl.png" mode="scaleToFill" style="width: 35rpx;height:35rpx;" lazy-load="true"></image>
<text style="font-size: 26rpx;">定制记录</text>
</view>
<view class="buy-button" @click="handleBuy">
<view class="button-content">
<view class="price-info">
<text class="footer-price-symbol">¥</text>
<text class="footer-price-value">{{ selectedSku.price || goodsInfo.price }}</text>
</view>
<text class="buy-text">{{ $t('goods.buyNow') }}</text>
</view>
</view>
</view>
<!-- 规格选择弹窗 -->
<view class="popup-mask" v-if="showSkuPopup" @click="closeSkuPopup">
<view class="popup-container" @click.stop>
<view class="sku-popup">
<view class="popup-header">
<text class="popup-title">选择规格</text>
<view class="close-btn" @click="closeSkuPopup">
<text class="close-icon">✕</text>
</view>
</view>
<view class="sku-info">
<image v-if="selectedSku.pictureUrl" :src="selectedSku.pictureUrl" class="sku-image"
mode="aspectFit" lazy-load="true"></image>
<view class="sku-detail">
<text class="sku-price">¥{{ selectedSku.price || goodsInfo.price }}</text>
<text class="sku-name">{{ selectedSku.optionName || '请选择规格' }}</text>
</view>
</view>
<view class="sku-list">
<text class="sku-list-title">规格</text>
<view class="sku-options">
<view v-for="sku in goodsInfo.skuList" :key="sku.skuId" class="sku-option"
:class="{ 'active': selectedSku.skuId === sku.skuId }" @click="selectSku(sku)">
<text class="sku-option-text">{{ sku.optionName }}</text>
<text class="sku-option-color" v-if="sku.color">{{ sku.color }}</text>
</view>
</view>
</view>
<view class="quantity-section">
<text class="quantity-label">数量</text>
<view class="quantity-control">
<view class="quantity-btn" :class="{ 'disabled': quantity <= 1 }" @click="decreaseQuantity">
<text>-</text>
</view>
<input class="quantity-input" type="number" v-model.number="quantity"
@blur="validateQuantity" />
<view class="quantity-btn" @click="increaseQuantity">
<text>+</text>
</view>
</view>
</view>
<view class="popup-footer">
<view class="confirm-btn" @click="confirmSku">
<text>确定</text>
</view>
</view>
</view>
</view>
</view>
<!-- 收货信息填写弹窗 -->
<view class="popup-mask" v-if="showAddressPopup" @click="closeAddressPopup">
<view class="popup-container" @click.stop>
<view class="address-popup">
<view class="popup-header">
<text class="popup-title">填写收货信息</text>
<view class="close-btn" @click="closeAddressPopup">
<text class="close-icon">✕</text>
</view>
</view>
<view class="form-section">
<view class="form-item">
<text class="form-label">收件人</text>
<input class="form-input" v-model="addressForm.receiverName" placeholder="请输入收件人姓名"
placeholder-class="input-placeholder" />
</view>
<view class="form-item">
<text class="form-label">手机号</text>
<input class="form-input" v-model="addressForm.receiverPhone" type="number" maxlength="11"
placeholder="请输入手机号" placeholder-class="input-placeholder" />
</view>
<view class="form-item">
<text class="form-label">收货地区</text>
<!-- 非支付宝小程序:使用多列 picker -->
<!-- #ifndef MP-ALIPAY -->
<picker mode="multiSelector" :range="regionColumns" range-key="name" :value="regionIndexes"
@change="onRegionChange" @columnchange="onRegionColumnChange">
<view class="form-input region-selector">
<text v-if="addressForm.province && addressForm.city && addressForm.district"
class="region-text">
{{ addressForm.province }} {{ addressForm.city }} {{ addressForm.district }}
</text>
<text v-else class="input-placeholder">请选择省市区</text>
<text class="arrow-icon"></text>
</view>
</picker>
<!-- #endif -->
<!-- 支付宝小程序:使用自定义弹窗 + picker-view -->
<!-- #ifdef MP-ALIPAY -->
<view class="form-input region-selector" @click="showRegionPicker = true">
<text v-if="addressForm.province && addressForm.city && addressForm.district"
class="region-text">
{{ addressForm.province }} {{ addressForm.city }} {{ addressForm.district }}
</text>
<text v-else class="input-placeholder">请选择省市区</text>
<text class="arrow-icon"></text>
</view>
<!-- #endif -->
</view>
<view class="form-item">
<text class="form-label">详细地址</text>
<textarea class="form-textarea" v-model="addressForm.receiverAddress"
placeholder="请输入详细地址街道门牌号等" placeholder-class="input-placeholder" :maxlength="200"
:show-confirm-bar="false" />
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea class="form-textarea" v-model="addressForm.remark" placeholder="选填可以告诉我们您的特殊需求"
placeholder-class="input-placeholder" :maxlength="200" :show-confirm-bar="false" />
</view>
</view>
<view class="popup-footer">
<view class="confirm-btn" @click="confirmAddress">
<text>确认并支付</text>
</view>
</view>
</view>
</view>
</view>
<!-- 地址展示弹窗(已有地址时显示) -->
<view class="popup-mask" v-if="showAddressDisplay" @click="closeAddressDisplay">
<view class="popup-container" @click.stop>
<view class="address-popup">
<view class="popup-header">
<text class="popup-title">收货信息</text>
<view class="close-btn" @click="closeAddressDisplay">
<text class="close-icon">✕</text>
</view>
</view>
<view class="form-section">
<view class="display-item">
<text class="display-label">收件人</text>
<text class="display-value">{{ savedAddress.receiverName }}</text>
</view>
<view class="display-item">
<text class="display-label">手机号</text>
<text class="display-value">{{ savedAddress.receiverPhone }}</text>
</view>
<view class="display-item">
<text class="display-label">收货地址</text>
<text class="display-value">{{ savedAddress.fullAddress }}</text>
</view>
</view>
<view class="popup-footer">
<view class="button-group">
<view class="secondary-btn" @click="changeAddress">
<text>更换地址</text>
</view>
<view class="confirm-btn" @click="payNow">
<text>立即支付</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 支付宝小程序地区选择弹窗 -->
<!-- #ifdef MP-ALIPAY -->
<view class="popup-mask" v-if="showRegionPicker" @click="closeRegionPicker">
<view class="popup-container" @click.stop>
<view class="address-popup">
<view class="popup-header">
<text class="popup-title">选择省市区</text>
<view class="close-btn" @click="closeRegionPicker">
<text class="close-icon">✕</text>
</view>
</view>
<view class="form-section">
<picker-view :value="regionIndexes" @change="onAliRegionChange"
indicator-style="height: 50px;">
<picker-view-column>
<view v-for="item in regionColumns[0]" :key="item.code">
{{ item.name }}
</view>
</picker-view-column>
<picker-view-column>
<view v-for="item in regionColumns[1]" :key="item.code">
{{ item.name }}
</view>
</picker-view-column>
<picker-view-column>
<view v-for="item in regionColumns[2]" :key="item.code">
{{ item.name }}
</view>
</picker-view-column>
</picker-view>
</view>
<view class="popup-footer">
<view class="confirm-btn" @click="confirmAliRegion">
<text>确定</text>
</view>
</view>
</view>
</view>
</view>
<!-- #endif -->
</view>
</template>
<script setup>
import {
ref,
onMounted,
computed
} from 'vue'
import {
onLoad
} from '@dcloudio/uni-app'
import {
useI18n
} from '@/utils/i18n.js'
import {
getProductDetail,
createProductOrder,
getUserAddress
} from '@/config/api/product.js'
import {
cancelProductOrder
} from '@/config/api/order.js'
import pcaData from '@/components/pca.json'
const {
t
} = useI18n()
// 商品信息
const goodsInfo = ref({
id: '',
name: '',
price: 99,
image: '/static/product-fan.png',
imageList: ['/static/product-fan.png'], // 图片列表用于轮播
description: '',
features: [],
skuList: [] // 商品规格列表
})
// 选中的规格
const selectedSku = ref({
skuId: '',
price: '',
pictureUrl: '',
optionName: '',
color: ''
})
// 购买数量
const quantity = ref(1)
// 收货信息表单
const addressForm = ref({
receiverName: '',
receiverPhone: '',
province: '',
provinceCode: '',
city: '',
cityCode: '',
district: '',
districtCode: '',
receiverAddress: '',
remark: ''
})
// 已保存的地址信息(用于判断是否已有地址)
const savedAddress = ref(null)
// 地区选择相关(uni原生picker
const provinceList = ref([])
const cityList = ref([])
const districtList = ref([])
const regionColumns = ref([
[],
[],
[]
]) // 三列数据:省、市、区
const regionIndexes = ref([0, 0, 0]) // 当前选中的索引
// 弹窗显示状态
const showSkuPopup = ref(false)
const showAddressPopup = ref(false)
const showAddressDisplay = ref(false) // 地址展示弹窗
const showRegionPicker = ref(false) // 支付宝地区选择弹窗
// 计算是否已有地址
const hasAddress = computed(() => {
return savedAddress.value !== null
})
// 加载状态
const loading = ref(false)
// 页面加载
onLoad((options) => {
if (options.productId) {
goodsInfo.value.id = options.productId
// 根据 productId 获取商品详情
fetchGoodsDetail(options.productId)
} else if (options.goodsId) {
// 兼容旧的参数名
goodsInfo.value.id = options.goodsId
fetchGoodsDetail(options.goodsId)
}
})
onMounted(() => {
uni.setNavigationBarTitle({
title: t('goods.goodsTitle')
})
// 初始化地区数据
initRegionData()
// 获取用户收货地址
fetchUserAddress()
})
// 初始化地区数据
const initRegionData = () => {
// 获取省份列表
const provinces = pcaData['86']
provinceList.value = Object.keys(provinces).map(code => ({
code,
name: provinces[code]
}))
// 初始化第一列(省份)
regionColumns.value[0] = provinceList.value
// 初始化第二列(城市)- 默认加载第一个省份的城市
if (provinceList.value.length > 0) {
const firstProvinceCode = provinceList.value[0].code
const cities = pcaData[firstProvinceCode]
if (cities) {
cityList.value = Object.keys(cities).map(code => ({
code,
name: cities[code]
}))
regionColumns.value[1] = cityList.value
// 初始化第三列(区县)- 默认加载第一个城市的区县
if (cityList.value.length > 0) {
const firstCityCode = cityList.value[0].code
const districts = pcaData[firstCityCode]
if (districts) {
districtList.value = Object.keys(districts).map(code => ({
code,
name: districts[code]
}))
regionColumns.value[2] = districtList.value
}
}
}
}
}
// 获取城市列表
const getCityList = (provinceCode) => {
const cities = pcaData[provinceCode]
if (cities) {
return Object.keys(cities).map(code => ({
code,
name: cities[code]
}))
}
return []
}
// 获取区县列表
const getDistrictList = (cityCode) => {
const districts = pcaData[cityCode]
if (districts) {
return Object.keys(districts).map(code => ({
code,
name: districts[code]
}))
}
return []
}
// picker列变化事件
const onRegionColumnChange = (e) => {
const {
column,
value
} = e.detail
const newIndexes = [...regionIndexes.value]
newIndexes[column] = value
if (column === 0) {
// 省份变化,更新城市和区县列表
const provinceCode = regionColumns.value[0][value].code
const cities = getCityList(provinceCode)
regionColumns.value[1] = cities
newIndexes[1] = 0 // 重置城市索引
if (cities.length > 0) {
const cityCode = cities[0].code
const districts = getDistrictList(cityCode)
regionColumns.value[2] = districts
newIndexes[2] = 0 // 重置区县索引
} else {
regionColumns.value[2] = []
}
} else if (column === 1) {
// 城市变化,更新区县列表
const cityCode = regionColumns.value[1][value].code
const districts = getDistrictList(cityCode)
regionColumns.value[2] = districts
newIndexes[2] = 0 // 重置区县索引
}
regionIndexes.value = newIndexes
}
// picker确认选择
const onRegionChange = (e) => {
const indexes = e.detail.value
regionIndexes.value = indexes
// 获取选中的省市区
const province = regionColumns.value[0][indexes[0]]
const city = regionColumns.value[1][indexes[1]]
const district = regionColumns.value[2][indexes[2]]
// 更新表单数据
addressForm.value.province = province.name
addressForm.value.provinceCode = province.code
addressForm.value.city = city.name
addressForm.value.cityCode = city.code
addressForm.value.district = district.name
addressForm.value.districtCode = district.code
}
// 支付宝小程序 picker-view 列变化
const onAliRegionChange = (e) => {
const newVal = e.detail.value || []
const oldVal = regionIndexes.value || []
// 找出发生变化的列
let column = -1
for (let i = 0; i < newVal.length; i++) {
if (newVal[i] !== oldVal[i]) {
column = i
break
}
}
if (column !== -1) {
onRegionColumnChange({
detail: {
column,
value: newVal[column]
}
})
}
}
// 关闭支付宝地区弹窗
const closeRegionPicker = () => {
showRegionPicker.value = false
}
// 支付宝地区选择“确定”
const confirmAliRegion = () => {
onRegionChange({
detail: {
value: regionIndexes.value
}
})
closeRegionPicker()
}
// 获取用户收货地址
const fetchUserAddress = async () => {
try {
const res = await getUserAddress()
console.log('获取收货地址结果:', res)
if (res && res.code === 200 && res.data) {
// 保存地址信息
savedAddress.value = {
receiverName: res.data.userName || '',
receiverPhone: res.data.userPhone || '',
province: res.data.province || '',
city: res.data.city || '',
district: res.data.district || '',
receiverAddress: res.data.userAddress || '',
fullAddress: `${res.data.province || ''}${res.data.city || ''}${res.data.district || ''}${res.data.userAddress || ''}`
}
console.log('收货地址已保存:', savedAddress.value)
}
} catch (error) {
console.error('获取收货地址异常:', error)
// 获取地址失败不影响用户继续操作,静默处理
}
}
// 获取商品详情
const fetchGoodsDetail = async (productId) => {
try {
loading.value = true
uni.showLoading({
title: t('common.loading'),
mask: true
})
const res = await getProductDetail(productId)
console.log('商品详情查询结果:', res)
if (res && res.code === 200 && res.data) {
const product = res.data
// 获取第一个 SKU 的信息(价格和图片)
const firstSku = product.skuList && product.skuList.length > 0 ? product.skuList[0] : null
// 收集所有 SKU 的图片用于轮播
let imageList = []
if (product.skuList && product.skuList.length > 0) {
product.skuList.forEach(sku => {
// 如果有 pictureUrlList,使用它
if (sku.pictureUrlList && sku.pictureUrlList.length > 0) {
imageList = imageList.concat(sku.pictureUrlList)
} else if (sku.pictureUrl) {
// 否则使用单张图片
imageList.push(sku.pictureUrl)
}
})
}
// 去重图片列表
imageList = [...new Set(imageList)]
// 如果没有图片,使用默认图片
if (imageList.length === 0) {
imageList = ['/static/product-fan.png']
}
// 更新商品信息(根据实际返回的字段名)
goodsInfo.value = {
id: product.id,
name: product.productName || '商品名称',
price: firstSku?.price || 99,
image: imageList[0], // 主图
imageList: imageList, // 图片列表用于轮播
description: product.productDescription || product.description || '暂无描述',
features: product.features || [],
skuList: product.skuList || [], // 保存规格列表
productNo: product.productNo, // 商品编号
remark: product.remark, // 备注
color: firstSku?.color, // 颜色
optionName: firstSku?.optionName // 规格名称
}
// 默认选中第一个规格
if (firstSku) {
selectedSku.value = {
skuId: firstSku.skuId,
price: firstSku.price,
pictureUrl: firstSku.pictureUrl,
optionName: firstSku.optionName,
color: firstSku.color
}
}
console.log('商品详情加载成功:', goodsInfo.value)
} else {
console.warn('获取商品详情失败:', res?.msg || '未知错误')
uni.showToast({
title: res?.msg || t('common.loadFailed'),
icon: 'none'
})
}
} catch (error) {
console.error('获取商品详情异常:', error)
uni.showToast({
title: t('common.loadFailed'),
icon: 'none'
})
} finally {
loading.value = false
uni.hideLoading()
}
}
// 处理购买 - 打开规格选择弹窗
const handleBuy = () => {
if (goodsInfo.value.skuList.length === 0) {
uni.showToast({
title: '商品暂无可选规格',
icon: 'none'
})
return
}
showSkuPopup.value = true
}
// 选择规格
const selectSku = (sku) => {
selectedSku.value = {
skuId: sku.skuId,
price: sku.price,
pictureUrl: sku.pictureUrl,
optionName: sku.optionName,
color: sku.color
}
}
// 减少数量
const decreaseQuantity = () => {
if (quantity.value > 1) {
quantity.value--
}
}
// 增加数量
const increaseQuantity = () => {
quantity.value++
}
// 验证数量
const validateQuantity = () => {
if (!quantity.value || quantity.value < 1) {
quantity.value = 1
}
quantity.value = Math.floor(quantity.value)
}
// 确认规格选择
const confirmSku = () => {
if (!selectedSku.value.skuId) {
uni.showToast({
title: '请选择规格',
icon: 'none'
})
return
}
closeSkuPopup()
// 判断是否已有地址
if (hasAddress.value) {
// 显示地址展示弹窗
showAddressDisplay.value = true
} else {
// 打开收货信息填写弹窗
showAddressPopup.value = true
}
}
// 关闭规格弹窗
const closeSkuPopup = () => {
showSkuPopup.value = false
}
// 关闭收货信息弹窗
const closeAddressPopup = () => {
showAddressPopup.value = false
}
// 关闭地址展示弹窗
const closeAddressDisplay = () => {
showAddressDisplay.value = false
}
// 更换地址
const changeAddress = () => {
// 关闭展示弹窗
closeAddressDisplay()
// 打开填写弹窗
showAddressPopup.value = true
}
// 确认收货信息并支付
const confirmAddress = async () => {
// 验证表单
if (!addressForm.value.receiverName) {
uni.showToast({
title: '请输入收件人姓名',
icon: 'none'
})
return
}
if (!addressForm.value.receiverPhone) {
uni.showToast({
title: '请输入手机号',
icon: 'none'
})
return
}
// 验证手机号格式
const phoneReg = /^1[3-9]\d{9}$/
if (!phoneReg.test(addressForm.value.receiverPhone)) {
uni.showToast({
title: '请输入正确的手机号',
icon: 'none'
})
return
}
if (!addressForm.value.province || !addressForm.value.city || !addressForm.value.district) {
uni.showToast({
title: '请选择收货地区',
icon: 'none'
})
return
}
if (!addressForm.value.receiverAddress) {
uni.showToast({
title: '请输入详细地址',
icon: 'none'
})
return
}
// 保存地址信息(将省市区和详细地址拼接)
const fullAddress =
`${addressForm.value.province}${addressForm.value.city}${addressForm.value.district}${addressForm.value.receiverAddress}`
savedAddress.value = {
receiverName: addressForm.value.receiverName,
receiverPhone: addressForm.value.receiverPhone,
province: addressForm.value.province,
city: addressForm.value.city,
district: addressForm.value.district,
receiverAddress: fullAddress, // 完整地址
fullAddress: fullAddress // 用于显示
}
// 关闭弹窗
closeAddressPopup()
// 调用支付接口
await createOrder()
}
// 立即支付(使用已保存的地址)
const payNow = async () => {
// 关闭展示弹窗
closeAddressDisplay()
// 调用支付接口
await createOrder()
}
const GotoList = ()=>{
uni.navigateTo({
url:'/subPackages/business/device-orderList'
})
}
// 创建订单并支付(接入商品多支付平台方案:微信 / 支付宝,小程序端)
const createOrder = async () => {
try {
uni.showLoading({
title: '正在创建订单...',
mask: true
})
// 构建订单数据(优先使用已保存的地址)
const addressData = savedAddress.value || addressForm.value
// 拼接完整地址:省市区 + 详细地址
const fullAddress = savedAddress.value ?
savedAddress.value.receiverAddress :
`${addressForm.value.province}${addressForm.value.city}${addressForm.value.district}${addressForm.value.receiverAddress}`
// 根据当前运行环境确定支付平台
let paymentPlatform = 'WECHAT' // 默认微信
// #ifdef MP-ALIPAY
paymentPlatform = 'ALIPAY'
// #endif
// #ifdef H5
// H5 预留 Antom 支付,这里暂时仍按微信处理,如需接入可改为 ANTOM 并补充 paymentType / osType
paymentPlatform = 'WECHAT'
// #endif
const orderData = {
skuId: selectedSku.value.skuId,
quantity: quantity.value,
receiverName: addressData.receiverName,
receiverPhone: addressData.receiverPhone,
receiverAddress: fullAddress, // 传递完整地址(省市区+详细地址)
remark: addressForm.value.remark || '',
paymentPlatform // WECHAT / ALIPAY /(预留)ANTOM
}
console.log('创建订单数据:', orderData)
const res = await createProductOrder(orderData)
console.log('创建订单结果:', res)
if (res && res.code === 200 && res.data) {
uni.hideLoading()
// 统一获取平台订单号(商品统一支付订单号)
const outOrderNo = res.data.OutOrderNo || res.data.outOrderNo
// ====================== 微信小程序支付 ======================
// #ifdef MP-WEIXIN
const payParams = res.data
uni.requestPayment({
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: (payRes) => {
console.log('支付成功:', payRes)
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
// 重置表单
resetForm()
// 跳转到订单页面
setTimeout(() => {
uni.switchTab({
url: '/subPackages/business/device-orderList'
})
}, 2000)
},
fail: async (payErr) => {
console.error('支付失败:', payErr)
// 判断是用户取消还是支付失败
if (payErr.errMsg && payErr.errMsg.includes('cancel')) {
// 用户取消支付,这里预留调用取消订单接口
try {
// await cancelProductOrder(outOrderNo)
uni.showToast({
title: '支付已取消',
icon: 'none'
})
} catch (cancelError) {
console.error('取消订单失败:', cancelError)
uni.showToast({
title: '支付已取消',
icon: 'none'
})
}
} else {
// 支付失败
uni.showToast({
title: '支付失败,请重试',
icon: 'none'
})
}
}
})
// #endif
// ====================== 支付宝小程序支付 ======================
// #ifdef MP-ALIPAY
console.log(res.data,'支付宝支付参数');
const tradeNO = res.data.tradeNo
if (!tradeNO) {
uni.showToast({
title: '未获取到支付宝支付参数',
icon: 'none'
})
return
}
my.tradePay({
tradeNO,
success: (payRes) => {
console.log('支付宝支付结果:', payRes)
if (payRes.resultCode === '9000') {
uni.showToast({
title: '支付成功',
icon: 'success',
duration: 2000
})
resetForm()
setTimeout(() => {
uni.switchTab({
url: '/subPackages/business/device-orderList'
})
}, 2000)
} else {
uni.showToast({
title: '支付失败,请重试',
icon: 'none'
})
}
},
fail: () => {
uni.showToast({
title: '支付失败,请重试',
icon: 'none'
})
}
})
// #endif
// ====================== H5 环境(预留 Antom 支付) ======================
// #ifdef H5
uni.showToast({
title: '当前环境暂不支持购买,请使用微信或支付宝小程序',
icon: 'none'
})
// #endif
} else {
uni.hideLoading()
uni.showToast({
title: res?.msg || '创建订单失败',
icon: 'none'
})
}
} catch (error) {
console.error('创建订单异常:', error)
uni.hideLoading()
uni.showToast({
title: '创建订单失败,请重试',
icon: 'none'
})
}
}
// 重置表单
const resetForm = () => {
quantity.value = 1
addressForm.value = {
receiverName: '',
receiverPhone: '',
province: '',
provinceCode: '',
city: '',
cityCode: '',
district: '',
districtCode: '',
receiverAddress: '',
remark: ''
}
regionIndexes.value = [0, 0, 0]
}
</script>
<style lang="scss" scoped>
.goods-container {
min-height: 100vh;
background: radial-gradient(95.17% 36.74% at 95% 5%, #B1FCCB 0%, #F1F2F6 100%);
padding-bottom: 200rpx;
position: relative;
}
// 价格区域
.price-section {
padding: 40rpx 40rpx 20rpx;
display: flex;
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 100;
// align-items: baseline;
.price-main {
display: flex;
// align-items: baseline;
.price-value {
font-size: 88rpx;
font-weight: bold;
color: #333;
line-height: 1;
font-family: 'DIN Alternate', 'Arial', sans-serif;
}
.price-unit {
font-size: 32rpx;
color: #333;
font-weight: 600;
margin-left: 8rpx;
}
}
.price-per-unit {
font-size: 32rpx;
color: #666;
margin-left: 8rpx;
}
}
// 商品图片轮播
.product-swiper-wrapper {
width: 100%;
height: 510rpx;
// padding: 0 60rpx;
margin-bottom: 40rpx;
.product-swiper {
width: 100%;
height: 100%;
// border-radius: 24rpx;
overflow: hidden;
.swiper-item-wrapper {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.5);
.product-image {
width: 100%;
height: 100%;
// transform: rotate(-15deg);
// animation: float 3s ease-in-out infinite;
}
}
}
}
@keyframes float {
0%,
100% {
transform: rotate(-15deg) translateY(0);
}
50% {
transform: rotate(-15deg) translateY(-20rpx);
}
}
// 商品名称
.product-name {
padding: 0 60rpx;
// margin-bottom: 60rpx;
margin: 30rpx auto;
// width: 680rpx;
text {
font-size: 32rpx;
color: #333;
line-height: 1.6;
font-weight: 500;
}
}
// 特性区域
.features-section {
display: flex;
justify-content: space-around;
// grid-template-columns: repeat(2, 1fr);
// gap: 40rpx;
padding: 0 60rpx;
// margin-bottom: 60rpx;
.feature-item {
// background: rgba(255, 255, 255, 0.8);
border-radius: 24rpx;
padding: 20rpx 15rpx;
display: flex;
flex-direction: column;
align-items: center;
// box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.06);
backdrop-filter: blur(10rpx);
.feature-icon {
width: 100rpx;
height: 100rpx;
background: #fff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
.icon-img {
width: 48rpx;
height: 48rpx;
}
}
.feature-label {
font-size: 24rpx;
color: #333;
font-weight: 600;
// margin-bottom: 8rpx;
text-align: center;
}
.feature-desc {
font-size: 24rpx;
color: #333;
font-weight: 600;
text-align: center;
}
}
}
// 商品详情描述
.description-section {
background: #fff;
border-radius: 32rpx;
padding: 60rpx;
margin: 40rpx;
.section-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
margin-bottom: 30rpx;
}
.description-content {
.description-text {
font-size: 28rpx;
color: #666;
line-height: 1.8;
}
}
}
// 底部购买区域
.footer {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 30rpx 60rpx;
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
.buy-button {
width: 100%;
height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(135deg, #07c160, #10d673);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.4);
// &:active {
// transform: scale(0.98);
// opacity: 0.9;
// }
.button-content {
display: flex;
align-items: center;
padding: 0 40rpx;
text-align: center;
.buy-text {
color: #fff;
font-size: 34rpx;
font-weight: 600;
}
.price-info {
display: flex;
align-items: baseline;
.footer-price-symbol {
font-size: 32rpx;
font-weight: bold;
color: #fff;
}
.footer-price-value {
font-size: 48rpx;
font-weight: bold;
color: #fff;
font-family: 'DIN Alternate', 'Arial', sans-serif;
margin-left: 4rpx;
}
}
}
}
}
// 弹窗遮罩层
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
.popup-container {
width: 100%;
animation: slideUp 0.3s ease-out;
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
// 规格选择弹窗
.sku-popup {
background: #fff;
border-radius: 32rpx 32rpx 0 0;
padding: 40rpx;
max-height: 80vh;
overflow-y: auto;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 50%;
.close-icon {
font-size: 32rpx;
color: #666;
}
}
}
.sku-info {
display: flex;
align-items: center;
padding: 30rpx;
background: #f8f8f8;
border-radius: 16rpx;
margin-bottom: 40rpx;
.sku-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
margin-right: 24rpx;
background: #fff;
}
.sku-detail {
flex: 1;
display: flex;
flex-direction: column;
.sku-price {
font-size: 40rpx;
font-weight: bold;
color: #ff4d4f;
margin-bottom: 8rpx;
}
.sku-name {
font-size: 28rpx;
color: #666;
}
}
}
.sku-list {
margin-bottom: 40rpx;
.sku-list-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 24rpx;
display: block;
}
.sku-options {
display: flex;
flex-wrap: wrap;
gap: 20rpx;
.sku-option {
padding: 20rpx 32rpx;
background: #f5f5f5;
border-radius: 12rpx;
border: 2rpx solid transparent;
display: flex;
flex-direction: column;
align-items: center;
&.active {
background: #e6f7ff;
border-color: #07c160;
.sku-option-text,
.sku-option-color {
color: #07c160;
}
}
.sku-option-text {
font-size: 28rpx;
color: #333;
margin-bottom: 4rpx;
}
.sku-option-color {
font-size: 24rpx;
color: #999;
}
}
}
}
.quantity-section {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 40rpx;
.quantity-label {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.quantity-control {
display: flex;
align-items: center;
.quantity-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 32rpx;
color: #333;
&.disabled {
opacity: 0.5;
}
}
.quantity-input {
width: 100rpx;
height: 60rpx;
text-align: center;
font-size: 28rpx;
margin: 0 20rpx;
background: #f5f5f5;
border-radius: 12rpx;
}
}
}
.popup-footer {
.confirm-btn {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #07c160, #10d673);
border-radius: 44rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
text {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
}
}
// 收货信息弹窗
.address-popup {
background: #fff;
border-radius: 32rpx 32rpx 0 0;
max-height: 85vh;
display: flex;
flex-direction: column;
.popup-header {
position: sticky;
top: 0;
background: #fff;
z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
padding: 40rpx 40rpx 30rpx;
border-bottom: 1rpx solid #f0f0f0;
.popup-title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 50%;
.close-icon {
font-size: 32rpx;
color: #666;
}
}
}
.form-section {
flex: 1;
overflow-y: auto;
padding: 40rpx 60rpx;
.form-item {
margin-bottom: 40rpx;
&:last-child {
margin-bottom: 0;
}
.form-label {
font-size: 28rpx;
color: #333;
font-weight: 600;
margin-bottom: 16rpx;
display: block;
}
.form-input {
width: 100%;
height: 88rpx;
padding: 0 24rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
box-sizing: border-box;
}
.region-selector {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.region-text {
color: #333;
}
.arrow-icon {
font-size: 40rpx;
color: #999;
font-weight: 300;
}
}
.form-textarea {
width: 100%;
min-height: 160rpx;
padding: 20rpx 24rpx;
background: #f5f5f5;
border-radius: 12rpx;
font-size: 28rpx;
color: #333;
line-height: 1.6;
box-sizing: border-box;
}
.input-placeholder {
color: #999;
}
}
.display-item {
margin-bottom: 40rpx;
padding: 30rpx;
background: #f8f8f8;
border-radius: 16rpx;
&:last-child {
margin-bottom: 0;
}
.display-label {
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
display: block;
}
.display-value {
font-size: 30rpx;
color: #333;
font-weight: 500;
line-height: 1.6;
}
}
}
.popup-footer {
position: sticky;
bottom: 0;
background: #fff;
z-index: 10;
padding: 30rpx 60rpx;
padding-bottom: calc(30rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f0f0;
.button-group {
display: flex;
gap: 20rpx;
.secondary-btn {
flex: 1;
height: 96rpx;
background: #fff;
border: 2rpx solid #07c160;
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
text {
font-size: 32rpx;
font-weight: 600;
color: #07c160;
}
&:active {
transform: scale(0.98);
opacity: 0.8;
}
}
.confirm-btn {
flex: 1;
height: 96rpx;
background: linear-gradient(135deg, #07c160, #10d673);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
text {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
}
.confirm-btn {
width: 100%;
height: 96rpx;
background: linear-gradient(135deg, #07c160, #10d673);
border-radius: 48rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(7, 193, 96, 0.3);
text {
font-size: 34rpx;
font-weight: 600;
color: #fff;
}
&:active {
transform: scale(0.98);
opacity: 0.9;
}
}
}
}
</style>