فهرست منبع

feat: add vendor order review page and entry

iwt 1 ماه پیش
والد
کامیت
6164350648

+ 6 - 0
cfc-frontend/pages.json

@@ -634,6 +634,12 @@
             "navigationBarTitleText": "订单管理"
             "navigationBarTitleText": "订单管理"
           }
           }
         },
         },
+        {
+          "path": "order-review/order-review",
+          "style": {
+            "navigationBarTitleText": "审核订单"
+          }
+        },
         {
         {
           "path": "product-edit/product-edit",
           "path": "product-edit/product-edit",
           "style": {
           "style": {

+ 515 - 0
cfc-frontend/pages/vendor/order-review/order-review.vue

@@ -0,0 +1,515 @@
+<template>
+  <view class="container">
+    <view v-if="loading" class="loading-wrap">
+      <text class="loading-text">加载中...</text>
+    </view>
+    <scroll-view v-else scroll-y class="content">
+      <!-- 订单头 -->
+      <view class="order-header-section">
+        <view class="order-no-row">
+          <text class="order-no">订单号:{{ order.orderNo }}</text>
+          <text class="status-tag">{{ statusLabel(order.status) }}</text>
+        </view>
+        <text class="order-hint">订单尚未支付,可进行审核修改</text>
+      </view>
+
+      <!-- 商品信息 -->
+      <view class="section">
+        <text class="section-title">商品信息</text>
+        <view class="product-card">
+          <image class="product-cover" :src="order.coverImage || ''" mode="aspectFill" />
+          <view class="product-info-wrap">
+            <text class="product-name">{{ order.productName }}</text>
+            <text class="product-qty">x{{ order.quantity || 1 }}</text>
+            <text class="product-price">¥{{ formatPrice(order.unitPrice) }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 修改价格 -->
+      <view class="section">
+        <text class="section-title">修改价格</text>
+        <view class="edit-row" @click="showPriceInput">
+          <text class="edit-label">当前单价</text>
+          <text class="edit-value">{{ formatPrice(order.unitPrice) }}</text>
+          <text class="edit-hint">点击修改</text>
+        </view>
+        <view v-if="priceInput" class="price-input-row">
+          <text class="input-label">新单价(元)</text>
+          <input class="price-input" type="digit" v-model="newUnitPrice" placeholder="输入新单价" />
+          <text class="price-preview">新总价:¥{{ formatPrice(newUnitPrice * (order.quantity || 1)) }}</text>
+        </view>
+      </view>
+
+      <!-- 修改配送方式 -->
+      <view class="section">
+        <text class="section-title">修改配送方式</text>
+        <view class="edit-row" @click="showDeliveryInput">
+          <text class="edit-label">当前配送方式</text>
+          <text class="edit-value">{{ deliveryLabel(order.deliveryMethod) }}</text>
+          <text class="edit-hint">点击修改</text>
+        </view>
+        <view v-if="deliveryInput" class="delivery-options">
+          <view class="delivery-option" :class="newDeliveryMethod === 1 ? 'selected' : ''" @click="newDeliveryMethod = 1">
+            <text>快递配送</text>
+          </view>
+          <view class="delivery-option" :class="newDeliveryMethod === 2 ? 'selected' : ''" @click="newDeliveryMethod = 2">
+            <text>自提</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 添加优惠券 -->
+      <view class="section">
+        <text class="section-title">添加优惠券</text>
+        <view v-if="!order.couponId" class="edit-row" @click="showCouponInput">
+          <text class="edit-label">优惠券</text>
+          <text class="edit-value">未使用</text>
+          <text class="edit-hint">点击添加</text>
+        </view>
+        <view v-else class="coupon-info">
+          <text class="coupon-tag">已添加优惠券</text>
+        </view>
+        <view v-if="couponInput" class="coupon-input-row">
+          <text class="input-label">优惠券金额(元)</text>
+          <input class="coupon-input" type="digit" v-model="couponValue" placeholder="输入优惠券金额" />
+        </view>
+      </view>
+
+      <!-- 添加赠品 -->
+      <view class="section">
+        <text class="section-title">添加赠品</text>
+        <view class="edit-row" @click="showGiftInput">
+          <text class="edit-label">当前赠品</text>
+          <text class="edit-value">{{ giftCount }} 件</text>
+          <text class="edit-hint">点击添加</text>
+        </view>
+        <view v-if="giftInput" class="gift-input-row">
+          <input class="gift-input" type="text" v-model="giftProductName" placeholder="赠品名称" />
+          <input class="gift-input" type="text" v-model="giftProductImage" placeholder="赠品图片URL(选填)" />
+          <view class="gift-add-btn" @click="addGift">+ 添加</view>
+        </view>
+        <view v-if="giftList.length > 0" class="gift-list">
+          <view v-for="gift in giftList" :key="gift.id" class="gift-item">
+            <image class="gift-img" :src="gift.giftProductImage || ''" mode="aspectFill" />
+            <text class="gift-name">{{ gift.giftProductName }}</text>
+            <text class="gift-qty">x{{ gift.quantity }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 修改备注 -->
+      <view class="section">
+        <text class="section-title">修改备注</text>
+        <textarea class="remark-textarea" v-model="remarkInput" placeholder="输入修改原因或备注(选填)" maxlength="200" />
+      </view>
+
+      <!-- 修改历史 -->
+      <view v-if="order.remark" class="section">
+        <text class="section-title">修改记录</text>
+        <text class="remark-history">{{ order.remark }}</text>
+      </view>
+    </scroll-view>
+
+    <!-- 底部操作栏 -->
+    <view class="bottom-bar">
+      <button class="btn-save" @click="onSave">保存修改</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { productOrderDetail, productOrderReviewUpdate } from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      order: {},
+      loading: false,
+      orderNo: '',
+      priceInput: false,
+      newUnitPrice: '',
+      deliveryInput: false,
+      newDeliveryMethod: null,
+      couponInput: false,
+      couponValue: '',
+      giftInput: false,
+      giftList: [],
+      giftProductName: '',
+      giftProductImage: '',
+      remarkInput: ''
+    }
+  },
+  onLoad(options) {
+    if (options.orderNo) {
+      this.orderNo = options.orderNo
+      this.loadDetail()
+    }
+  },
+  methods: {
+    loadDetail() {
+      this.loading = true
+      productOrderDetail({ orderNo: this.orderNo }).then(res => {
+        this.loading = false
+        if (res.code === 200 && res.data) {
+          this.order = res.data
+          var self = this
+          // 恢复已有赠品列表
+          this.$nextTick(function () {
+            // 后端目前不返回 giftList,前端从 remark 解析或初始为空
+          })
+        } else {
+          uni.showToast({ title: res.message || '加载失败', icon: 'none' })
+        }
+      }).catch(function () {
+        this.loading = false
+      })
+    },
+    showPriceInput() {
+      this.priceInput = true
+      this.newUnitPrice = String(this.order.unitPrice ? Math.round(this.order.unitPrice / 100) : '')
+    },
+    showDeliveryInput() {
+      this.deliveryInput = true
+      this.newDeliveryMethod = this.order.deliveryMethod
+    },
+    showCouponInput() {
+      this.couponInput = true
+      this.couponValue = ''
+    },
+    showGiftInput() {
+      this.giftInput = true
+      this.giftProductName = ''
+      this.giftProductImage = ''
+    },
+    addGift() {
+      if (!this.giftProductName) {
+        uni.showToast({ title: '请填写赠品名称', icon: 'none' })
+        return
+      }
+      this.giftList.push({
+        id: Date.now(),
+        giftType: 'product',
+        giftProductName: this.giftProductName,
+        giftProductImage: this.giftProductImage,
+        quantity: 1
+      })
+      this.giftProductName = ''
+      this.giftProductImage = ''
+    },
+    onSave() {
+      var params = { orderNo: this.orderNo }
+
+      if (this.priceInput && this.newUnitPrice) {
+        params.unitPrice = parseInt(this.newUnitPrice) * 100
+      }
+
+      if (this.deliveryInput && this.newDeliveryMethod != null) {
+        params.deliveryMethod = this.newDeliveryMethod
+      }
+
+      if (this.couponInput && this.couponValue && parseInt(this.couponValue) > 0) {
+        params.couponType = 'ONE_TIME'
+        params.couponValue = parseInt(this.couponValue) * 100
+      }
+
+      if (this.giftList.length > 0) {
+        params.giftItems = this.giftList
+      }
+
+      if (this.remarkInput) {
+        params.remark = this.remarkInput
+      }
+
+      if (!params.unitPrice && !params.deliveryMethod && !params.couponValue && !params.giftItems) {
+        uni.showToast({ title: '请至少修改一项', icon: 'none' })
+        return
+      }
+
+      uni.showLoading({ title: '保存中...' })
+      productOrderReviewUpdate(params).then(function (res) {
+        uni.hideLoading()
+        if (res.code === 200) {
+          uni.showToast({ title: '修改已保存', icon: 'success' })
+          setTimeout(function () {
+            uni.navigateBack()
+          }, 1500)
+        } else {
+          uni.showToast({ title: res.message || '保存失败', icon: 'none' })
+        }
+      }).catch(function () {
+        uni.hideLoading()
+        uni.showToast({ title: '网络错误', icon: 'none' })
+      })
+    },
+    statusLabel(status) {
+      var map = { pending: '待支付', paid: '已支付', completed: '已完成', cancelled: '已取消' }
+      return map[status] || status
+    },
+    deliveryLabel(method) {
+      var map = { 1: '快递配送', 2: '自提', 3: '快递/自提可选', 4: '线上-可选人', 5: '线上-单人' }
+      return map[method] || '未知'
+    },
+    formatPrice(value) {
+      if (!value && value !== 0) return '-'
+      return (value / 100).toFixed(2)
+    },
+    get giftCount() {
+      return this.giftList.length
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f5f5;
+  display: flex;
+  flex-direction: column;
+}
+.loading-wrap {
+  display: flex;
+  justify-content: center;
+  padding-top: 200rpx;
+}
+.loading-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.content {
+  flex: 1;
+  height: calc(100vh - 120rpx);
+  padding-bottom: 120rpx;
+}
+.order-header-section {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  padding: 40rpx 30rpx;
+}
+.order-no-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12rpx;
+}
+.order-no {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.85);
+}
+.status-tag {
+  font-size: 24rpx;
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  background: rgba(255,255,255,0.25);
+  color: #fff;
+}
+.order-hint {
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.75);
+}
+.section {
+  background: #fff;
+  margin: 20rpx;
+  border-radius: 16rpx;
+  padding: 24rpx;
+}
+.section-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+  display: block;
+}
+.product-card {
+  display: flex;
+  gap: 20rpx;
+  align-items: center;
+}
+.product-cover {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 10rpx;
+  background: #eee;
+  flex-shrink: 0;
+}
+.product-info-wrap {
+  flex: 1;
+}
+.product-name {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  margin-bottom: 8rpx;
+}
+.product-qty {
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 8rpx;
+}
+.product-price {
+  font-size: 30rpx;
+  color: #F97316;
+  font-weight: bold;
+}
+.edit-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16rpx 0;
+}
+.edit-label {
+  font-size: 26rpx;
+  color: #666;
+}
+.edit-value {
+  font-size: 26rpx;
+  color: #333;
+}
+.edit-hint {
+  font-size: 24rpx;
+  color: #F97316;
+}
+.price-input-row,
+.coupon-input-row {
+  margin-top: 16rpx;
+  padding-top: 16rpx;
+  border-top: 1rpx solid #f5f5f5;
+}
+.input-label {
+  font-size: 26rpx;
+  color: #666;
+  margin-bottom: 12rpx;
+  display: block;
+}
+.price-input,
+.coupon-input {
+  border: 1rpx solid #ddd;
+  border-radius: 8rpx;
+  padding: 12rpx 20rpx;
+  font-size: 28rpx;
+  margin-bottom: 12rpx;
+  width: 100%;
+  box-sizing: border-box;
+}
+.price-preview,
+.coupon-preview {
+  font-size: 26rpx;
+  color: #F97316;
+}
+.delivery-options {
+  display: flex;
+  gap: 20rpx;
+  margin-top: 16rpx;
+}
+.delivery-option {
+  flex: 1;
+  text-align: center;
+  padding: 16rpx;
+  border-radius: 8rpx;
+  border: 1rpx solid #ddd;
+  font-size: 26rpx;
+  color: #333;
+}
+.delivery-option.selected {
+  border-color: #F97316;
+  color: #F97316;
+  background: #fff7e6;
+}
+.coupon-info {
+  padding: 16rpx 0;
+}
+.coupon-tag {
+  font-size: 26rpx;
+  color: #52c41a;
+  background: #f6ffed;
+  padding: 8rpx 20rpx;
+  border-radius: 8rpx;
+}
+.gift-input-row {
+  margin-top: 16rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 12rpx;
+}
+.gift-input {
+  border: 1rpx solid #ddd;
+  border-radius: 8rpx;
+  padding: 12rpx 20rpx;
+  font-size: 26rpx;
+}
+.gift-add-btn {
+  background: #F97316;
+  color: #fff;
+  text-align: center;
+  padding: 16rpx;
+  border-radius: 8rpx;
+  font-size: 26rpx;
+  margin-top: 12rpx;
+}
+.gift-list {
+  margin-top: 16rpx;
+}
+.gift-item {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.gift-img {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 8rpx;
+  background: #eee;
+}
+.gift-name {
+  font-size: 24rpx;
+  color: #333;
+  flex: 1;
+}
+.gift-qty {
+  font-size: 24rpx;
+  color: #999;
+}
+.remark-textarea {
+  width: 100%;
+  height: 120rpx;
+  border: 1rpx solid #ddd;
+  border-radius: 8rpx;
+  padding: 16rpx;
+  font-size: 26rpx;
+  box-sizing: border-box;
+}
+.remark-history {
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.6;
+}
+.bottom-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 100rpx;
+  background: #fff;
+  border-top: 1rpx solid #eee;
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding: 0 30rpx;
+  z-index: 100;
+}
+.btn-save {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  color: #fff;
+  font-size: 28rpx;
+  padding: 0 60rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  border-radius: 36rpx;
+  border: none;
+}
+.btn-save::after {
+  border: none;
+}
+</style>

