sunshibo 1 месяц назад
Родитель
Сommit
69ef193b9e

+ 17 - 4
api/dining.js

@@ -90,6 +90,17 @@ function getUserOwnedCouponListImpl(params) {
 }
 export const GetUserOwnedCouponList = getUserOwnedCouponListImpl;
 
+// 用户优惠券列表(GET /dining/coupon/life-discount-coupon/getUserCouponList,入参 tabType 0未使用/1即将过期/2已使用/3已过期)
+export const GetUserCouponList = (params) =>
+  api.get({
+    url: '/dining/coupon/life-discount-coupon/getUserCouponList',
+    params: {
+      tabType: params?.tabType ?? 0,
+      page: params?.page ?? 1,
+      size: params?.size ?? 20
+    }
+  });
+
 // 获取购物车(GET /store/order/cart/{tableId},建立 SSE 连接之后调用)
 export const GetOrderCart = (tableId) =>
   api.get({ url: `/store/order/cart/${encodeURIComponent(tableId)}` });
@@ -163,7 +174,7 @@ export const GetMyOrders = (params) =>
 export const GetOrderInfo = (orderId) =>
   api.get({ url: `/store/order/info/${encodeURIComponent(orderId)}` });
 
-// 订单支付(POST /payment/prePay,入参 orderNo 订单号、payType 支付类型 默认 wechatPayMininProgram、payer 支付用户 openid、price 订单金额、subject 订单标题;返回微信支付调起参数)
+// 订单支付(GET /payment/prePay,入参 orderNo、payType、payer、price、subject、storeId 门店ID;返回微信支付调起参数)
 export const PostOrderPay = (params) =>
   api.get({
     url: '/payment/prePay',
@@ -172,18 +183,20 @@ export const PostOrderPay = (params) =>
       payType: 'wechatPayMininProgram',
       payer: params.payer,
       price: params.price,
-      subject: params.subject ?? '订单支付'
+      subject: params.subject ?? '订单支付',
+      storeId: params.storeId
     }
   });
 
-// 根据第三方订单号查询支付结果(GET /payment/searchOrderByOutTradeNoPath,入参 payType、transactionId)
+// 根据第三方订单号查询支付结果(GET /payment/searchOrderByOutTradeNoPath,入参 payType、transactionId、storeId
 // 支付页调用时不显示全局 loading
 export const GetSearchOrderByOutTradeNoPath = (params) =>
   api.get({
     url: '/payment/searchOrderByOutTradeNoPath',
     params: {
       payType: params.payType ?? 'wechatPayMininProgram',
-      transactionId: params.transactionId
+      transactionId: params.transactionId,
+      storeId: params.storeId
     },
     loading: false
   });

+ 23 - 4
pages/checkout/index.vue

@@ -62,7 +62,7 @@
       <view class="card-content">
         <view class="info-item">
           <view class="info-item-label">菜品总价</view>
-          <view class="info-item-value">¥{{ formatPrice(orderInfo.totalAmount) }}</view>
+          <view class="info-item-value">¥{{ formatPrice(dishTotal) }}</view>
         </view>
         <view class="info-item">
           <view class="info-item-label">餐具费</view>
