修复:修复下单可多次点击支付按钮问题;允许积分支付小数;

This commit is contained in:
2025-12-22 17:52:02 +08:00
parent 55455fa4f2
commit cafb86cc9d
9 changed files with 283 additions and 132 deletions

View File

@@ -7,8 +7,8 @@ export const ENV = process.env.NODE_ENV || 'development';
*/ */
const BASE_URL_MAP = { const BASE_URL_MAP = {
development: { development: {
MAIN: 'http://192.168.110.100:9300/pb/', // 张川川 // MAIN: 'http://192.168.110.100:9300/pb/', // 张川川
// MAIN: 'https://global.nuttyreading.com/', // 线上 MAIN: 'https://global.nuttyreading.com/', // 线上
// PAYMENT: 'https://dev-pay.example.com', // 暂时用不到 // PAYMENT: 'https://dev-pay.example.com', // 暂时用不到
// CDN: 'https://cdn-dev.example.com', // 暂时用不到 // CDN: 'https://cdn-dev.example.com', // 暂时用不到
}, },

View File

@@ -2,8 +2,7 @@
import { mainClient } from '@/api/clients/main' import { mainClient } from '@/api/clients/main'
import type { IApiResponse } from '@/api/types' import type { IApiResponse } from '@/api/types'
import type { import type {
ICreateOrderParams, ICreateOrderParams,
IGooglePayVerifyParams,
ICreateOrderResponse, ICreateOrderResponse,
IOrderGoods, IOrderGoods,
ICoupon, ICoupon,

View File

@@ -67,7 +67,7 @@
<view class="points-input-box"> <view class="points-input-box">
<input <input
v-model="pointsDiscounted" v-model="pointsDiscounted"
type="number" type="digit"
clearable clearable
:placeholder="$t('order.pointsPlaceholder')" :placeholder="$t('order.pointsPlaceholder')"
class="text-right" class="text-right"
@@ -90,7 +90,7 @@
<text class="label">{{ $t('order.total') }}</text> <text class="label">{{ $t('order.total') }}</text>
<text class="amount">{{ finalAmount }} {{ t('global.coin') }}</text> <text class="amount">{{ finalAmount }} {{ t('global.coin') }}</text>
</view> </view>
<wd-button type="primary" @click="handleSubmit"> <wd-button type="primary" :loading="submitLoading" @click="handleSubmit">
{{ $t('order.submit') }} {{ $t('order.submit') }}
</wd-button> </wd-button>
</view> </view>
@@ -128,6 +128,7 @@ import { getUserInfo } from '@/api/modules/user'
import { useUserStore } from '@/stores/user' import { useUserStore } from '@/stores/user'
import { t } from '@/utils/i18n' import { t } from '@/utils/i18n'
import type { IGoods, IGoodsDiscountParams } from '@/types/order' import type { IGoods, IGoodsDiscountParams } from '@/types/order'
import type { IUserInfo } from '@/types/user'
import PayWay from '@/components/order/PayWay.vue' import PayWay from '@/components/order/PayWay.vue'
const userStore = useUserStore() const userStore = useUserStore()
@@ -135,14 +136,13 @@ const userStore = useUserStore()
// 使用页面传参 // 使用页面传参
interface Props { interface Props {
goodsList: IGoods[], goodsList: IGoods[],
userInfo: object, userInfo: IUserInfo,
allowPointPay?: boolean, allowPointPay?: boolean,
orderType?: string, orderType?: string,
backStep?: number // 购买完成后返回几层页面 backStep?: number // 购买完成后返回几层页面
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
goodsList: () => [], goodsList: () => [],
userInfo: () => ({}),
allowPointPay: true, allowPointPay: true,
orderType: 'order', orderType: 'order',
backStep: 1 backStep: 1
@@ -270,13 +270,24 @@ const calculatePromotionDiscounted = async () => {
const handlePointsInput = (value: any) => { const handlePointsInput = (value: any) => {
let val = String(value.detail.value) let val = String(value.detail.value)
// 允许数字字符,去掉小数点 // 允许数字字符小数点
val = val.replace(/[^0-9]/g, '') val = val.replace(/[^0-9.]/g, '')
if (val === '0' || val === '') { // 确保只有一个小数点
const dotIndex = val.indexOf('.')
if (dotIndex !== -1) {
// 限制小数点后最多两位
val = val.substring(0, dotIndex + 1) + val.substring(dotIndex + 1).replace(/\./g, '')
const decimalPart = val.substring(dotIndex + 1)
if (decimalPart.length > 2) {
val = val.substring(0, dotIndex + 1) + decimalPart.substring(0, 2)
}
}
if (val === '' || val === '.') {
pointsDiscounted.value = 0 pointsDiscounted.value = 0
} else { } else {
let numericValue = parseInt(val, 10) let numericValue = parseFloat(val)
if (numericValue < 0 || isNaN(numericValue)) { if (numericValue < 0 || isNaN(numericValue)) {
numericValue = 0 numericValue = 0
} }
@@ -316,7 +327,7 @@ const calculateFinalPrice = () => {
const orderAmountAfterDiscount = totalAmount.value - promotionDiscounted.value - vipDiscounted.value - couponAmount const orderAmountAfterDiscount = totalAmount.value - promotionDiscounted.value - vipDiscounted.value - couponAmount
pointsUsableMax.value = Math.min( pointsUsableMax.value = Math.min(
props?.userInfo?.jf || 0, props?.userInfo?.jf || 0,
Math.floor(props.allowPointPay ? orderAmountAfterDiscount : 0) props.allowPointPay ? orderAmountAfterDiscount : 0
) )
pointsDiscounted.value = pointsUsableMax.value pointsDiscounted.value = pointsUsableMax.value
@@ -362,28 +373,37 @@ const validateOrder = (): boolean => {
/** /**
* 提交订单 * 提交订单
*/ */
const handleSubmit = async () => { const submitLoading = ref<boolean>(false)
const handleSubmit = async () => {
// 验证订单 // 验证订单
if (!validateOrder()) return if (!validateOrder()) return
submitLoading.value = true
// 创建订单 此app用天医币支付创建订单成功即支付成功 try {
await createOrder() // 创建订单 此app用天医币支付创建订单成功即支付成功
await createOrder()
// 重新获取用户信息更新store和本地缓存 // 重新获取用户信息更新store和本地缓存
const res = await getUserInfo() const res = await getUserInfo()
userStore.setUserInfo(res.result) userStore.setUserInfo(res.result)
uni.showToast({ uni.showToast({
title: t('order.orderSuccess'), title: t('order.orderSuccess'),
icon: 'success' icon: 'success'
})
// 返回上一页
setTimeout(() => {
uni.navigateBack({
delta: props.backStep
}) })
}, 500)
// 返回上一页
setTimeout(() => {
uni.navigateBack({
delta: props.backStep
})
}, 500)
} catch (error) {
console.error('提交订单失败:', error)
} finally {
// 无论成功还是失败都要重置loading状态
submitLoading.value = false
}
} }
/** /**

View File

@@ -1,5 +1,11 @@
<template> <template>
<view class="home-page"> <scroll-view
class="home-page"
scroll-y
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
>
<!-- 顶部背景区域 --> <!-- 顶部背景区域 -->
<view class="home-bg" :style="{ paddingTop: getNotchHeight() + 'px' }"> <view class="home-bg" :style="{ paddingTop: getNotchHeight() + 'px' }">
<wd-search <wd-search
@@ -244,7 +250,7 @@
</Skeleton> </Skeleton>
</view> </view>
</view> </view>
</view> </scroll-view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -427,12 +433,38 @@ const getPrompt = () => {
} }
} }
// 下拉刷新状态
const isRefreshing = ref(false)
/**
* 处理下拉刷新
*/
const handleRefresh = async () => {
isRefreshing.value = true
try {
// 刷新所有数据
await Promise.all([
myBookSkeleton.value?.reload(),
recommendBooksSkeleton.value?.reload(),
categoryLevel1LabelSkeleton.value?.reload()
])
} catch (error) {
console.error('刷新数据失败:', error)
} finally {
// 延迟关闭刷新状态,避免闪烁
setTimeout(() => {
isRefreshing.value = false
}, 500)
}
}
/** /**
* 页面显示 * 页面显示
*/ */
onShow(() => { onShow(() => {
// 刷新数据 // 刷新数据
myBookSkeleton.value?.reload() myBookSkeleton.value?.reload()
categoryLevel1LabelSkeleton.value.reload()
}) })
</script> </script>
@@ -469,6 +501,7 @@ onShow(() => {
.content-wrapper { .content-wrapper {
padding-bottom: 40rpx; padding-bottom: 40rpx;
height: calc(100vh - 240px);
} }
.mine-block { .mine-block {

View File

@@ -1,5 +1,11 @@
<template> <template>
<view class="course-home-page"> <scroll-view
class="course-home-page"
scroll-y
refresher-enabled
:refresher-triggered="isRefreshing"
@refresherrefresh="handleRefresh"
>
<!-- 头部区域 --> <!-- 头部区域 -->
<view class="home-bg" :style="{ paddingTop: getNotchHeight() + 'px' }"> <view class="home-bg" :style="{ paddingTop: getNotchHeight() + 'px' }">
<wd-search <wd-search
@@ -215,7 +221,7 @@
</template> </template>
</Skeleton> </Skeleton>
</view> </view>
</view> </scroll-view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -234,6 +240,9 @@ const userStore = useUserStore()
// 系统信息 // 系统信息
const scrollTop = ref<number>(0) // 滚动位置 const scrollTop = ref<number>(0) // 滚动位置
// 下拉刷新状态
const isRefreshing = ref(false)
/** /**
* 处理搜索点击 * 处理搜索点击
*/ */
@@ -451,6 +460,31 @@ const requestAll = async () => {
} }
getTryListenList() getTryListenList()
getNewsList() getNewsList()
// 刷新分类数据
if (selectedFirstLevel.value === '医学') {
medicineMenuSkeletonRef.value?.reload()
} else {
menuSkeletonRef.value?.reload()
}
}
/**
* 处理下拉刷新
*/
const handleRefresh = async () => {
isRefreshing.value = true
try {
// 刷新所有数据
await requestAll()
} catch (error) {
console.error('刷新数据失败:', error)
} finally {
// 延迟关闭刷新状态,避免闪烁
setTimeout(() => {
isRefreshing.value = false
}, 500)
}
} }
/** /**
@@ -526,7 +560,7 @@ $text-placeholder: #999999;
$border-color: #eeeeee; $border-color: #eeeeee;
.course-home-page { .course-home-page {
min-height: 100vh; height: 100vh;
background-color: $bg-color; background-color: $bg-color;
font-size: 28upx; font-size: 28upx;
} }

View File

@@ -4,7 +4,7 @@
<nav-bar :title="$t('order.confirmTitle')" /> <nav-bar :title="$t('order.confirmTitle')" />
<!-- 确认订单组件 --> <!-- 确认订单组件 -->
<Confirm :goodsList="goodsList" :userInfo="userInfo" :orderType="orderType"> <Confirm :goodsList="goodsList" :userInfo="userInfo" :orderType="orderType" :allow-point-pay="allowPointPay">
<template #goodsList> <template #goodsList>
<!-- 商品列表内容 --> <!-- 商品列表内容 -->
<view <view
@@ -86,12 +86,15 @@ const isRelearn = ref<boolean>(false)
const orderType = computed(() => { const orderType = computed(() => {
return isRelearn.value ? 'relearn' : 'order' return isRelearn.value ? 'relearn' : 'order'
}) })
// 是否允许积分支付
const allowPointPay = ref<boolean>(false)
/** /**
* 页面加载 * 页面加载
*/ */
onLoad(async (options: any) => { onLoad(async (options: any) => {
try { try {
allowPointPay.value = options.allowPointPay !== '0'
if (options.isRelearn == 1) { if (options.isRelearn == 1) {
uni.$on('selectedGoods', async (data: IOrderGoods) => { uni.$on('selectedGoods', async (data: IOrderGoods) => {
// 获取用户信息 // 获取用户信息

View File

@@ -1,83 +1,91 @@
<template> <template>
<view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }" v-if="tokenState"> <scroll-view
<!-- 设置图标 --> scroll-y
<view class="settings-icon" :style="{ top: getNotchHeight() + 30 + 'px' }" @click="goSettings"> refresher-enabled
<wd-icon name="setting1" size="24px" color="#666" /> :refresher-triggered="isRefreshing"
<text>{{ $t('user.settings') }}</text> @refresherrefresh="handleRefresh"
</view> v-if="tokenState"
>
<view class="user-page" :style="{ paddingTop: getNotchHeight() + 30 + 'px' }" v-if="tokenState">
<!-- 设置图标 -->
<view class="settings-icon" :style="{ top: getNotchHeight() + 30 + 'px' }" @click="goSettings">
<wd-icon name="setting1" size="24px" color="#666" />
<text>{{ $t('user.settings') }}</text>
</view>
<!-- 用户信息区域 --> <!-- 用户信息区域 -->
<view class="user-info-section"> <view class="user-info-section">
<view class="user-info"> <view class="user-info">
<image :src="userInfo.avatar || defaultAvatar" class="avatar" @click="goProfile" /> <image :src="userInfo.avatar || defaultAvatar" class="avatar" @click="goProfile" />
<view class="user-details"> <view class="user-details">
<text class="nickname">{{ userInfo.nickname || $t('user.notSet') }}</text> <text class="nickname">{{ userInfo.nickname || $t('user.notSet') }}</text>
<text v-if="userInfo.email" class="email">{{ userInfo.email }}</text> <text v-if="userInfo.email" class="email">{{ userInfo.email }}</text>
</view>
</view> </view>
</view> </view>
</view>
<!-- VIP订阅卡片 --> <!-- VIP订阅卡片 -->
<view class="vip-card-section"> <view class="vip-card-section">
<view class="vip-card"> <view class="vip-card">
<view class="vip-card-title">{{ $t('user.vip') }}</view> <view class="vip-card-title">{{ $t('user.vip') }}</view>
<view class="vip-card-content"> <view class="vip-card-content">
<view class="vip-item-list"> <view class="vip-item-list">
<view v-if="vipInfo?.length > 0" v-for="vip in vipInfo"> <view v-if="vipInfo?.length > 0" v-for="vip in vipInfo">
{{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view> {{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view>
<view v-else>办理课程VIP畅享更多权益</view> <view v-else>办理课程VIP畅享更多权益</view>
</view>
<wd-button v-if="vipInfo?.length > 0" plain type="primary" size="small"
@click="goCourseVipSub">{{ $t('vip.renewal') }}</wd-button>
<wd-button v-else plain type="primary" size="small"
@click="goCourseVipSub">{{ $t('vip.openVip') }}</wd-button>
</view> </view>
<wd-button v-if="vipInfo?.length > 0" plain type="primary" size="small" <view class="vip-card-content">
@click="goCourseVipSub">{{ $t('vip.renewal') }}</wd-button> <view class="vip-item-list">
<wd-button v-else plain type="primary" size="small" <view v-if="vipInfoEbook?.length > 0" v-for="vip in vipInfoEbook">
@click="goCourseVipSub">{{ $t('vip.openVip') }}</wd-button> 电子书VIP{{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view>
</view> <view v-else>办理电子书VIP畅享更多权益</view>
<view class="vip-card-content"> </view>
<view class="vip-item-list"> <wd-button v-if="!vipInfoEbook?.length" plain type="primary" size="small"
<view v-if="vipInfoEbook?.length > 0" v-for="vip in vipInfoEbook"> @click="goSubscribe">{{ $t('vip.openVip') }}</wd-button>
电子书VIP{{ vipTypeDict[vip.type] }}{{ parseTime(vip.endTime, '{y}-{m}-{d}') }} 截止</view>
<view v-else>办理电子书VIP畅享更多权益</view>
</view> </view>
<wd-button v-if="!vipInfoEbook?.length" plain type="primary" size="small"
@click="goSubscribe">{{ $t('vip.openVip') }}</wd-button>
</view> </view>
</view> </view>
</view>
<!-- 我的资产 --> <!-- 我的资产 -->
<view class="assets-card-section wallet-section"> <view class="assets-card-section wallet-section">
<view class="assets-card wallet_l"> <view class="assets-card wallet_l">
<view class="assets"> <view class="assets">
<view @click="goVirtualList"> <view @click="goVirtualList">
<view class="assets_row">{{ t('global.coin') }}</view> <view class="assets_row">{{ t('global.coin') }}</view>
<view>{{userInfo.peanutCoin ?? 1}}</view> <view>{{userInfo.peanutCoin ?? 1}}</view>
</view>
<view @click="goPointsList">
<view class="assets_row">积分</view>
<view>{{userInfo.jf ?? 1}}</view>
</view>
<!-- <view>
<view class="assets_row">优惠卷</view>
<view>0</view>
</view> -->
</view> </view>
<view @click="goPointsList"> <view class="chong_btn" @click="goRecharge"> </view>
<view class="assets_row">积分</view> <!-- <text class="wallet_title">{{$t('my.coin')}}<uni-icons type="help" size="19" color="#666"></uni-icons></text>
<view>{{userInfo.jf ?? 1}}</view> <text class="wallet_count">{{userMes.peanutCoin}}</text> -->
</view>
<!-- <view>
<view class="assets_row">优惠卷</view>
<view>0</view>
</view> -->
</view> </view>
<view class="chong_btn" @click="goRecharge"> </view> </view>
<!-- <text class="wallet_title">{{$t('my.coin')}}<uni-icons type="help" size="19" color="#666"></uni-icons></text>
<text class="wallet_count">{{userMes.peanutCoin}}</text> --> <!-- 功能菜单列表 -->
<view class="menu-section">
<wd-cell-group border class="menu-list">
<wd-cell v-for="item in menuItems" :key="item.id" :title="item.name" :label="item.desc" is-link
@click="handleMenuClick(item)">
<text v-if="item.hufenState" class="menu-list-hufen">{{hufenData?.total ?? 0}}<text
style="margin-left: 6rpx;">湖分</text></text>
</wd-cell>
</wd-cell-group>
</view> </view>
</view> </view>
</scroll-view>
<!-- 功能菜单列表 -->
<view class="menu-section">
<wd-cell-group border class="menu-list">
<wd-cell v-for="item in menuItems" :key="item.id" :title="item.name" :label="item.desc" is-link
@click="handleMenuClick(item)">
<text v-if="item.hufenState" class="menu-list-hufen">{{hufenData?.total ?? 0}}<text
style="margin-left: 6rpx;">湖分</text></text>
</wd-cell>
</wd-cell-group>
</view>
</view>
<visitor v-else></visitor> <visitor v-else></visitor>
</template> </template>
@@ -166,6 +174,9 @@
// 湖分 // 湖分
const hufenData = ref() const hufenData = ref()
const tokenState = ref(false) const tokenState = ref(false)
// 下拉刷新状态
const isRefreshing = ref(false)
/** /**
* 获取平台信息 * 获取平台信息
@@ -186,6 +197,28 @@
} }
} }
/**
* 处理下拉刷新
*/
const handleRefresh = async () => {
isRefreshing.value = true
try {
// 刷新所有数据
await Promise.all([
getData(),
getHufen()
])
} catch (error) {
console.error('刷新数据失败:', error)
} finally {
// 延迟关闭刷新状态,避免闪烁
setTimeout(() => {
isRefreshing.value = false
}, 500)
}
}
/** /**
* 获取用户湖分 * 获取用户湖分
*/ */
@@ -297,6 +330,7 @@
.user-page { .user-page {
min-height: calc(100vh - 50px); min-height: calc(100vh - 50px);
background-color: #f7faf9; background-color: #f7faf9;
position: relative;
} }
.settings-icon { .settings-icon {

View File

@@ -212,27 +212,15 @@
max-width: 96rem; max-width: 96rem;
} }
} }
.mt-2 {
margin-top: calc(var(--spacing) * 2);
}
.mt-2\! { .mt-2\! {
margin-top: calc(var(--spacing) * 2) !important; margin-top: calc(var(--spacing) * 2) !important;
} }
.mt-5 {
margin-top: calc(var(--spacing) * 5);
}
.mt-5\! { .mt-5\! {
margin-top: calc(var(--spacing) * 5) !important; margin-top: calc(var(--spacing) * 5) !important;
} }
.mt-20 {
margin-top: calc(var(--spacing) * 20);
}
.mt-\[20rpx\]\! { .mt-\[20rpx\]\! {
margin-top: 20rpx !important; margin-top: 20rpx !important;
} }
.mb-2 {
margin-bottom: calc(var(--spacing) * 2);
}
.mb-2\! { .mb-2\! {
margin-bottom: calc(var(--spacing) * 2) !important; margin-bottom: calc(var(--spacing) * 2) !important;
} }
@@ -242,15 +230,9 @@
.mb-\[20rpx\]\! { .mb-\[20rpx\]\! {
margin-bottom: 20rpx !important; margin-bottom: 20rpx !important;
} }
.ml-1 {
margin-left: calc(var(--spacing) * 1);
}
.ml-1\! { .ml-1\! {
margin-left: calc(var(--spacing) * 1) !important; margin-left: calc(var(--spacing) * 1) !important;
} }
.ml-2 {
margin-left: calc(var(--spacing) * 2);
}
.ml-2\.5\! { .ml-2\.5\! {
margin-left: calc(var(--spacing) * 2.5) !important; margin-left: calc(var(--spacing) * 2.5) !important;
} }
@@ -290,9 +272,6 @@
.flex-shrink { .flex-shrink {
flex-shrink: 1; flex-shrink: 1;
} }
.border-collapse {
border-collapse: collapse;
}
.transform { .transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,); transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
} }
@@ -312,9 +291,6 @@
.bg-white { .bg-white {
background-color: var(--color-white); background-color: var(--color-white);
} }
.p-0 {
padding: calc(var(--spacing) * 0);
}
.p-0\! { .p-0\! {
padding: calc(var(--spacing) * 0) !important; padding: calc(var(--spacing) * 0) !important;
} }
@@ -339,9 +315,6 @@
.pt-\[10rpx\] { .pt-\[10rpx\] {
padding-top: 10rpx; padding-top: 10rpx;
} }
.pb-0 {
padding-bottom: calc(var(--spacing) * 0);
}
.pb-0\! { .pb-0\! {
padding-bottom: calc(var(--spacing) * 0) !important; padding-bottom: calc(var(--spacing) * 0) !important;
} }
@@ -392,9 +365,6 @@
--tw-ordinal: ordinal; --tw-ordinal: ordinal;
font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,); font-variant-numeric: var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,);
} }
.underline {
text-decoration-line: underline;
}
.shadow-\[0_4rpx_12rpx_rgba\(0\,0\,0\,0\.05\)\] { .shadow-\[0_4rpx_12rpx_rgba\(0\,0\,0\,0\.05\)\] {
--tw-shadow: 0 4rpx 12rpx var(--tw-shadow-color, rgba(0,0,0,0.05)); --tw-shadow: 0 4rpx 12rpx var(--tw-shadow-color, rgba(0,0,0,0.05));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);

58
types/order.d.ts vendored
View File

@@ -160,3 +160,61 @@ export interface IOrderDetail {
remark?: string remark?: string
[key: string]: any [key: string]: any
} }
/**
* 创建订单参数
*/
export interface ICreateOrderParams {
buyType: number // 0-商品页直接下单 1-购物车结算
userId: number
paymentMethod: number // 4-天医币
orderMoney: number // 订单金额
realMoney: number // 实收金额
jfDeduction: number // 积分抵扣
couponId?: number // 优惠券ID
couponName?: string // 优惠券名称
vipDiscountAmount: number // VIP折扣金额
districtMoney: number // 地区优惠金额
remark?: string // 备注
productList: Array<{
productId: number
quantity: number
}>
orderType: string // "order"
addressId: number // 0 for course products
appName: string // "wumen"
come: number // 2
}
/**
* 创建订单响应
*/
export interface ICreateOrderResponse {
orderSn: string
money: number
[key: string]: any
}
/**
* 订单初始化数据
*/
export interface IOrderInitData {
goodsList: IOrderGoods[]
totalPrice: number
vipPrice: number
districtAmount: number
actualPayment: number
jfNumber: number
jfNumberMax: number
jfNumberShow: string
couponList: ICoupon[]
selectedCoupon: ICoupon | null
showCouponPopup: boolean
remark: string
showRemarkPopup: boolean
payType: number
loading: boolean
submitting: boolean
buyingFlag: boolean
[key: string]: any
}