+ 12 - 0
cfc-frontend/pages/vendor/orders/orders.vue

@@ -43,6 +43,11 @@
           <view class="order-footer">
           <view class="order-footer">
             <text class="order-time">{{ formatTime(item.createdAt) }}</text>
             <text class="order-time">{{ formatTime(item.createdAt) }}</text>
             <view class="order-actions">
             <view class="order-actions">
+              <button
+                v-if="item.status === 'pending'"
+                class="btn-xs btn-review"
+                @click.stop="onReview(item)"
+              >审核修改</button>
               <button
               <button
                 v-if="item.status === 'paid'"
                 v-if="item.status === 'paid'"
                 class="btn-xs btn-confirm"
                 class="btn-xs btn-confirm"
@@ -141,6 +146,9 @@ export default {
         }
         }
       })
       })
     },
     },
+    onReview(item) {
+      uni.navigateTo({ url: '/pages/vendor/order-review/order-review?orderNo=' + item.orderNo })
+    },
     statusLabel(status) {
     statusLabel(status) {
       const map = {
       const map = {
         pending: '待支付',
         pending: '待支付',
@@ -290,6 +298,10 @@ export default {
   background: #e6f7ff;
   background: #e6f7ff;
   color: #1890ff;
   color: #1890ff;
 }
 }
+.btn-review {
+  background: #fff7e6;
+  color: #F97316;
+}
 .btn-xs::after {
 .btn-xs::after {
   border: none;
   border: none;
 }
 }

+ 5 - 0
cfc-frontend/utils/api.js

@@ -1558,6 +1558,11 @@ export const productOrderConfirm = (params) => {
   return request('/api/product/order/confirm', 'POST', params)
   return request('/api/product/order/confirm', 'POST', params)
 }
 }
 
 
+// 供应商审核修改订单(仅待支付状态)
+export const productOrderReviewUpdate = (params) => {
+  return request('/api/product/order/review/update', 'POST', params)
+}
+
 // 用户申请订单退款
 // 用户申请订单退款
 export const productOrderRefundApply = (params) => {
 export const productOrderRefundApply = (params) => {
   return request('/api/product/order/refund/apply', 'POST', params)
   return request('/api/product/order/refund/apply', 'POST', params)