@@ -105,12 +105,14 @@ import * as diningApi from '@/api/dining.js';
 const orderId = ref('');
 const orderInfo = ref({
   orderNo: '',
+  storeId: '',
   tableId: '',
   tableNumber: '',
   diners: '',
   contactPhone: '',
   remark: '',
   totalAmount: 0,
+  dishTotal: null,
   utensilFee: 0,
   couponId: null,
   couponName: '',
@@ -123,6 +125,17 @@ const orderInfo = ref({
 
 const foodList = ref([]);
 
+// 菜品总价(不含餐具费):优先 API 的 dishTotal,否则 totalAmount - utensilFee
+const dishTotal = computed(() => {
+  const fromApi = orderInfo.value.dishTotal;
+  if (fromApi != null && fromApi !== '' && !Number.isNaN(Number(fromApi))) {
+    return Math.max(0, Number(fromApi));
+  }
+  const total = Number(orderInfo.value.totalAmount) || 0;
+  const utensil = Number(orderInfo.value.utensilFee) || 0;
+  return Math.max(0, total - utensil);
+});
+
 // 优惠券展示:满减券显示 nominalValue+元,折扣券显示 discountRate+折,否则显示 couponName 或 已使用优惠券
 const couponDisplayText = computed(() => {
   const o = orderInfo.value;
@@ -172,12 +185,15 @@ const fetchOrderDetail = async () => {
     const res = await diningApi.GetOrderInfo(id);
     const raw = res?.data ?? res ?? {};
     orderInfo.value.orderNo = raw?.orderNo ?? raw?.orderId ?? '';
+    orderInfo.value.storeId = raw?.storeId ?? raw?.store_id ?? '';
     orderInfo.value.tableId = raw?.tableId ?? raw?.tableNumber ?? '';
     orderInfo.value.tableNumber = raw?.tableNumber ?? raw?.tableNo ?? '';
     orderInfo.value.diners = raw?.dinerCount ?? '';
     orderInfo.value.contactPhone = raw?.contactPhone ?? raw?.phone ?? '';
     orderInfo.value.remark = raw?.remark ?? '';
-    orderInfo.value.totalAmount = Number(raw?.dishTotal ?? raw?.orderAmount ?? raw?.foodAmount ?? raw?.totalAmount ?? 0) || 0;
+    const total = Number(raw?.totalAmount ?? raw?.orderAmount ?? raw?.foodAmount ?? 0) || 0;
+    orderInfo.value.totalAmount = total;
+    orderInfo.value.dishTotal = raw?.dishTotal != null ? Number(raw.dishTotal) : null;
     orderInfo.value.utensilFee = Number(raw?.tablewareFee ?? raw?.tablewareAmount ?? 0) || 0;
     orderInfo.value.couponId = raw?.couponId ?? null;
     orderInfo.value.couponName = raw?.couponName ?? '';
@@ -219,11 +235,13 @@ const handleConfirmPay = async () => {
   const price = Math.round(priceAmount * 100);
   uni.showLoading({ title: '拉起支付...' });
   try {
+    const storeId = orderInfo.value.storeId || uni.getStorageSync('currentStoreId') || '';
     const res = await diningApi.PostOrderPay({
       orderNo,
       payer: openid,
       price,
-      subject: '订单支付'
+      subject: '订单支付',
+      storeId: storeId || undefined
     });
     uni.hideLoading();
     uni.requestPayment({
@@ -241,8 +259,9 @@ const handleConfirmPay = async () => {
         }
         const payType = 'wechatPayMininProgram';
         const transactionId = orderNo;
-        const oid = orderId.value || '';
+        const sid = orderInfo.value.storeId || uni.getStorageSync('currentStoreId') || '';
         const q = [`id=${encodeURIComponent(oid)}`, `payType=${encodeURIComponent(payType)}`, `transactionId=${encodeURIComponent(transactionId)}`];
+        if (sid) q.push(`storeId=${encodeURIComponent(sid)}`);
         setTimeout(() => go(`/pages/paymentSuccess/index?${q.join('&')}`), 1500);
       },
       fail: (err) => {

+ 76 - 67
pages/coupon/index.vue

@@ -22,9 +22,9 @@
             <view class="coupon-card__left">
               <view class="amount-wrapper">
                 <text class="amount-number">{{ coupon.amount }}</text>
-                <text class="amount-unit">元</text>
+                <text class="amount-unit">{{ coupon.amountUnit || '' }}</text>
               </view>
-              <text class="condition-text">满{{ coupon.minAmount }}可用</text>
+              <text class="condition-text">{{ coupon.conditionText || (coupon.minAmount > 0 ? '满' + coupon.minAmount + '可用' : '无门槛') }}</text>
             </view>
 
             <!-- 右侧信息区域 -->
@@ -35,25 +35,15 @@
                   使用规则
                   <text class="arrow">›</text>
                 </view>
-                <text class="coupon-expire">{{ coupon.expireDate }}到期</text>
+                <text class="coupon-expire">{{ coupon.expireDate ? coupon.expireDate + '到期' : '' }}</text>
               </view>
 
-              <!-- 操作按钮 -->
+              <!-- 状态展示 -->
               <view class="coupon-action">
-                <view v-if="coupon.status === 0" class="action-btn action-btn--use" hover-class="hover-active"
-                  @click="handleUseCoupon(coupon)">
-                  去使用
-                </view>
-                <view v-else-if="coupon.status === 1" class="action-btn action-btn--use" hover-class="hover-active"
-                  @click="handleUseCoupon(coupon)">
-                  去使用
-                </view>
-                <view v-else-if="coupon.status === 2" class="status-text status-text--used">
-                  已使用
-                </view>
-                <view v-else class="status-text status-text--expired">
-                  已过期
-                </view>
+                <view v-if="coupon.status === 0" class="status-text status-text--unused">未使用</view>
+                <view v-else-if="coupon.status === 1" class="status-text status-text--expiring">即将过期</view>
+                <view v-else-if="coupon.status === 2" class="status-text status-text--used">已使用</view>
+                <view v-else class="status-text status-text--expired">已过期</view>
               </view>
             </view>
           </view>
@@ -74,11 +64,12 @@
 </template>
 
 <script setup>
-import { onLoad } from "@dcloudio/uni-app";
+import { onShow } from "@dcloudio/uni-app";
 import { ref, computed } from "vue";
 import { go } from "@/utils/utils.js";
 import { getFileUrl } from "@/utils/file.js";
 import RulesModal from "./components/RulesModal.vue";
+import * as diningApi from "@/api/dining.js";
 
 // 标签页数据
 const tabs = ['未使用', '即将过期', '已使用', '已过期'];
@@ -95,56 +86,69 @@ const selectedCoupon = ref({
   description: '周一、周五不可用'
 });
 
-// 优惠券数据(模拟数据)
-const couponList = ref([
-  {
-    id: 1,
-    amount: 1000,
-    minAmount: 2000,
-    name: '美食必吃套餐专享券',
-    expireDate: '2024/07/28',
-    status: 0 // 0: 未使用, 1: 即将过期, 2: 已使用, 3: 已过期
-  },
-  {
-    id: 2,
-    amount: 38,
-    minAmount: 158,
-    name: '美食必吃套餐专享券',
-    expireDate: '2024/07/28',
-    status: 0
-  },
-  {
-    id: 3,
-    amount: 999,
-    minAmount: 2000,
-    name: '美食必吃套餐专享券',
-    expireDate: '2024/07/28',
-    status: 0
-  },
-  {
-    id: 4,
-    amount: 5,
-    minAmount: 158,
-    name: '美食必吃套餐专享券',
-    expireDate: '2024/07/28',
-    status: 0
+// 优惠券数据(接口返回)
+const couponList = ref([]);
+const loading = ref(false);
+
+// 规范化接口返回的优惠券项
+function normalizeCouponItem(raw) {
+  if (!raw || typeof raw !== 'object') return null;
+  const couponType = Number(raw.couponType) ?? 1;
+  const nominalValue = Number(raw.nominalValue ?? raw.amount ?? 0) || 0;
+  const discountRate = Number(raw.discountRate ?? 0) || 0;
+  const minAmount = Number(raw.minimumSpendingAmount ?? raw.minAmount ?? 0) || 0;
+  let amount = nominalValue;
+  let amountUnit = '元';
+  let conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
+  if (couponType === 2 && discountRate > 0) {
+    amount = discountRate;
+    amountUnit = '折';
+    conditionText = minAmount > 0 ? `满${minAmount}可用` : '无门槛';
   }
-]);
-
-// 根据当前标签页过滤优惠券
-const filteredCoupons = computed(() => {
-  return couponList.value.filter(coupon => {
-    if (currentTab.value === 0) return coupon.status === 0;
-    if (currentTab.value === 1) return coupon.status === 1;
-    if (currentTab.value === 2) return coupon.status === 2;
-    if (currentTab.value === 3) return coupon.status === 3;
-    return true;
-  });
-});
+  return {
+    id: raw.id ?? raw.userCouponId ?? raw.couponId ?? '',
+    amount,
+    amountUnit,
+    minAmount,
+    name: raw.name ?? raw.title ?? raw.couponName ?? '',
+    expireDate: raw.expirationTime ?? raw.endGetDate ?? raw.expireDate ?? '',
+    status: currentTab.value,
+    couponType,
+    nominalValue,
+    discountRate,
+    conditionText
+  };
+}
+
+// 根据当前标签页展示的优惠券(接口按 tabType 分页返回,直接使用)
+const filteredCoupons = computed(() => couponList.value);
 
 // 切换标签页
 const handleTabChange = (index) => {
   currentTab.value = index;
+  fetchCouponList();
+};
+
+// 拉取优惠券列表
+const fetchCouponList = async () => {
+  loading.value = true;
+  try {
+    const res = await diningApi.GetUserCouponList({
+      tabType: currentTab.value, // 0未使用 1即将过期 2已使用 3已过期
+      page: 1,
+      size: 20
+    });
+    const raw = res?.data ?? res ?? {};
+    const list = raw?.records ?? raw?.list ?? (Array.isArray(res) ? res : []);
+    const arr = Array.isArray(list) ? list : [];
+    couponList.value = arr.map(normalizeCouponItem).filter(Boolean);
+  } catch (err) {
+    console.error('获取优惠券列表失败:', err);
+    uni.showToast({ title: '加载失败', icon: 'none' });
+    couponList.value = [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 // 获取优惠券卡片样式类
@@ -171,9 +175,9 @@ const handleShowRules = (coupon) => {
   showRulesModal.value = true;
 };
 
-onLoad(() => {
-  // 页面加载时获取优惠券列表
-  // TODO: 调用API获取真实数据
+// 使用 onShow 拉取数据(onLoad 在 uni-app Vue3 组合式 API 下可能不触发,onShow 更可靠)
+onShow(() => {
+  fetchCouponList();
 });
 </script>
 
@@ -391,6 +395,11 @@ onLoad(() => {
           font-size: 24rpx;
           padding: 8rpx 16rpx;
 
+          &--unused,
+          &--expiring {
+            color: #999999;
+          }
+
           &--used {
             color: #999999;
           }

+ 4 - 1
pages/orderFood/index.vue

@@ -547,9 +547,12 @@ const handleOrderClick = () => {
   const fromApi = Number(cartData.value?.tablewareFee) || 0;
   const fromItems = getTablewareFeeFromCart();
   const utensilFee = fromApi > 0 ? fromApi : fromItems;
+  const totalAmount = displayTotalAmount.value;
+  const dishTotal = Math.max(0, Number(totalAmount) - Number(utensilFee));
   const cartPayload = {
     list: displayCartList.value,
-    totalAmount: displayTotalAmount.value,
+    totalAmount,
+    dishTotal,
     totalQuantity: displayTotalQuantity.value,
     tableId: tableId.value,
     tableNumber: tableNumber.value,

+ 5 - 1
pages/paymentSuccess/index.vue

@@ -20,11 +20,13 @@ import { GetSearchOrderByOutTradeNoPath, PostOrderComplete } from "@/api/dining.
 const orderId = ref('');
 const payType = ref('wechatPayMininProgram');
 const transactionId = ref('');
+const storeId = ref('');
 const loading = ref(true);
 
 async function queryPayResult() {
   const tid = transactionId.value;
   const ptype = payType.value;
+  const sid = storeId.value || uni.getStorageSync('currentStoreId') || '';
   const oid = orderId.value;
   if (!tid) {
     loading.value = false;
@@ -33,7 +35,8 @@ async function queryPayResult() {
   try {
     await GetSearchOrderByOutTradeNoPath({
       payType: ptype,
-      transactionId: tid
+      transactionId: tid,
+      storeId: sid || undefined
     });
     if (oid) {
       try {
@@ -52,6 +55,7 @@ onLoad((options) => {
   orderId.value = options?.id ?? '';
   payType.value = options?.payType ?? 'wechatPayMininProgram';
   transactionId.value = options?.transactionId ?? '';
+  storeId.value = options?.storeId ?? '';
   queryPayResult();
 });
 

+ 21 - 8
pages/placeOrder/index.vue

@@ -76,7 +76,7 @@
       <view class="card-content">
         <view class="info-item">
           <view class="info-item-label">菜品总价</view>
-          <view class="info-item-value">¥{{ formatPrice(orderInfo.totalAmount) }}</view>
+          <view class="info-item-value">¥{{ formatPrice(dishTotal) }}</view>
         </view>
         <view class="info-item">
           <view class="info-item-label">餐具费</view>
@@ -123,7 +123,7 @@
 
 <script setup>
 import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
-import { ref } from "vue";
+import { ref, computed } from "vue";
 import { go } from "@/utils/utils.js";
 import { getFileUrl } from "@/utils/file.js";
 import { useUserStore } from "@/store/user.js";
@@ -139,6 +139,7 @@ const orderInfo = ref({
   contactPhone: '',
   remark: '',
   totalAmount: 0,
+  dishTotal: null,
   utensilFee: 0,
   discountAmount: 0,
   payAmount: 0,
@@ -155,6 +156,17 @@ const selectedCouponDisplay = ref('');
 // 菜品列表(从购物车带过来)
 const foodList = ref([]);
 
+// 菜品总价(不含餐具费):优先缓存中的 dishTotal,否则 totalAmount - utensilFee
+const dishTotal = computed(() => {
+  const fromCache = orderInfo.value.dishTotal;
+  if (fromCache != null && fromCache !== '' && !Number.isNaN(Number(fromCache))) {
+    return Math.max(0, Number(fromCache));
+  }
+  const total = Number(orderInfo.value.totalAmount) || 0;
+  const utensil = Number(orderInfo.value.utensilFee) || 0;
+  return Math.max(0, total - utensil);
+});
+
 // 菜品图片:兼容 cuisineImage/image,逗号分隔取首图
 function getItemImage(item) {
   const raw = item?.cuisineImage ?? item?.image ?? item?.imageUrl ?? '';
@@ -215,7 +227,7 @@ const openCouponModal = async () => {
       selectedCouponId.value = first.id != null && first.id !== '' ? String(first.id) : null;
       orderInfo.value.couponId = selectedCouponId.value;
       selectedCouponDisplay.value = first.amountDisplay || (first.amount ? first.amount + '元' : '');
-      const total = Number(orderInfo.value.totalAmount) || 0;
+      const total = dishTotal.value;
       const couponType = Number(first.couponType) || 0;
       if (couponType === 2 && first.discountRate != null && total > 0) {
         const rate = Number(first.discountRate) || 0;
@@ -241,7 +253,7 @@ const handleCouponSelect = ({ coupon, selectedId }) => {
     orderInfo.value.discountAmount = 0;
   } else {
     selectedCouponDisplay.value = coupon.amountDisplay || (coupon.amount ? coupon.amount + '元' : '');
-    const total = Number(orderInfo.value.totalAmount) || 0;
+    const total = dishTotal.value;
     const couponType = Number(coupon.couponType) || 0;
     if (couponType === 2 && coupon.discountRate != null && total > 0) {
       // 折扣券:优惠金额 = 菜品总价 × (1 - 折数/10),如 5.5折 即 优惠 = 总价 × 0.45
@@ -255,12 +267,12 @@ const handleCouponSelect = ({ coupon, selectedId }) => {
   couponModalOpen.value = false;
 };
 
-// 根据总价、餐具费、优惠额计算应付金额
+// 根据菜品总价(不含餐具)、餐具费、优惠额计算应付金额
 const updatePayAmount = () => {
-  const total = Number(orderInfo.value.totalAmount) || 0;
+  const dish = dishTotal.value;
   const utensil = Number(orderInfo.value.utensilFee) || 0;
   const discount = Number(orderInfo.value.discountAmount) || 0;
-  orderInfo.value.payAmount = Math.max(0, total + utensil - discount);
+  orderInfo.value.payAmount = Math.max(0, dish + utensil - discount);
 };
 
 // 进入页面时调一次优惠券接口:有券则默认选第一张,无券则显示请选择
@@ -283,7 +295,7 @@ const fetchCouponsOnEnter = async () => {
     selectedCouponId.value = first.id != null && first.id !== '' ? String(first.id) : null;
     orderInfo.value.couponId = selectedCouponId.value;
     selectedCouponDisplay.value = first.amountDisplay || (first.amount ? first.amount + '元' : '');
-    const total = Number(orderInfo.value.totalAmount) || 0;
+    const total = dishTotal.value;
     const couponType = Number(first.couponType) || 0;
     if (couponType === 2 && first.discountRate != null && total > 0) {
       const rate = Number(first.discountRate) || 0;
@@ -395,6 +407,7 @@ onLoad((options) => {
       if (data.remark != null) orderInfo.value.remark = data.remark;
       const total = Number(data.totalAmount) || 0;
       orderInfo.value.totalAmount = total;
+      if (data.dishTotal != null && data.dishTotal !== '') orderInfo.value.dishTotal = Number(data.dishTotal) || 0;
       const fee = data.utensilFee ?? data.tablewareFee;
       if (fee != null && fee !== '') orderInfo.value.utensilFee = Number(fee) || 0;
       if (data.discountAmount != null) orderInfo.value.discountAmount = data.discountAmount;