差异反馈原因就该这样设计!

  • 收货确认页面


    页面
差异反馈页面
选择差异类型

差异反馈原因: 可以新增、可以删除、可以选择品相(品相不可以重复,选择一项减少一项,删除一项释放一项)、少收(数量最大为下单量、多收无限制)

差异反馈原因
  • 主要是这4个文件


    image.png
  • index.wxml

<view class="container">
    <view class="navBar" style="height:{{navH}}px;width:100%;">
        <navBar page-name="差异反馈" showNav="{{true}}" showHome="{{true}}" fontColor="{{fontColor}}" />
    </view>
    
    <scroll-view scroll-y="{{true}}" class="main-content" style="height:calc(100% - {{navH}}px - 100px);">
        <card title='差异反馈'>
            <van-cell-group>
                <van-field bind:change="onChange" data-field='differenceContact' type="number" value="{{ form.differenceContact || '' }}" label="收货人手机号" placeholder="请输入收货人手机号" input-align="right" required="{{true}}" />
                <van-field bind:change="onChange" data-field='driverPhone' type="number" value="{{ form.driverPhone || '' }}" label="送货司机电话" placeholder="请输入送货司机电话" input-align="right" required="{{true}}" />
                <van-field bind:click-input='chooseOptions'  value="{{ form.differenceTypeName || '' }}" label="差异类型" placeholder="请选择差异类型" input-align="right" required="{{true}}" is-link="{{true}}" readonly="{{true}}" />
                <!-- <van-field class="textarea" maxlength="{{500}}" autosize="{{autosize}}" type='textarea' bind:change="onChange" data-field='content' value="{{ form.content || '' }}" label="备注" placeholder="(如订单品项、数量、质量有差异,请当场与相关人员共识,明确差异类型,并留下您的联系方式。最多可填写500个字) " show-word-limit="{{true}}" input-align="right"  /> -->
                <van-collapse value="{{ activeNames }}" bind:change="onChangeCollapse">
                    <van-collapse-item  name="1">
                        <!-- 使用插槽自定义标题 -->
                        <view slot="title" class="required-title">
                            <text class="required-star">*</text>
                            <text>差异反馈原因</text>
                        </view>
                        <view wx:for="{{feedbackItems}}" class="reason-item" wx:key="index">
                            <picker mode="selector" range="{{differenceDeliveryList}}" range-key="typeId" bindchange="handleTypeIdChange" data-index="{{index}}">
                              <view class="reason-item-typeId picker">
                                <text wx:if="{{item.typeId}}">{{item.typeId}}</text>
                                <text wx:else class="select-placeholder">选择订单内的品相</text>
                              </view>
                            </picker>
                            <view class="reason-item-row">
                              <picker mode="selector" range="{{typeOptions}}" range-key="label" bindchange="handleTypeChange" data-index="{{index}}">
                                <view class="reason-item-type picker">
                                  <text wx:if="{{item.type}}">{{item.type}}</text>
                                  <text wx:else class="select-placeholder">(多收/少收)</text>
                                </view>
                              </picker>
                              <view class="number-input-container">
                                <view class="number-input-wrapper">
                                  <view class="number-input-minus" bindtap="handleDecrease" data-index="{{index}}">-</view>
                                  <input 
                                      type="number" 
                                      placeholder="0" 
                                      value="{{item.quantity || 0}}" 
                                      bindinput="handleQuantityInput" 
                                      data-index="{{index}}"
                                      class="quantity-input"
                                  />
                                  <view class="number-input-plus" bindtap="handleIncrease" data-index="{{index}}">+</view>
                                </view>
                                <view class="number-input-unit">件</view>
                              </view>
                              <view class="delete-btn" bindtap="handleDelete" data-index="{{index}}">
                                <van-icon name="delete" color="#fff" size="32rpx" />
                              </view>
                            </view>
                            <view class="dashed-divider"></view>
                        </view>
                        <!-- 添加新项按钮 -->
                        <view class="add-btn" bindtap="addFeedbackItem">
                          <text>+ 添加差异项</text>
                        </view>
                    </van-collapse-item>
                </van-collapse>
            </van-cell-group>
            <view style="height: 40rpx;"></view>

        </card>
    </scroll-view>
    <view class="btm-btn">
        <view class="bottom-btn-best" bindtap="close">关闭</view>
        <view wx:if="{{code !== 'view'}}" class="bottom-btn-best" bindtap="sumbit">确认</view>
    </view>
</view>

