Jelajahi Sumber

feat(hexiao): 添加门店审核状态筛选和水印功能

- 在门店列表页面新增审核状态Tab筛选(待审核、已审核、已驳回)
- 为图片上传功能增加地理位置与时间水印
- 实现门店汇总数据展示表格
- 优化门店列表滚动加载逻辑,防止重复触发
- 修复门店详情跳转及数据显示问题
- 调整页面样式以适配新增功能布局
mws 4 minggu lalu
induk
melakukan
94f58dda6a

+ 10 - 1
api/hexiao.js

@@ -241,11 +241,12 @@ export function removeRetail(id){
 
 
 
-export function getStoreList(pageIndex,pageSize,storeName,ywyId){
+export function getStoreList(pageIndex,pageSize,storeName,ywyId,status){
     let data = {
         pageIndex:pageIndex,
         pageSize:pageSize,
         storeName:storeName,
+        status:status
     }
     if(ywyId>0){
         data.ywyId = ywyId;
@@ -698,3 +699,11 @@ export function queryYwyWritePrizeDetail(data){
         data: data
     })
 }
+
+export function queryStoreSummary(data){
+    return request({
+        url: `/ywy/queryStoreWriteOffSumRecord`,
+        method: 'post',
+        data: data
+    })
+}

+ 4 - 3
pages/cjx/shop/score_list.vue

@@ -6,7 +6,7 @@
           <text class="label">我的积分</text>
           <text class="value">{{totalPoints}}</text>
         </view>
-        <button class="use-points-btn" @click="goToUsagePage">去使用</button>
+        <button class="use-points-btn" @click="goToUsagePage" v-if="info.roleType === 1">去使用</button>
       </view>
     </view>
 
@@ -44,9 +44,11 @@ export default {
       pagination: { page: 1, limit: 15 },
       loadStatus: 'more', // 'more', 'loading', 'noMore'
       isLoading: false,
+      info: {}
     };
   },
   onLoad(opt) {
+    this.info = getAdminInfo();
     this.totalPoints = opt.pointsCount;
     this.fetchRecords(true);
   },
@@ -101,8 +103,7 @@ export default {
     },
     // 跳转到积分使用页面
     goToUsagePage() {
-      let info = getAdminInfo()
-      let userType = info.roleType;
+      let userType = this.info.roleType;
       let type = 0;
       if(userType === 1){
         type = 2;

+ 367 - 69
pages/hexiao/ywy/add_retail.vue

@@ -1,5 +1,12 @@
 <template>
   <view class="page-container">
+    <!-- 隐藏的Canvas用于添加水印 -->
+    <canvas
+        id="watermarkCanvas"
+        canvas-id="watermarkCanvas"
+        :style="{width: canvasWidth + 'px', height: canvasHeight + 'px', position: 'fixed', left: '-9999px', top: '-9999px'}">
+    </canvas>
+
     <view class="form-card">
 
       <view class="form-item">
@@ -56,6 +63,8 @@
             mode="grid"
             :limit="9"
             title="最多选择9张图片"
+            @tap="checkPermissionBeforeSelect"
+            @select="handleFileSelect"
             v-model="formData.photos"
         />
       </view>
@@ -77,6 +86,10 @@ export default {
     return {
       edit:false,
       isLocating:false,
+      canvasWidth: 800,
+      canvasHeight: 600,
+      processingImages: false,
+      imageAddress: '',
       // 表单数据
       formData: {
         photos: [],
@@ -90,67 +103,322 @@ export default {
   },
   onLoad(opt){
     this.edit = opt.edit == 1;
-      if(opt.id){
-        this.formData.storeId = opt.id;
-        getRetailDetail(opt.id).then(res=>{
-          let data = res.data;
-          this.formData = {
-            storeId: data.id,
-            storeName: data.store_name,
-            ownerName: data.contact_name,
-            contact: data.contact_phone,
-            address: data.address,
+    if(opt.id){
+      this.formData.storeId = opt.id;
+      getRetailDetail(opt.id).then(res=>{
+        let data = res.data;
+        this.formData = {
+          storeId: data.id,
+          storeName: data.store_name,
+          ownerName: data.contact_name,
+          contact: data.contact_phone,
+          address: data.address,
+        }
+        this.formData.photos = [];
+        let photos = data.store_photo.split(",")
+        for (let i = 0; i < photos.length; i++) {
+          let photo = photos[i];
+          this.formData.photos.push({url:photo})
+        }
+      })
+    }
+  },
+  methods: {
+    // 点击时检查权限
+    async checkPermissionBeforeSelect() {
+      console.log("检查权限");
+
+      try {
+        // 检查定位权限状态
+        const settingRes = await uni.getSetting();
+        const locationAuth = settingRes.authSetting['scope.userLocation'];
+
+        if (locationAuth === false) {
+          // 已拒绝授权,提示用户
+          const modalRes = await uni.showModal({
+            title: '提示',
+            content: '为了给图片添加地理位置水印,需要获取您的地理位置权限。是否前往设置开启?',
+            confirmText: '去设置',
+            cancelText: '取消'
+          });
+
+          if (modalRes.confirm) {
+            // 打开设置页面
+            const settingResult = await uni.openSetting();
+            if (settingResult.authSetting['scope.userLocation']) {
+              // 权限开启成功,先获取位置再选择图片
+              await this.getLocationForImageSelection();
+              this.$refs.files.choose();
+            } else {
+              uni.showToast({ title: '未开启定位权限,图片将不带位置水印', icon: 'none' });
+              // 用户仍然可以选择图片,但没有位置水印
+              this.$refs.files.choose();
+            }
+          } else {
+            // 用户取消开启权限,仍然允许选择图片
+            uni.showToast({ title: '未开启定位权限,图片将不带位置水印', icon: 'none' });
+            this.$refs.files.choose();
           }
-          this.formData.photos = [];
-          let photos = data.store_photo.split(",")
-          for (let i = 0; i < photos.length; i++) {
-            let photo = photos[i];
-            this.formData.photos.push({url:photo})
+        } else {
+          console.log("已获取定位权限");
+          // 已有权限或未申请过权限,先获取位置再选择图片
+          await this.getLocationForImageSelection();
+
+        }
+      } catch (error) {
+        console.error('权限检查失败:', error);
+        // 出现异常时,仍然允许选择图片
+        uni.showToast({ title: '权限检查失败,图片将不带位置水印', icon: 'none' });
+
+      }
+    },
+
+    // 专门用于图片选择的位置获取方法
+    async getLocationForImageSelection() {
+       await this.doGetLocation("imageAddress");
+    },
+
+    handleFileSelect(event) {
+      this.onFileSelect(event);
+    },
+
+    // 修改后的添加水印处理函数
+    async addWatermarkToImage(imagePath, address) {
+      return new Promise((resolve) => {
+        uni.getImageInfo({
+          src: imagePath,
+          success: (imageInfo) => {
+            // 设置合理的画布尺寸
+            let maxWidth = 800;
+            let maxHeight = 1200;
+            let width = imageInfo.width;
+            let height = imageInfo.height;
+
+            // 按比例缩放
+            if (width > maxWidth || height > maxHeight) {
+              let ratio = Math.min(maxWidth / width, maxHeight / height);
+              width = width * ratio;
+              height = height * ratio;
+            }
+
+            // 更新画布尺寸
+            this.canvasWidth = Math.round(width);
+            this.canvasHeight = Math.round(height);
+
+            // 等待画布更新
+            this.$nextTick(() => {
+              setTimeout(() => {
+                const ctx = uni.createCanvasContext('watermarkCanvas', this);
+
+                // 清除画布
+                ctx.clearRect(0, 0, width, height);
+
+                // 绘制原图
+                ctx.drawImage(imagePath, 0, 0, width, height);
+
+                // 添加时间水印
+                const now = new Date();
+                const timeText = `${now.getFullYear()}-${(now.getMonth()+1).toString().padStart(2, '0')}-${now.getDate().toString().padStart(2, '0')} ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
+
+                // 设置水印样式
+                ctx.setFontSize(24);
+                ctx.setFillStyle('rgba(255, 255, 255, 0.8)');
+                ctx.setTextAlign('right');
+
+                // 添加时间水印
+                ctx.fillText(timeText, width - 20, height - 40);
+
+                // 添加地址水印
+                if (address) {
+                  let addressText = address;
+                  if (address.length > 12) {
+                    addressText = address.substring(0, 12) + '...';
+                  }
+                  ctx.fillText(addressText, width - 20, height - 15);
+                }
+
+                // 绘制并导出
+                ctx.draw(false, () => {
+                  setTimeout(() => {
+                    uni.canvasToTempFilePath({
+                      canvasId: 'watermarkCanvas',
+                      x: 0,
+                      y: 0,
+                      width: width,
+                      height: height,
+                      destWidth: width,
+                      destHeight: height,
+                      quality: 0.8,
+                      fileType: 'jpg',
+                      success: (res) => {
+                        console.log('水印图片生成成功:', res.tempFilePath);
+                        resolve(res.tempFilePath);
+                      },
+                      fail: (err) => {
+                        console.error('导出水印图片失败:', err);
+                        // 如果导出失败,直接返回原图
+                        resolve(imagePath);
+                      }
+                    }, this);
+                  }, 300);
+                });
+              }, 100);
+            });
+          },
+          fail: (err) => {
+            console.error('获取图片信息失败:', err);
+            resolve(imagePath); // 获取图片信息失败时返回原图
           }
-        })
+        });
+      });
+    },
+
+    // 批量处理水印
+    async processImagesWithWatermark(files, address) {
+      const processedFiles = [];
+      for (let i = 0; i < files.length; i++) {
+        const file = files[i];
+        const watermarkedPath = await this.addWatermarkToImage(file.path || file.url, address);
+        processedFiles.push({
+          ...file,
+          path: watermarkedPath,
+          url: watermarkedPath
+        });
       }
-  },
-  methods: {
+      return processedFiles;
+    },
+
     getLocation() {
       this.isLocating = true;
+      let self = this;
+      // 先检查定位权限
+      uni.getSetting({
+        success: (res) => {
+          // 如果没有授权定位权限
+          if (res.authSetting['scope.userLocation'] === false) {
+            // 提示用户需要授权
+            uni.showModal({
+              title: '提示',
+              content: '需要获取您的地理位置,请在设置中开启定位权限',
+              confirmText: '去设置',
+              cancelText: '取消',
+              success: (modalRes) => {
+                if (modalRes.confirm) {
+                  // 打开授权设置页面
+                  uni.openSetting({
+                    success: (settingRes) => {
+                      if (settingRes.authSetting['scope.userLocation']) {
+                        // 用户打开了定位权限,重新获取位置
+                        this.doGetLocation();
+                      } else {
+                        uni.showToast({ title: '未开启定位权限', icon: 'none' });
+                        this.isLocating = false;
+                      }
+                    },
+                    fail: () => {
+                      uni.showToast({ title: '打开设置失败', icon: 'none' });
+                      this.isLocating = false;
+                    }
+                  });
+                } else {
+                  this.isLocating = false;
+                }
+              }
+            });
+          } else {
+            // 已有权限或未申请过权限,直接获取位置
+            this.doGetLocation();
+          }
+        },
+        fail: () => {
+          uni.showToast({ title: '权限检查失败', icon: 'none' });
+          this.isLocating = false;
+        }
+      });
+    },
+
+    // 实际执行获取位置的方法
+    doGetLocation(value) {
       let self = this;
       uni.getLocation({
-        type: 'gcj02', // 'wgs84' GPS坐标, 'gcj02' 国测局坐标
+        type: 'gcj02',
         isHighAccuracy: true,
-        geocode: true, // 获取带有地址信息(仅App和H5支持)
+        geocode: true,
         success: (res) => {
           console.log('当前位置信息:', res);
           parseLocation(res.latitude, res.longitude).then(res=>{
             if("Success" === res.data.message){
               let formatted_addresses = res.data.result.formatted_addresses
+              if ( value === 'imageAddress'){
+                self.imageAddress = formatted_addresses.standard_address;
+                console.log('图片地址:', self.imageAddress);
+                return;
+              }
               self.formData.address = formatted_addresses.standard_address
             }else{
               uni.showToast({ title: '解析地址失败', icon: 'none' });
             }
-
-          })
+          }).catch(err => {
+            console.error('地址解析错误:', err);
+            uni.showToast({ title: '地址解析失败', icon: 'none' });
+          });
         },
         fail: (err) => {
-          uni.showToast({ title: '获取位置失败', icon: 'none' });
-          console.error(err);
+          console.error('获取位置失败:', err);
+          if (err.errCode === 103 || err.errMsg.includes('auth deny')) {
+            uni.showToast({ title: '定位权限被拒绝', icon: 'none' });
+          } else if (err.errCode === 101 || err.errMsg.includes('timeout')) {
+            uni.showToast({ title: '定位超时,请稍后重试', icon: 'none' });
+          } else {
+            uni.showToast({ title: '获取位置失败', icon: 'none' });
+          }
         },
         complete: () => {
           this.isLocating = false;
         }
       });
     },
+
+    // 修改后的图片选择处理方法
+    async onFileSelect(event) {
+      if (this.processingImages) {
+        uni.showToast({ title: '正在处理图片,请稍候', icon: 'none' });
+        return;
+      }
+      this.processingImages = true;
+      uni.showLoading({ title: '正在添加水印...' });
+
+      const newFiles = event.tempFiles;
+
+      try {
+        // 对新选择的文件添加水印
+        const watermarkedFiles = await this.processImagesWithWatermark(newFiles, this.imageAddress);
+
+        // 保留之前已选择的图片,添加新的带水印图片
+        const existingPhotos = this.formData.photos.filter(photo => photo.url);
+        const newPhotos = watermarkedFiles.map(file => ({
+          ...file,
+          url: file.path || file.url
+        }));
+
+        this.formData.photos = [...existingPhotos, ...newPhotos];
+      } catch (error) {
+        console.error('处理图片失败:', error);
+        uni.showToast({ title: '图片处理失败', icon: 'none' });
+      } finally {
+        this.processingImages = false;
+        uni.hideLoading();
+      }
+    },
+
     // 提交表单
     async submitForm() {
-      if(this.$refs.files.files.length === 0){
+      if(this.formData.photos.length === 0){
         uni.showToast({ title: '请上传门店照片', icon: 'none' });
         return;
       }
-      const imageUrlarr = await uploadImage(this.$refs.files.files);
-      if(imageUrlarr.length === 0){
-        uni.showToast({ title: '门店照片上传失败,请重试', icon: 'none' });
-        return;
-      }
-      // --- 手动进行数据校验 ---
+
+      // 数据校验
       if (!this.formData.storeName) {
         uni.showToast({ title: '请输入店铺名称', icon: 'none' });
         return;
@@ -163,7 +431,6 @@ export default {
         uni.showToast({ title: '请输入联系方式', icon: 'none' });
         return;
       }
-      // 简单的手机号格式校验
       if (!/^1[3-9]\d{9}$/.test(this.formData.contact)) {
         uni.showToast({ title: '请输入正确的手机号码', icon: 'none' });
         return;
@@ -173,47 +440,63 @@ export default {
         return;
       }
 
-      console.log('校验通过,准备提交的数据:', this.formData);
-      // 执行提交逻辑
       uni.showLoading({ title: '正在保存...' });
-      if(this.formData.storeId>0){
-        updateStore(this.formData.storeId,this.formData.storeName,this.formData.ownerName,this.formData.contact,this.formData.address,imageUrlarr.join(","))
-            .then(res=>{
-              uni.hideLoading();
-              if(res.code == 0){
-                uni.showToast({
-                  title: '保存成功',
-                  icon: 'success'
-                });
-                uni.navigateBack();
-              }else{
-                uni.showToast({
-                  title: res.msg,
-                  icon: 'none'
-                });
-              }
 
+      try {
+        // 直接上传已添加水印的图片
+        const filesToUpload = this.formData.photos.map(photo => ({
+          path: photo.url || photo.path
+        }));
 
-            })
-      }else{
-        addStore(this.formData.storeName,this.formData.ownerName,this.formData.contact,this.formData.address,imageUrlarr.join(","))
-            .then(res=>{
-              uni.hideLoading();
-              if(res.code == 0){
-                uni.showToast({
-                  title: '保存成功',
-                  icon: 'success'
-                });
-                uni.navigateBack();
-              }else{
-                uni.showToast({
-                  title: res.msg,
-                  icon: 'none'
-                });
-              }
+        const imageUrlarr = await uploadImage(filesToUpload);
 
+        if(imageUrlarr.length === 0){
+          uni.showToast({ title: '门店照片上传失败,请重试', icon: 'none' });
+          return;
+        }
 
-            })
+        if(this.formData.storeId>0){
+          updateStore(this.formData.storeId,this.formData.storeName,this.formData.ownerName,this.formData.contact,this.formData.address,imageUrlarr.join(","))
+              .then(res=>{
+                uni.hideLoading();
+                if(res.code == 0){
+                  uni.showToast({
+                    title: '保存成功',
+                    icon: 'success'
+                  });
+                  uni.navigateBack();
+                }else{
+                  uni.showToast({
+                    title: res.msg,
+                    icon: 'none'
+                  });
+                }
+              })
+        }else{
+          addStore(this.formData.storeName,this.formData.ownerName,this.formData.contact,this.formData.address,imageUrlarr.join(","))
+              .then(res=>{
+                uni.hideLoading();
+                if(res.code == 0){
+                  uni.showToast({
+                    title: '保存成功',
+                    icon: 'success'
+                  });
+                  uni.navigateBack();
+                }else{
+                  uni.showToast({
+                    title: res.msg,
+                    icon: 'none'
+                  });
+                }
+              })
+        }
+      } catch (error) {
+        uni.hideLoading();
+        console.error('提交失败:', error);
+        uni.showToast({
+          title: '保存失败,请重试',
+          icon: 'none'
+        });
       }
     }
   }
@@ -287,4 +570,19 @@ export default {
     border: none;
   }
 }
-</style>
+
+/* 为uni-file-picker添加样式 */
+.file-picker {
+  margin-top: 20rpx;
+}
+
+/* 确保图片预览正常显示 */
+::v-deep .uni-file-picker__lists {
+  .uni-file-picker__lists-item {
+    image {
+      width: 100%;
+      height: 100%;
+    }
+  }
+}
+</style>

+ 302 - 37
pages/hexiao/ywy/hexiao_record.vue

@@ -35,7 +35,67 @@
       </view>
     </view>
 
-    <scroll-view class="list-container" scroll-y="true" @scrolltolower="loadMore">
+    <!-- 门店汇总显示区域 - 表格形式 -->
+    <view v-if="currentTab === 3" class="store-summary-container">
+      <scroll-view class="summary-list" scroll-y="true">
+        <view v-if="summaryList.length === 0" class="empty-list">
+          <text>暂无汇总数据</text>
+        </view>
+
+        <view v-else class="summary-table-wrapper">
+          <!-- 表头 -->
+          <view class="table-header">
+            <view class="header-cell store-col">门店</view>
+            <view class="header-cell product-col">品相</view>
+            <view class="header-cell number-col">数量</view>
+          </view>
+
+          <!-- 表格内容 -->
+          <view class="table-body">
+
+              <!-- 每个门店的第一行 -->
+              <view class="table-row" v-for="(store, storeIndex) in summaryList" :key="storeIndex">
+                <view class="table-cell store-col">
+                  <view class="store-name-cell">
+                    <uni-icons type="shop" size="16" color="#3c82f8"></uni-icons>
+                    <text class="store-name-text">{{ store.storeName }}</text>
+                  </view>
+                </view>
+                <view>
+                  <view class="table-row-cl2" v-for="(item, itemIndex) in store.writeOffRecordDetailVos" :key="itemIndex">
+                    <view class="table-cell product-col">
+                      <text>{{ item.item }}</text>
+                    </view>
+                    <view class="table-cell number-col">
+                      <text>{{ item.writeOffAmount }}</text>
+                    </view>
+                  </view>
+                  <view class="table-row-cl2 ">
+                    <view class="table-cell product-col">
+                      <text>汇总</text>
+                    </view>
+                    <view class="table-cell number-col">
+                      <text style="color: orange;">{{ store.summaryCount }}</text>
+                    </view>
+                  </view>
+
+                </view>
+
+
+              </view>
+
+          </view>
+
+        </view>
+      </scroll-view>
+    </view>
+    <!-- 原有记录列表(tab 0-2时显示) -->
+    <scroll-view
+        v-else
+        class="list-container"
+        scroll-y="true"
+        @scrolltolower="loadMore"
+    >
       <view v-if="recordList.length === 0 && loadStatus !== 'loading'" class="empty-list">
         <text>暂无相关记录</text>
       </view>
@@ -73,7 +133,6 @@
             <text class="detail-value">{{ item.num }}</text>
           </view>
 
-
           <view class="detail-row" v-if="record.status === 1 ||record.status === 2">
             <text class="detail-label">提交时间</text>
             <text class="detail-value">{{ record.applyTime }}</text>
@@ -92,13 +151,13 @@
 
       <uni-load-more :status="loadStatus"></uni-load-more>
     </scroll-view>
-    <u-calendar min-date="2025-07-01" max-date="2030-07-01" @close="timeShow = false" :show="timeShow" :mode="mode" @confirm="confirm"></u-calendar>
 
+    <u-calendar min-date="2025-07-01" max-date="2030-07-01" @close="timeShow = false" :show="timeShow" :mode="mode" @confirm="confirm"></u-calendar>
   </view>
 </template>
 
 <script>
-import {queryAddRecord,doCommitToJxs} from "@/api/hexiao";
+import {queryAddRecord, doCommitToJxs, queryStoreSummary} from "@/api/hexiao"; // 假设新的接口
 
 export default {
   data() {
@@ -106,16 +165,23 @@ export default {
       startTime: '',
       endTime: '',
       timeShow: false,
-      startTimeXd:"",
+      startTimeXd: "",
       mode: 'range',
       searchQuery: '',
       tabs: [
-        { name: '待核销', key: 'pending',id: 0 },
+        { name: '待核销', key: 'pending', id: 0 },
         { name: '核销中', key: 'verifying', id: 1},
-        { name: '已核销', key: 'verified' , id :2},
+        { name: '已核销', key: 'verified', id: 2},
+        { name: '门店汇总', key: 'all', id: 3},
       ],
       currentTab: 0,
       recordList: [],
+      summaryList: [
+          {
+            writeOffRecordDetailVos:[]
+          }
+      ], // 门店汇总数据
+      grandTotal: 0, // 总计数量
       pagination: { page: 1, limit: 10 },
       loadStatus: 'more', // 'more', 'loading', 'noMore'
       isLoading: false,
@@ -125,7 +191,11 @@ export default {
     this.fetchRecords(true);
   },
   onPullDownRefresh() {
-    this.fetchRecords(true);
+    if (this.currentTab === 3) {
+      this.fetchStoreSummary(true);
+    } else {
+      this.fetchRecords(true);
+    }
   },
   methods: {
     confirm(e) {
@@ -136,8 +206,8 @@ export default {
         this.handleSearch()
       }
       this.timeShow = false;
-
     },
+
     getStatusClass(status){
       const statusMap = {
         0: 'pending',
@@ -146,6 +216,7 @@ export default {
       };
       return statusMap[status] || '';
     },
+
     getStatusText(status) {
       const statusMap = {
         0: '待核销',
@@ -154,11 +225,53 @@ export default {
       };
       return statusMap[status] || '未知';
     },
+
     changeTab(tabKey) {
       if (this.currentTab === tabKey) return;
       this.currentTab = tabKey;
-      this.fetchRecords(true); // 切换tab时重新加载数据
+
+      if (this.currentTab === 3) {
+        // 切换到门店汇总tab
+        this.fetchStoreSummary(true);
+      } else {
+        // 切换到其他tab(0-2)
+        this.fetchRecords(true);
+      }
+    },
+
+    async fetchStoreSummary(isRefresh = false) {
+      if (this.isLoading) return;
+
+      this.isLoading = true;
+
+      try {
+        const params = {
+          startTime: this.startTime,
+          endTime: this.endTime
+        };
+
+        const res = await queryStoreSummary(params);
+        if (res.code === 0) {
+          this.summaryList = res.data || [];
+          this.grandTotal = res.data.grandTotal || 0;
+        } else {
+          this.summaryList = [];
+          this.grandTotal = 0;
+          uni.showToast({ title: '获取汇总数据失败', icon: 'none' });
+        }
+        console.log('this.summaryList',this.summaryList)
+
+        console.log('this.summaryList',this.summaryList.length)
+      } catch (error) {
+        console.error('获取门店汇总失败:', error);
+        this.summaryList = [];
+        this.grandTotal = 0;
+      } finally {
+        this.isLoading = false;
+        uni.stopPullDownRefresh();
+      }
     },
+
     async fetchRecords(isRefresh = false) {
       if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
         return;
@@ -171,7 +284,6 @@ export default {
       this.isLoading = true;
       this.loadStatus = 'loading';
 
-
       let data = {};
       if(this.currentTab !== -1){
         data.status = this.currentTab;
@@ -180,6 +292,7 @@ export default {
       data.endTime = this.endTime;
       data.pageIndex = this.pagination.page
       data.pageSize = this.pagination.limit
+
       queryAddRecord(data).then(res=>{
         let finalData = res.data.records;
         if(!finalData){
@@ -195,25 +308,35 @@ export default {
         this.isLoading = false;
         uni.stopPullDownRefresh();
       });
-
-
-
     },
+
     detail(id){
       uni.navigateTo({ url: `/pages/hexiao/ywy/hexiao_detail?id=`+id });
     },
+
     loadMore() {
+      if (this.currentTab === 3) {
+        // 门店汇总不需要分页加载
+        return;
+      }
       this.fetchRecords();
     },
+
     clearSearch(){
       this.startTime = "";
       this.endTime = "";
       this.startTimeXd = "";
       this.handleSearch()
     },
+
     handleSearch() {
-      this.fetchRecords(true);
+      if (this.currentTab === 3) {
+        this.fetchStoreSummary(true);
+      } else {
+        this.fetchRecords(true);
+      }
     },
+
     verify(id,orderNo) {
       uni.showModal({
         title: '提示',
@@ -227,7 +350,6 @@ export default {
               }else{
                 uni.showToast({ title: '核销失败' });
               }
-
             });
           }
         }
@@ -251,20 +373,11 @@ export default {
   z-index: 100;
   background-color: #f5f6fa;
 }
+
 .search-wrapper {
   padding: 20rpx;
   background-color: #ffffff;
 }
-.search-bar {
-  display: flex;
-  align-items: center;
-  background-color: #f5f6fa;
-  border-radius: 50rpx;
-  padding: 0 25rpx;
-  height: 70rpx;
-}
-.search-input { flex: 1; font-size: 28rpx; margin-left: 15rpx; }
-.placeholder { color: #b0b0b0; }
 
 .tabs-wrapper {
   display: flex;
@@ -295,11 +408,166 @@ export default {
   }
 }
 
+/* 门店汇总样式 - 表格形式 */
+.store-summary-container {
+  //flex: 1;
+  background-color: #f5f6fa;
+  overflow: auto;
+  height: 100%;
+}
+
+.summary-list {
+  height: 100%;
+}
+
+.summary-table-wrapper {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  margin: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+}
+
+.table-header {
+  display: flex;
+  background-color: #f8f9fa;
+  padding: 24rpx 20rpx;
+  border-bottom: 1rpx solid #e8e8e8;
+
+  .header-cell {
+    font-size: 28rpx;
+    font-weight: 600;
+    color: #333;
+    text-align: center;
+
+    &.store-col {
+      width: 220rpx;
+      flex-shrink: 0;
+    }
+
+    &.product-col {
+      flex: 1;
+    }
+
+    &.number-col {
+      width: 150rpx;
+      flex-shrink: 0;
+      text-align: center;
+    }
+  }
+}
+
+.table-body {
+  .table-row {
+    display: flex;
+    align-items: center;
+    padding: 0 20rpx;
+    min-height: 100rpx;
+    border-bottom: 1rpx solid #a5a5a5;
+
+    .table-cell {
+      font-size: 28rpx;
+      color: #333;
+      display: flex;
+      align-items: center;
+      padding: 20rpx 0;
+      min-height: 100rpx;
+      box-sizing: border-box;
+
+      &.store-col {
+        width: 220rpx;
+        flex-shrink: 0;
+
+        .store-name-cell {
+          display: flex;
+          align-items: center;
+
+          uni-icons {
+            margin-right: 10rpx;
+          }
+
+          .store-name-text {
+            font-weight: 500;
+          }
+        }
+      }
+
+      &.product-col {
+        flex: 1;
+      }
+
+      &.number-col {
+        width: 150rpx;
+        flex-shrink: 0;
+        text-align: right;
+        justify-content: flex-end;
+      }
+    }
+  }
+
+  .store-divider {
+    height: 1rpx;
+    background-color: #f0f0f0;
+    margin: 0 20rpx;
+  }
+}
+
+.table-footer {
+  display: flex;
+  align-items: center;
+  padding: 0 20rpx;
+  min-height: 100rpx;
+  background-color: #f0f7ff;
+  border-top: 2rpx solid #3c82f8;
+
+  .footer-cell {
+    font-size: 30rpx;
+    font-weight: bold;
+    display: flex;
+    align-items: center;
+    padding: 20rpx 0;
+    min-height: 100rpx;
+    box-sizing: border-box;
+
+    &.store-col {
+      width: 220rpx;
+      flex-shrink: 0;
+    }
+
+    &.product-col {
+      flex: 1;
+      justify-content: flex-end;
+      padding-right: 10rpx;
+
+      .total-label {
+        color: #333;
+      }
+    }
+
+    &.number-col {
+      width: 150rpx;
+      flex-shrink: 0;
+      text-align: right;
+
+      .total-number {
+        color: #3c82f8;
+        font-size: 32rpx;
+      }
+    }
+  }
+}
+
+.empty-list {
+  text-align: center;
+  color: #999;
+  padding-top: 150rpx;
+}
+
+/* 原有样式保持不变 */
 .list-container {
   flex: 1;
   height: 100%;
 }
-.empty-list { text-align: center; color: #999; padding-top: 150rpx; }
 
 .record-card {
   background-color: #ffffff;
@@ -341,6 +609,7 @@ export default {
 .card-body {
   padding-top: 10rpx;
 }
+
 .detail-row {
   display: flex;
   justify-content: space-between;
@@ -360,6 +629,7 @@ export default {
   justify-content: flex-end;
   margin-top: 20rpx;
 }
+
 .verify-btn {
   background-color: #3c82f8;
   color: #fff;
@@ -382,18 +652,13 @@ export default {
   justify-content: center;
   align-items: center;
 }
-.query-btn-icon {
-  height: 32rpx;
-  width: 32rpx;
-  background-image:
-      url("https://hyscancode.oss-cn-hangzhou.aliyuncs.com/xiaochengxu/cjx/queryIoc.png");
-  background-size: cover;
-  background-position: center;
-  background-repeat: no-repeat;
-}
+
 .query-btn-text {
   font-weight: 400;
   font-size: 30rpx;
   color: #F5F5F5;
 }
-</style>
+.table-row-cl2{
+  display: flex;
+}
+</style>

+ 325 - 44
pages/hexiao/ywy/retail.vue

@@ -8,19 +8,68 @@
             v-model="searchQuery"
             placeholder="输入门店名称"
             placeholder-class="placeholder"
-           @confirm = "handleSearch"
+            @confirm = "handleSearch"
         />
+      </view>
 
-
+      <!-- 新增:审核状态Tab筛选 -->
+      <view class="filter-tabs">
+<!--        <view-->
+<!--            class="tab-item"-->
+<!--            :class="{ active: auditFilter === '' }"-->
+<!--            @click="changeAuditFilter('')"-->
+<!--        >-->
+<!--          <text>全部</text>-->
+<!--        </view>-->
+        <view
+            class="tab-item"
+            :class="{ active: auditFilter === 0 }"
+            @click="changeAuditFilter(0)"
+        >
+          <text>待审核</text>
+        </view>
+        <view
+            class="tab-item"
+            :class="{ active: auditFilter === 1 }"
+            @click="changeAuditFilter(1)"
+        >
+          <text>已审核</text>
+        </view>
+        <view
+            class="tab-item"
+            :class="{ active: auditFilter === 2 }"
+            @click="changeAuditFilter(2)"
+        >
+          <text>已驳回</text>
+        </view>
       </view>
     </view>
 
-    <!-- 修改1: 为scroll-view添加scrolltolower事件 -->
-    <scroll-view scroll-y="true" class="list-container with-padding-bottom" @scrolltolower="loadMoreData">
-      <view v-if="storeList.length === 0" class="empty-list">
+    <!-- 修改:优化scroll-view的高度和滚动设置 -->
+    <scroll-view
+        scroll-y="true"
+        class="list-container"
+        :style="listContainerStyle"
+        @scrolltolower="handleScrollToLower"
+        :lower-threshold="100"
+    >
+      <!-- 添加加载状态提示 -->
+      <view v-if="isLoading && pagination.page === 1" class="loading-wrapper">
+        <text>加载中...</text>
+      </view>
+
+      <view v-if="storeList.length === 0 && !isLoading" class="empty-list">
         <text>没有找到相关门店</text>
       </view>
+
       <view v-for="store in storeList" :key="store.id" class="store-card">
+        <!-- 新增:审核状态标签 -->
+        <view v-if="store.audit_status" class="audit-tag" :class="store.audit_status">
+          <text v-if="store.audit_status === 'pending'">待审核</text>
+          <text v-if="store.audit_status === 'approved'">已审核</text>
+          <text v-if="store.audit_status === 'rejected'">已驳回</text>
+        </view>
+
         <view class="card-decorator"></view>
         <view class="card-content">
           <view class="card-header">
@@ -40,6 +89,10 @@
               <text class="info-label">门店地址:</text>
               <text class="info-value">{{ store.address }}</text>
             </view>
+            <view class="info-row"   v-if="store.review_status === 2">
+              <text class="info-label">驳回原因:</text>
+              <text class="info-value" style="color: red">{{ store.reject_reason }}</text>
+            </view>
           </view>
           <view class="action-buttons">
             <view class="action-btn" @click="viewDetails(store.id)">
@@ -54,7 +107,7 @@
               <uni-icons type="tune-filled" size="18" color="#3c82f8"></uni-icons>
               <text>编辑</text>
             </view>
-            <view class="action-btn" @click="patrolStore(store.id)" v-if="ywyId === 0">
+            <view class="action-btn" @click="patrolStore(store.id)" v-if="ywyId === 0 && store.review_status === 1">
               <uni-icons type="home-filled" size="18" color="#3c82f8"></uni-icons>
               <text>巡店</text>
             </view>
@@ -65,6 +118,16 @@
           </view>
         </view>
       </view>
+      <!-- 在列表最后添加一个空的占位元素 -->
+      <view class="list-placeholder"></view>
+
+      <!-- 添加底部加载状态 -->
+      <view v-if="isLoading && pagination.page > 1" class="load-more">
+        <text>加载更多...</text>
+      </view>
+      <view v-if="loadStatus === 'noMore' && storeList.length > 0" class="load-more">
+        <text>没有更多数据了</text>
+      </view>
     </scroll-view>
 
     <view class="fixed-footer" v-if="ywyId === 0">
@@ -93,11 +156,28 @@ export default {
         limit: 10,
       },
       ywyId: 0,
-      // 修改2: 初始化加载状态变量
       isLoading: false,
-      loadStatus: 'more' // more: 可以继续加载, loading: 加载中, noMore: 没有更多数据
+      loadStatus: 'more', // more: 可以继续加载, loading: 加载中, noMore: 没有更多数据
+      auditFilter: 0, // 1 已审核 0 未审核 2 已驳回
+      // 新增:防止重复触发滚动事件
+      scrollTimer: null,
+      isScrolling: false,
     };
   },
+  computed: {
+    // 计算列表容器高度
+    listContainerStyle() {
+      let bottomPadding = 0;
+      if (this.ywyId === 0) {
+        // 有底部按钮和tabbar时
+        bottomPadding = 250; // rpx
+      }
+      return {
+        height: `calc(100vh - ${this.getHeaderHeight()}px)`,
+        paddingBottom: `${bottomPadding}rpx`
+      };
+    }
+  },
   onShow(){
     this.fetchStoreList(true);
   },
@@ -105,9 +185,17 @@ export default {
     if(opt.id){
       this.ywyId = opt.id;
     }
-   this.fetchStoreList(true);
+    this.fetchStoreList(true);
   },
   methods: {
+    // 获取头部高度(搜索栏+筛选Tab)
+    getHeaderHeight() {
+      // 搜索栏高度大约 70rpx + 40rpx padding = 110rpx ≈ 55px
+      // 筛选Tab高度大约 70rpx ≈ 35px
+      // 总共约 90px
+      return 90; // 可以根据实际情况调整
+    },
+
     fetchStoreList(isRefresh = false) {
       if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
         return; // 防止重复加载
@@ -119,13 +207,12 @@ export default {
       }
 
       this.isLoading = true;
-      this.loadStatus = 'loading';
 
-      // --- [模拟API请求] ---
-      // 在这里替换成您真实的 uni.request API 调用
-      console.log(`正在请求第 ${this.pagination.page} 页数据, 搜索词: "${this.searchQuery}"`);
+      if (!isRefresh) {
+        this.loadStatus = 'loading';
+      }
 
-      getStoreList(this.pagination.page,this.pagination.limit,this.searchQuery,this.ywyId).then(res=>{
+      getStoreList(this.pagination.page, this.pagination.limit, this.searchQuery, this.ywyId, this.auditFilter).then(res=>{
         let data = res.data;
         let mockData = data.records;
         if (mockData.length > 0) {
@@ -136,21 +223,60 @@ export default {
           this.loadStatus = 'noMore';
         }
         this.isLoading = false;
+
+        // 新增:加载完成后重置滚动状态
+        this.isScrolling = false;
+      }).catch(err => {
+        this.isLoading = false;
+        this.isScrolling = false;
+        console.error('加载数据失败:', err);
       });
     },
+
     handleSearch() {
+      this.pagination.page = 1;
+      this.fetchStoreList(true);
+    },
+
+    changeAuditFilter(status) {
+      if (this.auditFilter !== status) {
+        this.auditFilter = status;
         this.pagination.page = 1;
         this.fetchStoreList(true);
+      }
     },
+
+    // 优化滚动到底部事件处理
+    handleScrollToLower() {
+      // 防止重复触发
+      if (this.isScrolling || this.isLoading || this.loadStatus === 'noMore') {
+        return;
+      }
+
+      this.isScrolling = true;
+
+      // 添加防抖处理
+      if (this.scrollTimer) {
+        clearTimeout(this.scrollTimer);
+      }
+
+      this.scrollTimer = setTimeout(() => {
+        this.fetchStoreList(false);
+        this.scrollTimer = null;
+      }, 300);
+    },
+
     viewDetails(id) {
       uni.navigateTo({ url: '/pages/hexiao/ywy/add_retail?edit=0&id='+id });
       console.log('查看详情 ID:', id);
-      },
+    },
     dataDetail(id){
       uni.navigateTo({ url: '/pages/hexiao/ywy/retail_detail?id='+id });
       console.log('查看数据 ID:', id);
     },
-    editStore(id) {    uni.navigateTo({ url: '/pages/hexiao/ywy/add_retail?edit=1&id='+id });},
+    editStore(id) {
+      uni.navigateTo({ url: '/pages/hexiao/ywy/add_retail?edit=1&id='+id });
+    },
     patrolStore(id) {
       uni.navigateTo({ url: '/pages/hexiao/ywy/add_patrol?edit=1&id='+id });
     },
@@ -175,12 +301,6 @@ export default {
     },
     addNewStore() {
       uni.navigateTo({ url: `/pages/hexiao/ywy/add_retail?edit=1` });
-    },
-    // 修改:将方法名改为loadMoreData
-    loadMoreData() {
-      // 当滚动到底部时,调用fetchStoreList加载下一页数据
-      // 传入false表示不是刷新操作,而是加载更多
-      this.fetchStoreList(false);
     }
   }
 }
@@ -192,41 +312,197 @@ export default {
   height: 100vh;
   display: flex;
   flex-direction: column;
+  overflow: hidden; /* 防止页面整体滚动 */
 }
 
 .search-wrapper {
   padding: 20rpx;
   background-color: #ffffff;
-  position: sticky; top: 0; z-index: 100;
+  position: sticky;
+  top: 0;
+  z-index: 100;
+  flex-shrink: 0; /* 防止搜索区域被压缩 */
 }
+
 .search-bar {
-  display: flex; align-items: center; background-color: #f5f6fa;
-  border-radius: 50rpx; padding: 0 25rpx; height: 70rpx;
+  display: flex;
+  align-items: center;
+  background-color: #f5f6fa;
+  border-radius: 50rpx;
+  padding: 0 25rpx;
+  height: 70rpx;
+}
+
+.search-input {
+  flex: 1;
+  font-size: 28rpx;
+  margin-left: 15rpx;
+}
+
+.placeholder {
+  color: #b0b0b0;
+}
+
+/* 新增:审核状态Tab筛选样式 */
+.filter-tabs {
+  display: flex;
+  background-color: #f5f6fa;
+  border-radius: 10rpx;
+  margin-top: 20rpx;
+  padding: 8rpx;
+  flex-shrink: 0; /* 防止Tab区域被压缩 */
+}
+
+.tab-item {
+  flex: 1;
+  text-align: center;
+  padding: 16rpx 0;
+  font-size: 26rpx;
+  color: #666;
+  border-radius: 8rpx;
+  transition: all 0.3s;
+}
+
+.tab-item.active {
+  background-color: #ffffff;
+  color: #3c82f8;
+  font-weight: bold;
+  box-shadow: 0 2rpx 8rpx rgba(60, 130, 248, 0.2);
+}
+
+/* 新增:审核状态标签样式 */
+.audit-tag {
+  position: absolute;
+  top: 10rpx;
+  right: 10rpx;
+  padding: 6rpx 16rpx;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+  font-weight: bold;
+  z-index: 1;
+}
+
+.audit-tag.pending {
+  background-color: #fff8e1;
+  color: #ff9800;
+  border: 1rpx solid #ff9800;
+}
+
+.audit-tag.approved {
+  background-color: #e8f5e9;
+  color: #4caf50;
+  border: 1rpx solid #4caf50;
+}
+
+.audit-tag.rejected {
+  background-color: #ffebee;
+  color: #f44336;
+  border: 1rpx solid #f44336;
 }
-.search-input { flex: 1; font-size: 28rpx; margin-left: 15rpx; }
-.placeholder { color: #b0b0b0; }
 
 .list-container {
-  flex: 1; padding: 0 20rpx; box-sizing: border-box;
+  flex: 1;
+  padding: 0 20rpx;
+  box-sizing: border-box;
+  overflow: hidden; /* 确保滚动区域正常工作 */
 }
-/* --- 关键改动 2 --- */
-.list-container.with-padding-bottom {
-  /* 按钮高度(90+40=130) + tabBar高度(100) + 一点富余 = 250rpx */
+
+/* 移除原来的padding-bottom设置,改用动态计算 */
+/* .list-container.with-padding-bottom {
   padding-bottom: 250rpx;
+} */
+
+/* 加载状态样式 */
+.loading-wrapper {
+  text-align: center;
+  padding: 40rpx 0;
+  color: #999;
+  font-size: 28rpx;
+}
+
+.load-more {
+  text-align: center;
+  padding: 30rpx 0;
+  color: #999;
+  font-size: 26rpx;
+  border-top: 1rpx solid #f0f0f0;
+  margin-top: 20rpx;
+}
+
+.empty-list {
+  text-align: center;
+  color: #999;
+  padding-top: 100rpx;
+  font-size: 28rpx;
+}
+
+.store-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  margin-top: 20rpx;
+  position: relative;
+  overflow: hidden;
+  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
+}
+
+.card-decorator {
+  position: absolute;
+  left: 0;
+  top: 0;
+  bottom: 0;
+  width: 8rpx;
+  background-color: #e3efff;
+}
+
+.card-content {
+  padding: 30rpx;
+  padding-left: 40rpx;
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
 }
 
-.empty-list { text-align: center; color: #999; padding-top: 100rpx; }
-.store-card { background-color: #ffffff; border-radius: 16rpx; margin-top: 20rpx; position: relative; overflow: hidden; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04); }
-.card-decorator { position: absolute; left: 0; top: 0; bottom: 0; width: 8rpx; background-color: #e3efff; }
-.card-content { padding: 30rpx; padding-left: 40rpx; }
-.card-header { display: flex; align-items: center; }
-.store-name { font-size: 32rpx; font-weight: bold; color: #333; margin-left: 15rpx; }
-.info-grid { margin-top: 20rpx; font-size: 26rpx; color: #666; }
-.info-row { margin-top: 10rpx; }
-.action-buttons { display: flex; justify-content: space-around; border-top: 1rpx solid #f5f5f5; margin-top: 30rpx; padding-top: 25rpx; }
-.action-btn { display: flex; align-items: center; font-size: 26rpx; color: #3c82f8; }
-.action-btn text { margin-left: 8rpx; }
-.delete-btn { color: #e54d42; }
+.store-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-left: 15rpx;
+}
+
+.info-grid {
+  margin-top: 20rpx;
+  font-size: 26rpx;
+  color: #666;
+}
+
+.info-row {
+  margin-top: 10rpx;
+}
+
+.action-buttons {
+  display: flex;
+  justify-content: space-around;
+  border-top: 1rpx solid #f5f5f5;
+  margin-top: 30rpx;
+  padding-top: 25rpx;
+}
+
+.action-btn {
+  display: flex;
+  align-items: center;
+  font-size: 26rpx;
+  color: #3c82f8;
+}
+
+.action-btn text {
+  margin-left: 8rpx;
+}
+
+.delete-btn {
+  color: #e54d42;
+}
 
 .fixed-footer {
   position: fixed;
@@ -241,6 +517,7 @@ export default {
   padding: 20rpx 30rpx;
   box-sizing: border-box;
   z-index: 90; // z-index比tabBar低,但比页面内容高
+  flex-shrink: 0; /* 防止底部按钮区域被压缩 */
 }
 
 .add-store-btn {
@@ -254,4 +531,8 @@ export default {
     border: none;
   }
 }
-</style>
+.list-placeholder {
+  height: 150rpx; /* 这个高度应该等于底部按钮区域的高度 */
+  width: 100%;
+}
+</style>