diff --git a/api/modules/order.ts b/api/modules/order.ts index 3fd6fe8..2b66a02 100644 --- a/api/modules/order.ts +++ b/api/modules/order.ts @@ -4,7 +4,11 @@ import type { IApiResponse } from '@/api/types' import type { ICreateOrderParams, IGooglePayVerifyParams, - ICreateOrderResponse + ICreateOrderResponse, + IOrderGoods, + ICoupon, + ICourseOrderCreateParams, + IOrderInitData } from '@/types/order' import type { IUserInfo } from '@/types/user' @@ -59,5 +63,96 @@ export const orderApi = { method: 'POST' }) return res + }, + + /** + * 初始化订单准备数据 + * @param params 初始化参数 + */ + async initPrepareOrder(params: { uid: number; productList: Array<{ productId: number; quantity: number }> }) { + const res = await mainClient.request>({ + url: 'common/buyOrder/initPrepareOrder', + method: 'POST', + data: params + }) + return res + }, + + /** + * 根据商品ID获取商品详情列表 + * @param productIds 商品ID字符串(逗号分隔) + */ + async getShopProductListByIds(productIds: string) { + const res = await mainClient.request>({ + url: 'book/buyOrder/getShopProductListByIds', + method: 'POST', + data: { productIds } + }) + return res + }, + + /** + * 获取VIP优惠金额 + * @param productList 商品列表 + */ + async getVipDiscountAmount(productList: Array<{ productId: number; quantity: number }>) { + const res = await mainClient.request>({ + url: 'book/buyOrder/getVipDiscountAmount', + method: 'POST', + data: { productList } + }) + return res + }, + + /** + * 获取地区优惠金额 + * @param productList 商品列表 + */ + async getDistrictAmount(productList: Array<{ productId: number; quantity: number }>) { + const res = await mainClient.request>({ + url: 'book/buyOrder/getDistrictAmount', + method: 'POST', + data: { productList } + }) + return res + }, + + /** + * 获取可用优惠券列表 + * @param shopProductInfos 商品信息字符串(格式:productId:price:quantity,productId:price:quantity) + */ + async getCouponListPayment(shopProductInfos: string) { + const res = await mainClient.request>({ + url: 'common/coupon/getCouponListPayment', + method: 'POST', + data: { shopProductInfos } + }) + return res + }, + + /** + * 更新购物车商品 + * @param data 商品数据 + */ + async updateCart(data: { productId: number; productAmount: number; price: number }) { + const res = await mainClient.request({ + url: 'book/ordercart/update', + method: 'POST', + data + }) + return res + }, + + /** + * 创建课程订单 + * @param data 订单数据 + */ + async placeCourseOrder(data: ICourseOrderCreateParams) { + const res = await mainClient.request>({ + url: 'book/buyOrder/placeOrder', + method: 'POST', + data + }) + return res } } diff --git a/api/modules/user.ts b/api/modules/user.ts index 0b1fbd8..bfb928d 100644 --- a/api/modules/user.ts +++ b/api/modules/user.ts @@ -39,11 +39,11 @@ export async function getVipInfo() { * @param current 当前页码 * @param limit 每页数量 */ -export async function getOrderList(current: number, limit: number, orderStatus: string) { +export async function getOrderList(page: number, limit: number, orderStatus: string) { const res = await mainClient.request }>>({ url: 'common/buyOrder/commonBuyOrderList', method: 'POST', - data: { current, limit, orderStatus, come: '10', userId: uni.getStorageSync('userInfo').id } + data: { page, limit, orderStatus, come: '10', userId: uni.getStorageSync('userInfo').id } }) return res } diff --git a/components/order/GoodsSelector.vue b/components/order/GoodsSelector.vue index 0f3a675..92c0c72 100644 --- a/components/order/GoodsSelector.vue +++ b/components/order/GoodsSelector.vue @@ -31,7 +31,7 @@ {{ parseFloat(item.vipPrice).toFixed(2) }} 天医币 - VIP到手价 + VIP优惠价 {{ parseFloat(item.price).toFixed(2) }} 天医币 diff --git a/components/order/Payment.vue b/components/order/Payment.vue new file mode 100644 index 0000000..e69de29 diff --git a/components/order/Price.vue b/components/order/Price.vue new file mode 100644 index 0000000..44f183b --- /dev/null +++ b/components/order/Price.vue @@ -0,0 +1,229 @@ + + + + + \ No newline at end of file diff --git a/locale/en.json b/locale/en.json index cd62f5d..5e6ce41 100644 --- a/locale/en.json +++ b/locale/en.json @@ -412,6 +412,66 @@ }, "order": { "selectFuduScheme": "Select Fudu Scheme", - "selectPurchaseScheme": "Select Purchase Scheme" + "selectPurchaseScheme": "Select Purchase Scheme", + "confirmTitle": "Confirm Order", + "goodsInfo": "Product Information", + "priceDetail": "Price Details", + "totalPrice": "Subtotal", + "coupon": "Coupon", + "points": "Points", + "vipDiscount": "VIP Exclusive Discount", + "activityDiscount": "Activity Discount", + "districtDiscount": "Regional Discount", + "actualPayment": "Total", + "paymentMethod": "Payment Method", + "virtualCoin": "Virtual Coin", + "balance": "Balance", + "recharge": "Recharge Now", + "remark": "Order Remark", + "remarkPlaceholder": "Optional: Leave a message", + "remarkTitle": "Order Remark", + "submit": "Pay Now", + "submitting": "Submitting...", + "total": "Total", + "noCoupon": "No coupons available", + "unavailable": "Unavailable", + "selectCoupon": "Please select a coupon", + "notUseCoupon": "Don't use coupon", + "reselect": "Reselect", + "selected": "Confirm", + "maxPoints": "Available Points ({max} pts)", + "pointsPlaceholder": "Enter points", + "allPoints": "Total Points", + "insufficientBalance": "Insufficient virtual coin balance", + "orderCreating": "Creating order", + "orderSuccess": "Purchase successful", + "orderFailed": "Failed, please try again", + "tooFrequent": "Too frequent, please wait", + "duplicateOrder": "You have a similar order recently. Continue?", + "duplicateConfirm": "Continue", + "duplicateCancel": "Cancel", + "customerService": "Customer Service", + "paymentTip": "1 Virtual Coin = 1 CNY", + "paymentTipTitle": "Notes", + "paymentTip1": "1. 1 Virtual Coin = 1 CNY", + "paymentTip2": "2. For questions, please call customer service", + "paymentTip3": "3. Non-mainland China users can pay by credit card. Simple and fast, recommended! Credit cards with Visa or MasterCard logos are accepted. Please send payment request to email", + "paymentTip3_1": "(click to copy) with course name, amount, registered name and phone number, or add WeChat customer service (", + "paymentTip3_2": ") (click to copy). We will send payment link within 24 hours.", + "ensureBalance": "Ensure sufficient virtual coin balance", + "vipLabel": "VIP Discount", + "activityLabel": "Activity Price", + "vipPriceLabel": "VIP Price", + "quantity": "Quantity", + "couponAmount": "¥", + "couponUseLevel": "Min. {level} CNY", + "couponExpiry": "Valid until", + "couponForever": "Permanent", + "couponReason": "Reason", + "couponUsage": "Usage", + "couponType0": "All Products", + "couponType1": "Specific Courses", + "couponType2": "Course Categories", + "couponCount": "{count} coupons" } } diff --git a/locale/zh-Hans.json b/locale/zh-Hans.json index cd690a3..20a81de 100644 --- a/locale/zh-Hans.json +++ b/locale/zh-Hans.json @@ -413,6 +413,66 @@ }, "order": { "selectFuduScheme": "选择复读方案", - "selectPurchaseScheme": "选择购买方案" + "selectPurchaseScheme": "选择购买方案", + "confirmTitle": "确认订单", + "goodsInfo": "商品信息", + "priceDetail": "价格明细", + "totalPrice": "商品总价", + "coupon": "优惠券", + "points": "积分", + "vipDiscount": "VIP专享立减", + "activityDiscount": "活动立减", + "districtDiscount": "地区优惠", + "actualPayment": "实付款", + "paymentMethod": "支付方式", + "virtualCoin": "天医币", + "balance": "余额", + "recharge": "立即充值", + "remark": "订单备注", + "remarkPlaceholder": "选填:给商家留言", + "remarkTitle": "订单备注", + "submit": "立即支付", + "submitting": "提交中...", + "total": "合计", + "noCoupon": "暂无可用优惠券", + "unavailable": "不可用", + "selectCoupon": "请选择优惠券", + "notUseCoupon": "不使用优惠券", + "reselect": "重新选择", + "selected": "选好了", + "maxPoints": "可用积分({max}分)", + "pointsPlaceholder": "请输入积分", + "allPoints": "全部积分", + "insufficientBalance": "天医币余额不足", + "orderCreating": "正在请求订单", + "orderSuccess": "购买成功", + "orderFailed": "失败,请重新下单", + "tooFrequent": "操作太频繁了,休息下吧", + "duplicateOrder": "您短时间内有一笔相同金额的订单,是否确定继续下单?", + "duplicateConfirm": "继续操作", + "duplicateCancel": "点错了", + "customerService": "客服", + "paymentTip": "1天医币 = 1元人民币", + "paymentTipTitle": "说明", + "paymentTip1": "1. 1天医币 = 1元人民币", + "paymentTip2": "2.若有疑问或意见请致电客服", + "paymentTip3": "3.非中国大陆用户可以信用卡支付。简单快捷,推荐使用!支付时使用的信用卡需要带有Visa或MasterCard的标识。请向邮箱", + "paymentTip3_1": "(点击复制)发送支付请求,内容需包含:拟购买的课程名称、支付金额、APP注册姓名及手机号码,或者加一路健康客服微信(", + "paymentTip3_2": ")(点击复制)联系我们,我们将在24小时内向您的邮箱或者微信发送支付链接,根据提示即可完成信用卡支付,无需兑换外币。", + "ensureBalance": "确保您的天医币足够支付", + "vipLabel": "VIP优惠", + "activityLabel": "活动价", + "vipPriceLabel": "VIP到手价", + "quantity": "数量", + "couponAmount": "¥", + "couponUseLevel": "满{level}元可用", + "couponExpiry": "有效期至", + "couponForever": "永久有效", + "couponReason": "不可用原因", + "couponUsage": "使用说明", + "couponType0": "全场通用", + "couponType1": "指定课程可用", + "couponType2": "指定课程品类可用", + "couponCount": "共 {count} 张" } } diff --git a/pages.json b/pages.json index 98b8be9..a563e23 100644 --- a/pages.json +++ b/pages.json @@ -152,10 +152,10 @@ "navigationBarTitleText": "%courseDetails.chapter%" } }, { - "path": "pages/course/order", + "path": "pages/order/confirmOrder", "style": { "navigationStyle": "custom", - "navigationBarTitleText": "%courseOrder.orderTitle%" + "navigationBarTitleText": "%order.confirmTitle%" } } ], diff --git a/pages/course/details/course.vue b/pages/course/details/course.vue index 6b7f211..daff58a 100644 --- a/pages/course/details/course.vue +++ b/pages/course/details/course.vue @@ -449,8 +449,6 @@ const closeGoodsSelector = () => { */ const confirmPurchase = () => { showProtocol.value = false - uni.showToast({ icon: 'none', title: '订单及支付功能开发中' }) - return false if (!selectedGoods.value) return showProtocol.value = false @@ -458,15 +456,16 @@ const confirmPurchase = () => { // 跳转到确认订单页 const orderData = { goods: [{ ...selectedGoods.value, productAmount: 1 }], - typeId: 0, - navTitle: courseDetail.value?.title, - title: courseDetail.value?.title, - isFudu: isFudu.value, - fuduId: isFudu.value ? fuduCatalogueId.value : undefined + // typeId: 0, + // navTitle: courseDetail.value?.title, + // title: courseDetail.value?.title, + // isFudu: isFudu.value, + // fuduId: isFudu.value ? fuduCatalogueId.value : undefined } uni.navigateTo({ - url: `/pages/course/order?data=${encodeURIComponent(JSON.stringify(orderData))}` + // url: `/pages/order/confirmOrder?data=${encodeURIComponent(JSON.stringify(orderData))}` + url: `/pages/order/confirmOrder?goods=${selectedGoods.value.productId}` }) } diff --git a/pages/order/confirmOrder.vue b/pages/order/confirmOrder.vue index e69de29..056eb52 100644 --- a/pages/order/confirmOrder.vue +++ b/pages/order/confirmOrder.vue @@ -0,0 +1,1419 @@ + + + + + diff --git a/static/icon/jifen.png b/static/icon/jifen.png new file mode 100644 index 0000000..5319e4d Binary files /dev/null and b/static/icon/jifen.png differ diff --git a/static/icon/pay_3.png b/static/icon/pay_3.png new file mode 100644 index 0000000..9a0b1b5 Binary files /dev/null and b/static/icon/pay_3.png differ diff --git a/style/tailwind.css b/style/tailwind.css index 057e99a..243f738 100644 --- a/style/tailwind.css +++ b/style/tailwind.css @@ -10,6 +10,7 @@ --color-red-500: oklch(63.7% 0.237 25.331); --color-white: #fff; --spacing: 0.25rem; + --font-weight-bold: 700; --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); --default-transition-duration: 150ms; --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); @@ -293,15 +294,40 @@ .bg-red-500 { background-color: var(--color-red-500); } + .p-0 { + padding: calc(var(--spacing) * 0); + } + .p-0\! { + padding: calc(var(--spacing) * 0) !important; + } .pt-\[40px\] { padding-top: 40px; } + .pb-0 { + padding-bottom: calc(var(--spacing) * 0); + } + .pb-0\! { + padding-bottom: calc(var(--spacing) * 0) !important; + } .text-center { text-align: center; } + .text-right { + text-align: right; + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } .text-\[\#000\] { color: #000; } + .text-\[\#7dc1f0\] { + color: #7dc1f0; + } + .text-\[\#f94f04\] { + color: #f94f04; + } .text-\[\#fff\] { color: #fff; } @@ -393,6 +419,10 @@ inherits: false; initial-value: solid; } +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} @property --tw-ordinal { syntax: "*"; inherits: false; @@ -585,6 +615,7 @@ --tw-skew-x: initial; --tw-skew-y: initial; --tw-border-style: solid; + --tw-font-weight: initial; --tw-ordinal: initial; --tw-slashed-zero: initial; --tw-numeric-figure: initial; diff --git a/style/ui.scss b/style/ui.scss index 6c8f50f..8975596 100644 --- a/style/ui.scss +++ b/style/ui.scss @@ -1,10 +1,10 @@ :root, page { /* 文字字号 */ - --wot-fs-title: 18px; // 标题字号/重要正文字号 - --wot-fs-content: 16px; // 普通正文 - --wot-fs-secondary: 14px; // 次要信息,注释/补充/正文 - --wot-fs-tertiary: 12px; // 次次要信息,注释/补充/正文 + --wot-fs-title: 36rpx; // 标题字号/重要正文字号 + --wot-fs-content: 28rpx; // 普通正文 + --wot-fs-secondary: 24rpx; // 次要信息,注释/补充/正文 + --wot-fs-tertiary: 20rpx; // 次次要信息,注释/补充/正文 // 导航栏 --wot-navbar-background: #fff; @@ -16,7 +16,7 @@ page { // --wot-tabbar-item-title-font-size: 16px; // cell - --wot-cell-title-fs: 16px; + --wot-cell-title-fs: 28rpx; // dropdown // --wot-drop-menu-selected-color: #5355C8; diff --git a/types/order.d.ts b/types/order.d.ts index 764c247..9dfe4b3 100644 --- a/types/order.d.ts +++ b/types/order.d.ts @@ -26,61 +26,127 @@ export interface IOrder { } /** - * 订单创建参数接口 + * 课程订单商品信息 */ -export interface ICreateOrderParams { - paymentMethod: '4' | '5' // 支付方式: 4-虚拟币, 5-Google Pay - orderMoney: number | string // 订单金额 - abroadBookId: number // 图书ID - orderType: 'abroadBook' // 订单类型 +export interface IOrderGoods { + productId: number + productName: string + productImages: string + price: number + vipPrice: number | null + activityPrice: number | null + isVipPrice: number // 是否有VIP优惠 0-否 1-是 + productAmount: number // 购买数量 + goodsType: string // 商品类型 "05" for course } /** - * Google Pay 验证参数接口 + * 优惠券实体信息 */ -export interface IGooglePayVerifyParams { - purchaseToken: string // 购买凭证 - orderSn: string // 订单号 - productId: string // 产品ID +export interface ICouponEntity { + id: number + couponName: string + couponAmount: number + useLevel: number // 满多少可用 + couponRange: number // 0-全场 1-指定课程 2-指定品类 + remark?: string } /** - * 订单创建响应接口 + * 优惠券信息 */ -export interface ICreateOrderResponse { - code: number - orderSn: string // 订单号 - msg?: string +export interface ICoupon { + id: number + couponId: number + canUse: number // 0-不可用 1-可用 + canUseReason?: string + effectType: number // 0-永久有效 + endTime?: string + couponEntity: ICouponEntity } /** - * Google Pay SKU 信息接口 + * 课程订单创建参数 */ -export interface IGooglePaySku { - productId: string - type: string - price: string - price_amount_micros: number - price_currency_code: string - title: string - description: string -} - -/** - * Google Pay 支付结果接口 - */ -export interface IGooglePayResult { - code: number - data?: Array<{ - original: { - purchaseToken: string - orderId: string - packageName: string - productId: string - purchaseTime: number - purchaseState: number - } +export interface ICourseOrderCreateParams { + 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 IOrderInitData { + user: { + id: number + jf: number // 积分 + peanutCoin: number // 天医币余额 + vip?: number // VIP状态 + } + is_course: boolean +} + +/** + * 订单路由参数 + */ +export interface IOrderRouteParams { + goods: IOrderGoods[] + typeId: number + sourceType?: string + navTitle?: string + title?: string +} + +/** + * 价格明细项 + */ +export interface IPriceBreakdownItem { + type: number // 1-商品总价 2-运费 3-优惠券 4-积分 5-活动立减 6-VIP立减 + text: string + imgUrl?: string + icon?: string +} + +/** + * 订单状态 + */ +export interface IOrderState { + orderData: IOrderRouteParams + goodsList: IOrderGoods[] + initData: IOrderInitData | null + 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 } /** diff --git a/utils/index.ts b/utils/index.ts index dbb5a0f..7490f19 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -18,7 +18,7 @@ export const onPageBack = () => { /** * 拨打电话 */ -export const makePhoneCall = (phoneNumber: string, title: string) => { +export const makePhoneCall = (phoneNumber: string, title: string = '') => { uni.showModal({ title: title, content: phoneNumber,