<van-notify class="notify" id="van-notify" />
<van-popup show="{{ isShow }}" position="bottom">
    <van-picker show-toolbar value-key='label' columns="{{ options }}" bind:cancel='onCancel' bind:confirm="onConfirm" />
</van-popup>
<wxs src="/utils/enums.wxs" module="transEnums" />

  • index.json
{
  "usingComponents": {
    "navBar":"/components/navbar/index",
    "van-popup": "@vant/weapp/popup/index",
    "van-datetime-picker": "@vant/weapp/datetime-picker/index",
    "card":"/components/card/card",
    "van-cell": "@vant/weapp/cell/index",
    "van-field": "@vant/weapp/field/index",
    "van-picker": "@vant/weapp/picker/index",
    "van-collapse": "@vant/weapp/collapse/index",
    "van-collapse-item": "@vant/weapp/collapse-item/index"
  },
  "navigationStyle":"custom",
  "navigationBarTitleText":"销售计划提报"
}
  • index.js
// finance-center/freezerProperty/index.js
import Notify from "@vant/weapp/notify/notify";
import { commonModel } from "../../../model/Common";
import { orderModel } from "../../../model/Order";
const app = getApp();
Page({
  /**
   * 页面的初始数据
   */
  data: {
    navH: app.globalData.navHeight,
    fontColor: "#ffffff",
    activeNames: ["1"],
    list: [],
    buttons: [
      {
        name: "新建报损单",
        code: "add",
      },
    ],
    username3: "",
    dictTypeCodeList: ["difference_delivery_type"],
    filesList: [],
    pdProdcut: {},
    pfProduct: {},
    form: {},
    isShow: false,
    options: [
      { value: "1", label: "漏袋" },
      { value: "2", label: "封口不严" },
      { value: "3", label: "瓶体破损" },
      { value: "4", label: "针眼状" },
      { value: "161", label: "角漏" },
      { value: "162", label: "摔漏" },
      { value: "163", label: "划漏" },
      { value: "157", label: "杯盖/杯体破损" },
    ],
    autosize: {
      maxHeight: 200,
      minHeight: 100,
    },
    allDifferenceDeliveryList: [], // 完整的品类列表
    differenceDeliveryList: [], // 实际显示的品类列表(会动态变化)
    selectedTypeIds: [], // 已选择的品类ID集合
    feedbackItems: [
      {
        typeId: "",
        type: "",
        quantity: 1
      },
    ],
    typeOptions: [
      { value: "多收", label: "多收" },
      { value: "少收", label: "少收" },
    ],
    // 手机号校验规则
    isValidPhone: function(phone) {
      return /^1[3-9]\d{9}$/.test(phone);
    }
  },

  // picker 选择事件
  handleTypeIdChange(e) {
    const { index } = e.currentTarget.dataset; // 获取当前项的索引
    const selectedIndex = e.detail.value; // 用户选择的数组下标
    const selectedItem =
      this.data.differenceDeliveryList[selectedIndex]; // 获取选择的 typeId

    // 更新已选集合
    const newSelectedTypeIds = new Set(this.data.selectedTypeIds);
    // 先移除这个位置之前选择的品类(如果有)
    if(this.data.feedbackItems[index].typeId) {
      newSelectedTypeIds.delete(this.data.feedbackItems[index].typeId);
    }
    // 添加新选择的品类
    newSelectedTypeIds.add(selectedItem.typeId);

    // 更新 feedbackItems 中对应项的 typeId
    this.setData({
      [`feedbackItems[${index}].typeId`]: selectedItem.typeId,
      [`feedbackItems[${index}].quantity`]: 1,
      selectedTypeIds: newSelectedTypeIds
    });

    // 少收的时候最大填写数量为交货单数量;多收的时候没有数量限制
    if (this.data.feedbackItems[index].type == '少收' && this.data.feedbackItems[index].quantity > selectedItem.quantity) {
      this.setData({
        [`feedbackItems[${index}].quantity`]: selectedItem.quantity,
      });
    }

    // 更新可选列表
    this.updateAvailableTypes();
  },
  // 更新可选品类列表
  updateAvailableTypes: function() {
    // 过滤掉已选的品类
    const availableTypes = this.data.allDifferenceDeliveryList.filter(
      item => !this.data.selectedTypeIds.has(item.typeId)
    );
    
    this.setData({
      differenceDeliveryList: availableTypes
    });
  },
  handleTypeChange(e) {
    const { index } = e.currentTarget.dataset; // 获取当前项的索引
    const selectedIndex = e.detail.value; // 用户选择的数组下标
    const selectedType = this.data.typeOptions[selectedIndex].value;
    // 获取当前品类的交货单数量
    const currentTypeId = this.data.feedbackItems[index].typeId;
    let deliveryQuantity = 0;
    
    if (currentTypeId) {
      const selectedItem = this.data.allDifferenceDeliveryList.find(
        item => item.typeId === currentTypeId
      );
      deliveryQuantity = selectedItem ? selectedItem.quantity : 0;
    }
    
    // 设置数量和类型
    let newQuantity = 1;
    
    // 如果是少收且当前数量超过交货单数量,则限制为交货单数量
    if (selectedType === '少收' && currentTypeId) {
      const currentQuantity = this.data.feedbackItems[index].quantity || 1;
      newQuantity = Math.min(currentQuantity, deliveryQuantity);
    }
    
    this.setData({ 
      [`feedbackItems[${index}].type`]: selectedType,
      [`feedbackItems[${index}].quantity`]: newQuantity
    });
    
    // 如果是少收,显示提示信息
    if (selectedType === '少收') {
      wx.showToast({
        title: `最大可填写数量为${deliveryQuantity}`,
        icon: 'none',
        duration: 2000
      });
    }
  },
  handleQuantityInput(e) {
    const index = e.currentTarget.dataset.index;
    const item = this.data.feedbackItems[index];
    let value = parseInt(e.detail.value) || 0;
    
    // 如果是少收,限制最大数量
    if (item.type === '少收' && item.typeId) {
      const selectedItem = this.data.allDifferenceDeliveryList.find(
        i => i.typeId === item.typeId
      );
      const maxQuantity = selectedItem ? selectedItem.quantity : 0;
      
      value = Math.min(value, maxQuantity);
      
      if (value >= maxQuantity) {
        wx.showToast({
          title: `已达到最大数量${maxQuantity}`,
          icon: 'none',
          duration: 1000
        });
      }
    }
    
    // 最小值限制
    value = Math.max(value, 0);
    
    this.setData({
      [`feedbackItems[${index}].quantity`]: value
    });
  },
  handleDecrease(e) {
      const index = e.currentTarget.dataset.index;
      let value = parseInt(this.data.feedbackItems[index].quantity) || 1;
      if(value > 1) value--;
      this.setData({
          [`feedbackItems[${index}].quantity`]: value
      });
  },
  handleIncrease(e) {
      const index = e.currentTarget.dataset.index;
      const item = this.data.feedbackItems[index];
      let value = parseInt(item.quantity) || 1;

      // 如果是少收,检查是否超过最大数量
      if (item.type === '少收' && item.typeId) {
        const selectedItem = this.data.allDifferenceDeliveryList.find(
          i => i.typeId === item.typeId
        );
        const maxQuantity = selectedItem ? selectedItem.quantity : 0;
        
        if (value >= maxQuantity) {
          wx.showToast({
            title: `已达到最大数量${maxQuantity}`,
            icon: 'none',
            duration: 1000
          });
          return;
        }
      }

      value++;
      this.setData({
          [`feedbackItems[${index}].quantity`]: value
      });
  },
  // 删除项目时需要释放已选品类
  handleDelete(e) {
    const index = e.currentTarget.dataset.index;
    const deletedItem = this.data.feedbackItems[index];
    if(index !== 0 || this.data.feedbackItems.length>1) {
      // 从已选集合中移除
      const newSelectedTypeIds = new Set(this.data.selectedTypeIds);
      if(deletedItem.typeId) {
        newSelectedTypeIds.delete(deletedItem.typeId);
      }
      // 获取当前 feedbackItems 数组
      let feedbackItems = this.data.feedbackItems;
      // 删除指定索引的项
      feedbackItems.splice(index, 1);     
      // 更新数据
      this.setData({
        feedbackItems,
        selectedTypeIds: newSelectedTypeIds
      }, () => {
        // 删除后更新可选列表
        this.updateAvailableTypes();
      });
    } else {
      if(deletedItem.typeId || deletedItem.typeId || deletedItem.quantity!=1) {
        // 从已选集合中移除
        const newSelectedTypeIds = new Set(this.data.selectedTypeIds);
        if(deletedItem.typeId) {
          newSelectedTypeIds.delete(deletedItem.typeId);
        }
        this.setData({
          [`feedbackItems[0]`]: {
            typeId: "",
            type: "",
            quantity: 1,
          },
          selectedTypeIds: newSelectedTypeIds
        }, () => {
          // 删除后更新可选列表
          this.updateAvailableTypes();
        });
      } else {
        wx.showToast({
          title: '已清空所有差异项数据!',
          icon: 'none',
          duration: 2000
        });
      }
    }
  },
  // 添加新反馈项
  addFeedbackItem() {
    // 检查是否已达到最大允许的反馈项数量
    const maxItems = this.data.allDifferenceDeliveryList.length;
    if (this.data.feedbackItems.length >= maxItems) {
      wx.showToast({
        title: `最多只能添加${maxItems}个差异项`,
        icon: 'none',
        duration: 2000
      });
      return;
    }
    this.setData({
      feedbackItems: [
        ...this.data.feedbackItems,
        { typeId: "", type: "", quantity: 1 }
      ]
    });
  },

  onChangeCollapse(event) {
    this.setData({
      activeNames: event.detail,
    });
  },

  //获取数据字典
  async getDicts() {
    const { dictTypeCodeList } = this.data;
    let params = {
      dictTypeCodeList,
    };
    const res = await commonModel.getDicts(params);
    if (res.success) {
      const { result } = res;
      for (let key in result) {
        this.setData({
          dicts: result[key].map((a) => ({
            ...a,
            value: a.dictCode,
            label: a.dictValue,
          })),
        });
      }
    }
  },
  //获取差异反馈原因
  async getDifferenceDeliveryList() {
    const res = await orderModel.getDifferenceDeliveryList({
      outId: this.data.outId,
    });
    if (res.success) {
      const { result } = res;
      this.setData({
        allDifferenceDeliveryList: result || [],
        differenceDeliveryList: result || []
      });
    }
  },
  onCancel() {
    this.setData({
      isShow: false,
    });
  },
  onConfirm(e) {
    const {
      detail: { value },
    } = e;
    this.setData(
      {
        ["form.differenceType"]: value.value,
        ["form.differenceTypeName"]: value.label,
      },
      () => {
        this.onCancel();
      }
    );
  },
  onChange(e) {
    const {
      currentTarget: {
        dataset: { field },
      },
      detail,
    } = e;
    const str = `form.${field}`;
    this.setData({
      [str]: detail,
    });
  },
  chooseOptions(e) {
    this.setData({
      isShow: true,
      options: this.data.dicts,
    });
  },
  async sumbit() {
    const { form, feedbackItems } = this.data;
    // 1. 校验基础表单字段
    if (!form.differenceContact) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "请输入收货人手机号",
      });
    }
    
    if (!this.data.isValidPhone(form.differenceContact)) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "收货人手机号格式不正确",
      });
    }

    if (!form.driverPhone) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "请输入送货司机电话",
      });
    }
    
    if (!this.data.isValidPhone(form.driverPhone)) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "送货司机电话格式不正确",
      });
    }

    if (!form.differenceType) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "请选择差异类型",
      });
    }

    // 2. 校验差异项
    if (feedbackItems.length === 0) {
      return Notify({
        type: "danger",
        top: this.data.navH,
        message: "请至少添加一个差异项",
      });
    }

    // 检查每个差异项是否完整填写
    for (let i = 0; i < feedbackItems.length; i++) {
      const item = feedbackItems[i];
      
      // 检查品类是否选择
      if (!item.typeId) {
        return Notify({
          type: "danger",
          top: this.data.navH,
          message: `请选择第${i+1}项的品相`,
        });
      }
      
      // 检查(多收/少收)是否选择
      if (!item.type) {
        return Notify({
          type: "danger",
          top: this.data.navH,
          message: `请选择第${i+1}项的多收/少收类型`,
        });
      }
      
      // 检查数量是否有效
      if (!item.quantity || item.quantity <= 0) {
        return Notify({
          type: "danger",
          top: this.data.navH,
          message: `请输入第${i+1}项的有效数量`,
        });
      }
      
      // 如果是少收,检查数量是否超过最大限制
      if (item.type === '少收') {
        const selectedItem = this.data.allDifferenceDeliveryList.find(
          d => d.typeId === item.typeId
        );
        if (selectedItem && item.quantity > selectedItem.quantity) {
          return Notify({
            type: "danger",
            top: this.data.navH,
            message: `第${i+1}项的数量不能超过${selectedItem.quantity}`,
          });
        }
      }
    }

    // 拼接差异反馈原因
    const differenceReason = feedbackItems
      .map(item => `${item.typeId}${item.type}${item.quantity}件`)
      .join(',');
    let params = {
      outId: this.data.outId,
      differenceContact: form.differenceContact,
      driverPhone: form.driverPhone,
      differenceType: form.differenceType,
      differenceReason
    };
    const res = await orderModel.differenceDeliveryConfirmCustomer(params);
    if (res.success) {
      wx.navigateBack({
        delta: 1,
      });
    }
  },
  close() {
    wx.navigateBack();
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    this.setData({
      outId: options.id,
    });
    this.getDicts();
    this.getDifferenceDeliveryList();
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {},

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {},

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {},

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {},

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {},

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {},

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {},
});

  • index.wxss
.container {
  width: 100vw;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: #f7f7f7;
  position: relative;
}
.container .navBar {
  position: sticky;
  top: 0;
  left: 0;
  z-index: 99;
}
.main-content {
  box-sizing: border-box;
  position: relative;
}
.main-content {
  position: absolute;
  top: 160rpx;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* background-color: red; */
  width: 710rpx;
  margin-bottom: 164rpx;
}
.container image {
  width: 100vw;
  height: 150rpx;
}
.main-content .item {
  display: flex;
  justify-content: space-between;
  padding: 20rpx 10rpx;
  font-size: 28rpx;
  color: #646566;
}
.fileName .check {
  width: 32rpx;
  height: 32rpx;
  vertical-align: bottom;
  margin: 0rpx 18rpx;
}
.fileName {
  display: flex;
  justify-content: space-between;
  font-size: 26rpx;
  margin: 10rpx 0;
  color: #9a9a9a;
}
.btm-btn {
  height: 164rpx;
  width: 100%;
  background-color: #ffffff;
  z-index: 99;
  position: fixed;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: space-around;
}
.textarea {
  height: 200rpx;
}

.index--van-cell.van-cell.cell-index--van-cell.van-cell--clickable.cell-index--van-cell--clickable {
  padding-left: 14rpx;
}

.required-star {
  color: red;
  margin-right: 4rpx;
  font-size: 28rpx;
}

.reason-item {
  margin-bottom: 20rpx;
}
.picker {
  position: relative;
  border: 1px solid #ccc;
  padding: 15rpx 40rpx 15rpx 20rpx; /* 右侧留出箭头空间 */
  border-radius: 6rpx;
}
/* 三角形箭头 */
.picker::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 20rpx;
  transform: translateY(-50%);
  width: 0;
  height: 0;
  border-left: 10rpx solid transparent;
  border-right: 10rpx solid transparent;
  border-top: 12rpx solid #333; /* 箭头颜色 */
  pointer-events: none; /* 防止箭头遮挡点击 */
}

.reason-item-typeId {
    color: #666666;
}
.select-placeholder {
    color: #cccccc;
}
.reason-item-row {
    margin-top:10rpx;
    display: flex;
    align-items: center;
}
.reason-item-type {
  width: 162rpx;
}

/* 数字输入框容器 */
.number-input-container {
    margin-left: 10px;
    display: flex;
    flex: 1;
    align-items: center;
    height: 64rpx;
}

.number-input-wrapper {
    display: flex;
    align-items: center;
    border: 1px solid #ddd;
    border-radius: 4px;
    overflow: hidden;
    flex: 1;
    height: 100%;
}

/* 加减按钮样式 */
.number-input-minus, 
.number-input-plus {
    width: 30px;
    height: 100%; /* 使用容器高度 */
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #f5f5f5;
    font-size: 16px;
}

/* 输入框样式 */
.quantity-input {
    flex:1;
    height: 100%; /* 使用容器高度 */
    text-align: center;
    border-left: 1px solid #ddd;
    border-right: 1px solid #ddd;
    border-top: none;
    border-bottom: none;
    padding: 0;
}

/* 单位文字样式 - 移到外面 */
.number-input-unit {
    margin-left: 8px;
    font-size: 14px;
    color: #666;
}

.delete-btn {
  margin-left: 8px;
  width: 54rpx;
  height: 54rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f78989;
  border-color: #f78989;
  border-radius: 6rpx;
  padding: 2rpx;
}


/* 分割线 */
.dashed-divider {
  height: 1px;
  margin: 20rpx 0;
  border-top: 1px dashed #ccc;
}

/* 添加按钮样式 */
.add-btn {
  color: #1890ff;
  padding: 20rpx;
  text-align: center;
  border: 1rpx dashed #1890ff;
  border-radius: 8rpx;
  margin-top: 20rpx;
}
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容