فهرست منبع

feat 管理端页面

wzh 4 ماه پیش
والد
کامیت
ccac44efe4

+ 354 - 0
pages/cjx/hexiao/ywy/add_patrol.vue

@@ -0,0 +1,354 @@
+<template>
+	<view class="page-container">
+		<view class="store-info-card">
+			<view class="card-header">
+				<uni-icons type="shop-filled" size="20" color="#3c82f8"></uni-icons>
+				<text class="store-name">{{ storeInfo.name }}</text>
+			</view>
+			<view class="info-row">
+				<text class="info-label">店主名称:</text>
+				<text class="info-value">{{ storeInfo.owner }}</text>
+			</view>
+			<view class="info-row">
+				<text class="info-label">联系方式:</text>
+				<text class="info-value">{{ storeInfo.contact }}</text>
+			</view>
+			<view class="info-row">
+				<text class="info-label">门店地址:</text>
+				<text class="info-value">{{ storeInfo.address }}</text>
+			</view>
+		</view>
+
+		<view class="patrol-content-card">
+			<view class="form-item">
+				<view class="form-label required">门头照片</view>
+				<uni-file-picker
+					:value="formData.storefrontPhoto"
+					file-mediatype="image"
+					mode="grid"
+          ref="mentou"
+					:limit="1"
+				/>
+			</view>
+
+			<view class="form-item">
+				<view class="form-label required">陈列照片</view>
+				<uni-file-picker
+					:value="formData.displayPhoto"
+					file-mediatype="image"
+					mode="grid"
+          ref="chenlie"
+					:limit="9"
+				/>
+			</view>
+
+			<view class="form-item">
+				<view class="form-label required">获取位置</view>
+				<view class="location-wrapper" @click="getLocation">
+					<text class="location-text" :class="{'placeholder': !locationInfo.address}">
+						{{ locationInfo.address || '获取当前位置' }}
+					</text>
+					<uni-icons v-if="!isLocating" type="location-filled" size="20" color="#3c82f8"></uni-icons>
+					<view v-if="isLocating" class="loading-spinner"></view>
+				</view>
+			</view>
+
+			<view class="form-item">
+				<view class="form-label">备注</view>
+				<textarea
+					class="remark-textarea"
+					v-model="formData.remarks"
+					placeholder="请输入备注信息"
+					placeholder-class="placeholder"
+					auto-height
+				/>
+			</view>
+		</view>
+
+		<view class="footer-save-button">
+			<button class="save-btn" @click="submitForm">提 交</button>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getRetailDetail,  uploadImage,addPatrolRecord,parseLocation} from "@/api/hexiao";
+
+	export default {
+		data() {
+			return {
+				// 假设的门店信息,从上个页面传来
+				storeInfo: {
+					id: 1,
+					name: '',
+					owner: '',
+					contact: '',
+					address: ''
+				},
+				// 表单数据
+				formData: {
+					storefrontPhoto: [], // 用于 file-picker 显示
+					storefrontPhotoUrl: '', // 门头照上传后的URL
+					displayPhoto: [], // 用于 file-picker 显示
+					displayPhotoUrl: '', // 陈列照上传后的URL
+					remarks: ''
+				},
+				// 位置信息
+				locationInfo: {
+					latitude: null,
+					longitude: null,
+					address: ''
+				},
+				isLocating: false, // 是否正在定位中
+			};
+		},
+    onLoad(opt){
+      this.storeInfo.id = opt.id;
+      getRetailDetail(opt.id).then(res=>{
+        let data = res.data;
+        this.storeInfo = {
+          id: data.id,
+          name: data.store_name,
+          owner: data.contact_name,
+          contact: data.contact_phone,
+          address: data.address,
+        }
+      })
+    },
+		methods: {
+			// 文件选择后自动上传
+			async handleFileSelect(type, e) {
+				const tempFile = e.tempFiles[0];
+				try {
+					const imageUrl = await this.uploadImageToServer(tempFile.path);
+					if (type === 'storefrontPhoto') {
+						this.formData.storefrontPhotoUrl = imageUrl;
+						this.formData.storefrontPhoto = [tempFile];
+					} else if (type === 'displayPhoto') {
+						this.formData.displayPhotoUrl = imageUrl;
+						this.formData.displayPhoto = [tempFile];
+					}
+					uni.showToast({ title: '上传成功', icon: 'success' });
+				} catch (error) {
+					uni.showToast({ title: '上传失败', icon: 'none' });
+				}
+			},
+
+			// 删除图片
+			handleFileDelete(type) {
+				if (type === 'storefrontPhoto') {
+					this.formData.storefrontPhoto = [];
+					this.formData.storefrontPhotoUrl = '';
+				} else if (type === 'displayPhoto') {
+					this.formData.displayPhoto = [];
+					this.formData.displayPhotoUrl = '';
+				}
+			},
+			// 获取地理位置
+			getLocation() {
+				this.isLocating = true;
+        let self = this;
+				uni.getLocation({
+					type: 'gcj02', // 'wgs84' GPS坐标, 'gcj02' 国测局坐标
+					isHighAccuracy: true,
+					geocode: true, // 获取带有地址信息(仅App和H5支持)
+					success: (res) => {
+						console.log('当前位置信息:', res);
+						this.locationInfo.latitude = res.latitude;
+						this.locationInfo.longitude = res.longitude;
+
+            parseLocation(res.latitude, res.longitude).then(res=>{
+              if("Success" === res.data.message){
+                let address = res.data.result.address
+                self.locationInfo.address = address
+              }else{
+                uni.showToast({ title: '解析地址失败', icon: 'none' });
+              }
+
+            })
+					},
+					fail: (err) => {
+						uni.showToast({ title: '获取位置失败', icon: 'none' });
+						console.error(err);
+					},
+					complete: () => {
+						this.isLocating = false;
+					}
+				});
+			},
+
+			// 提交表单
+			async submitForm() {
+        if(this.$refs.chenlie.files.length === 0){
+          return uni.showToast({ title: '请上传门头照片', icon: 'none' });
+        }
+        if(this.$refs.mentou.files.length === 0){
+          return uni.showToast({ title: '请上传陈列照片', icon: 'none' });
+        }
+        const chenlieArr = await uploadImage(this.$refs.chenlie.files);
+        const mentouArr = await uploadImage(this.$refs.mentou.files);
+				// 数据校验
+        if(mentouArr.length === 0){
+          uni.showToast({ title: '门店照片上传失败,请重试', icon: 'none' });
+          return;
+        }
+        if(chenlieArr.length === 0){
+          uni.showToast({ title: '陈列照片上传失败,请重试', icon: 'none' });
+          return;
+        }
+				if (!this.locationInfo.address) {
+					return uni.showToast({ title: '请获取当前位置', icon: 'none' });
+				}
+
+				uni.showLoading({ title: '正在提交...' });
+
+				const finalData = {
+					storeId: this.storeInfo.id,
+          storeImg: mentouArr[0],
+          displayImg: chenlieArr,
+					latitude: this.locationInfo.latitude,
+					longitude: this.locationInfo.longitude,
+					address: this.locationInfo.address,
+          remark: this.formData.remarks
+				};
+
+        let res = await addPatrolRecord(finalData)
+        if(res.code == 0){
+          uni.showToast({ title: '提交成功', icon: 'success' });
+        }else{
+          uni.showToast({ title: '提交失败', icon: 'error' });
+        }
+        uni.hideLoading();
+				setTimeout(() => uni.navigateBack(), 1000);
+			},
+
+			// 上传图片到您的后端服务器(此方法保持不变)
+			uploadImageToServer(tempFilePath) {
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: 'https://您的服务器地址/api/upload', // 【重要】请替换
+						filePath: tempFilePath,
+						name: 'file',
+						success: (res) => {
+							const data = JSON.parse(res.data);
+							if (data.code === 0) {
+								resolve(data.data.url);
+							} else {
+								reject(new Error(data.msg || '上传失败'));
+							}
+						},
+						fail: (err) => { reject(err); }
+					});
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page-container {
+		min-height: 100vh;
+		background: linear-gradient(to bottom, #e4efff, #f5f6fa 40%);
+		padding: 30rpx;
+		box-sizing: border-box;
+		padding-bottom: 160rpx; // 为底部按钮留出空间
+	}
+
+	.store-info-card, .patrol-content-card {
+		background-color: #ffffff;
+		border-radius: 20rpx;
+		padding: 30rpx;
+		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+		margin-bottom: 30rpx;
+	}
+
+	.store-info-card {
+		.card-header {
+			display: flex;
+			align-items: center;
+			.store-name { font-size: 32rpx; font-weight: bold; margin-left: 15rpx; }
+		}
+		.info-row {
+			margin-top: 20rpx;
+			font-size: 28rpx;
+			.info-label { color: #999; }
+			.info-value { color: #333; margin-left: 20rpx; }
+		}
+	}
+
+	.patrol-content-card {
+		padding: 10rpx 30rpx;
+	}
+
+	.form-item {
+		padding: 30rpx 0;
+		border-bottom: 1rpx solid #f0f0f0;
+		&:last-child { border-bottom: none; }
+	}
+
+	.form-label {
+		display: block;
+		font-size: 30rpx;
+		color: #333;
+		font-weight: 500;
+		margin-bottom: 20rpx;
+		&.required::before {
+			content: '*';
+			color: #e54d42;
+			margin-right: 8rpx;
+		}
+	}
+
+	.location-wrapper {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		.location-text {
+			font-size: 28rpx;
+			color: #333;
+			flex: 1;
+			&.placeholder { color: #c0c4cc; }
+		}
+	}
+
+	.remark-textarea {
+		width: 100%;
+		font-size: 28rpx;
+		min-height: 150rpx;
+	}
+	.placeholder { color: #c0c4cc; }
+
+	.footer-save-button {
+		position: fixed;
+		left: 0;
+		bottom: 0;
+		width: 100%;
+		padding: 20rpx 30rpx;
+		background-color: #f5f6fa;
+		box-sizing: border-box;
+		padding-bottom: calc(20rpx + constant(safe-area-inset-bottom));
+		padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+	}
+
+	.save-btn {
+		background-color: #409eff;
+		color: #ffffff;
+		border-radius: 50rpx;
+		font-size: 32rpx;
+		height: 90rpx;
+		line-height: 90rpx;
+		&::after { border: none; }
+	}
+
+	.loading-spinner {
+		width: 40rpx;
+		height: 40rpx;
+		border-radius: 50%;
+		border: 4rpx solid #f3f3f3;
+		border-top-color: #3498db;
+		animation: spin 1s linear infinite;
+	}
+	@keyframes spin {
+		to { transform: rotate(360deg); }
+	}
+</style>

+ 209 - 0
pages/cjx/hexiao/ywy/hexiao_detail.vue

@@ -0,0 +1,209 @@
+<template>
+  <view class="page-container">
+    <view class="info-card">
+      <view class="info-row">
+        <text class="info-label">核销门店</text>
+        <text class="info-value">{{ recordInfo.storeName }}</text>
+      </view>
+      <view class="info-row">
+        <text class="info-label">核销时间</text>
+        <text class="info-value">{{ recordInfo.verificationTime }}</text>
+      </view>
+    </view>
+
+    <view class="summary-card">
+      <view
+          class="summary-tab"
+          v-for="item in summaryData"
+          :key="item.key"
+      >
+        <text class="tab-category">{{ item.category }}</text>
+        <text class="tab-count">{{ item.count }}{{ item.unit }}</text>
+      </view>
+    </view>
+
+    <scroll-view class="details-list" scroll-y="true" @scrolltolower="loadMore">
+      <view v-if="detailsList.length === 0 && loadStatus !== 'loading'" class="empty-tip">
+        暂无明细记录
+      </view>
+      <view v-for="(item, index) in detailsList" :key="index" class="detail-item-card">
+        <text class="item-name">{{ item.name }}</text>
+        <text class="item-code">扫码编号:{{ item.code }}</text>
+      </view>
+      <uni-load-more :status="loadStatus"></uni-load-more>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import {queryAddRecordDetail} from "@/api/hexiao";
+
+export default {
+  data() {
+    return {
+      id:"",
+      // 总览信息
+      recordInfo: {
+        storeName: '湖南长沙超吉炫旗舰店',
+        verificationTime: '2025-08-11 09:30:00',
+      },
+      // 分类汇总数据
+      summaryData: [
+        { category: '10元槟榔', count: 20, unit: '袋', key: 'product_10' },
+        { category: '20元槟榔', count: 20, unit: '袋', key: 'product_20' },
+        { category: '30元槟榔', count: 20, unit: '袋', key: 'product_30' },
+        { category: '能量饮料', count: 20, unit: '罐', key: 'product_drink' },
+      ],
+      // 所有明细的列表
+      detailsList: [],
+      // 分页加载相关
+      pagination: { page: 1, limit: 15 },
+      loadStatus: 'more', // 'more', 'loading', 'noMore'
+      isLoading: false,
+    };
+  },
+  onLoad(opt) {
+    // 页面加载时,获取第一页数据
+    // options.id 可以用来从上个页面传递记录ID
+    this.id = opt.id
+    this.getDetail(true);
+  },
+  onPullDownRefresh() {
+    this.fetchDetails(true);
+  },
+  methods: {
+    getDetail(){
+      queryAddRecordDetail(this.id).then(res=>{
+        debugger;
+      })
+    },
+    // 模拟从API获取明细数据
+    async fetchDetails(isRefresh = false) {
+      if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
+        return;
+      }
+      if (isRefresh) {
+        this.pagination.page = 1;
+        this.detailsList = [];
+        this.loadStatus = 'more';
+      }
+      this.isLoading = true;
+      this.loadStatus = 'loading';
+
+      console.log(`请求第 ${this.pagination.page} 页明细...`);
+      await new Promise(resolve => setTimeout(resolve, 1000));
+
+      const mockData = Array.from({ length: this.pagination.limit }, (_, i) => {
+        const id = (this.pagination.page - 1) * this.pagination.limit + i + 1;
+        return {
+          name: `超吉炫10元精制槟榔 - ${id}`,
+          code: `BH2025010020${String(id).padStart(3, '0')}`
+        };
+      });
+      const noMoreData = this.pagination.page >= 5; // 模拟加载4页后没有更多
+
+      uni.stopPullDownRefresh();
+      if (mockData.length > 0 && !noMoreData) {
+        this.detailsList = [...this.detailsList, ...mockData];
+        this.pagination.page++;
+        this.loadStatus = 'more';
+      } else {
+        this.loadStatus = 'noMore';
+      }
+      this.isLoading = false;
+    },
+    // 滚动到底部加载更多
+    loadMore() {
+      this.fetchDetails();
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f5f6fa;
+}
+
+.info-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  margin: 20rpx;
+  padding: 20rpx 30rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+}
+
+.info-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 28rpx;
+  padding: 15rpx 0;
+  .info-label { color: #666; }
+  .info-value { color: #333; }
+}
+
+.summary-card {
+  display: flex;
+  justify-content: space-around;
+  background: linear-gradient(to right, #6ca1ff, #3c82f8);
+  border-radius: 16rpx;
+  margin: 0 20rpx 20rpx;
+  padding: 20rpx 10rpx;
+  color: #ffffff;
+}
+
+.summary-tab {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 10rpx;
+
+  .tab-category {
+    font-size: 26rpx;
+    opacity: 0.9;
+  }
+  .tab-count {
+    font-size: 28rpx;
+    font-weight: bold;
+    margin-top: 10rpx;
+  }
+}
+
+.details-list {
+  flex: 1;
+  height: 100%;
+  padding: 0 20rpx;
+}
+
+.empty-tip {
+  text-align: center;
+  color: #999;
+  padding-top: 100rpx;
+}
+
+.detail-item-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  padding: 30rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+
+  .item-name {
+    display: block;
+    font-size: 30rpx;
+    font-weight: bold;
+    color: #333;
+  }
+
+  .item-code {
+    display: block;
+    font-size: 26rpx;
+    color: #999;
+    margin-top: 15rpx;
+  }
+}
+</style>

+ 391 - 0
pages/cjx/hexiao/ywy/hexiao_record.vue

@@ -0,0 +1,391 @@
+<template>
+  <view class="page-container">
+    <view class="sticky-header">
+      <view class="search-wrapper">
+        <view class="query">
+          <u-row customStyle="margin-bottom: 10px" gutter="20">
+            <u-col span="9">
+              <u--input
+                  @focus="timeShow = true"
+                  v-model="startTimeXd"
+                  placeholder="开始日期 ~ 结束日期"
+                  prefixIcon="calendar"
+                  prefixIconStyle="font-size: 22px;color: #909399"
+                  @clear="handleSearch"
+              ></u--input>
+            </u-col>
+            <u-col span="3">
+              <view class="query-btn" @click="clearSearch">
+                <view class="query-btn-text">清空</view>
+              </view>
+            </u-col>
+          </u-row>
+        </view>
+      </view>
+      <view class="tabs-wrapper">
+        <view
+            v-for="tab in tabs"
+            :key="tab.key"
+            class="tab-item"
+            :class="{ 'active': currentTab === tab.id }"
+            @click="changeTab(tab.id)"
+        >
+          {{ tab.name }}
+        </view>
+      </view>
+    </view>
+
+    <scroll-view class="list-container" scroll-y="true" @scrolltolower="loadMore">
+      <view v-if="recordList.length === 0 && loadStatus !== 'loading'" class="empty-list">
+        <text>暂无相关记录</text>
+      </view>
+
+      <view v-for="record in recordList" :key="record.orderNo" class="record-card">
+        <view class="card-header">
+          <uni-icons type="paperclip" size="20" color="#3c82f8"></uni-icons>
+          <text class="record-id" @click="detail(record.id)" style="text-decoration: underline">{{ record.orderNo }}</text>
+          <view class="status-badge pending" v-if="record.status === 0">
+            {{ getStatusText(record.status) }}
+          </view>
+          <view class="status-badge verifying"  v-if="record.status === 1">
+            {{ getStatusText(record.status) }}
+          </view>
+          <view class="status-badge verified"  v-if="record.status === 2">
+            {{ getStatusText(record.status) }}
+          </view>
+        </view>
+
+        <view class="card-body">
+          <view class="detail-row">
+            <text class="detail-label">待核销商品总数</text>
+            <text class="detail-value">{{ record.writeOffNum }}</text>
+          </view>
+          <view class="detail-row product-item" v-for="(item, index) in record.writeOffRecordDetailVos" :key="index">
+            <text class="detail-label">{{ item.categoryName }}</text>
+            <text class="detail-value">{{ item.num }}</text>
+          </view>
+
+          <view class="detail-row" v-if="record.applicationTime">
+            <text class="detail-label">申请时间</text>
+            <text class="detail-value">{{ record.applicationTime }}</text>
+          </view>
+
+          <view class="detail-row" v-if="record.verificationTime">
+            <text class="detail-label">核销时间</text>
+            <text class="detail-value">{{ record.verificationTime }}</text>
+          </view>
+        </view>
+
+        <view class="card-footer" v-if="record.status === 0">
+          <button class="verify-btn" @click="verify(record.id,record.orderNo)">一键核销</button>
+        </view>
+      </view>
+
+      <uni-load-more :status="loadStatus"></uni-load-more>
+    </scroll-view>
+    <u-calendar @close="timeShow = false" :show="timeShow" :mode="mode" @confirm="confirm"></u-calendar>
+
+  </view>
+</template>
+
+<script>
+import {queryAddRecord,doCommitToJxs} from "@/api/hexiao";
+
+export default {
+  data() {
+    return {
+      startTime: '',
+      endTime: '',
+      timeShow: false,
+      startTimeXd:"",
+      mode: 'range',
+      searchQuery: '',
+      tabs: [
+        { name: '全部', key: 'all' ,id:-1 },
+        { name: '待核销', key: 'pending',id: 0 },
+        { name: '核销中', key: 'verifying', id: 1},
+        { name: '已核销', key: 'verified' , id :2},
+      ],
+      currentTab: -1,
+      recordList: [],
+      pagination: { page: 1, limit: 10 },
+      loadStatus: 'more', // 'more', 'loading', 'noMore'
+      isLoading: false,
+    };
+  },
+  onLoad() {
+    this.fetchRecords(true);
+  },
+  onPullDownRefresh() {
+    this.fetchRecords(true);
+  },
+  methods: {
+    confirm(e) {
+      this.startTime = e[0];
+      this.endTime = e[e.length-1];
+      this.startTimeXd = e[0]+' ~ '+e[e.length-1];
+      if(!this.show){
+        this.handleSearch()
+      }
+      this.timeShow = false;
+
+    },
+    getStatusClass(status){
+      const statusMap = {
+        0: 'pending',
+        1: 'verifying',
+        2: 'verified'
+      };
+      return statusMap[status] || '';
+    },
+    getStatusText(status) {
+      const statusMap = {
+        0: '待核销',
+        1: '核销中',
+        2: '已核销'
+      };
+      return statusMap[status] || '未知';
+    },
+    changeTab(tabKey) {
+      if (this.currentTab === tabKey) return;
+      this.currentTab = tabKey;
+      this.fetchRecords(true); // 切换tab时重新加载数据
+    },
+    async fetchRecords(isRefresh = false) {
+      if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
+        return;
+      }
+      if (isRefresh) {
+        this.pagination.page = 1;
+        this.recordList = [];
+        this.loadStatus = 'more';
+      }
+      this.isLoading = true;
+      this.loadStatus = 'loading';
+
+
+      let data = {};
+      if(this.currentTab !== -1){
+        data.status = this.currentTab;
+      }
+      data.startTime = this.startTime;
+      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){
+          finalData = [];
+        }
+        if (finalData.length > 0) {
+          this.recordList = [...this.recordList, ...finalData];
+          this.pagination.page++;
+          this.loadStatus = 'more';
+        } else {
+          this.loadStatus = 'noMore';
+        }
+        this.isLoading = false;
+        uni.stopPullDownRefresh();
+      });
+
+
+
+    },
+    detail(id){
+      uni.navigateTo({ url: `/pages/cjx/hexiao/ywy/hexiao_detail?id=`+id });
+    },
+    loadMore() {
+      this.fetchRecords();
+    },
+    clearSearch(){
+      this.startTime = "";
+      this.endTime = "";
+      this.startTimeXd = "";
+      this.handleSearch()
+    },
+    handleSearch() {
+      this.fetchRecords(true);
+    },
+    verify(id,orderNo) {
+      uni.showModal({
+        title: '提示',
+        content: `确认核销订单 ${orderNo} 吗?`,
+        success: (res) => {
+          if (res.confirm) {
+            doCommitToJxs(id).then(res=>{
+              if(res.code === 0){
+                this.fetchRecords(true);
+                uni.showToast({ title: '核销成功' });
+              }else{
+                uni.showToast({ title: '核销失败' });
+              }
+
+            });
+          }
+        }
+      });
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f5f6fa;
+}
+
+.sticky-header {
+  position: sticky;
+  top: 0;
+  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;
+  background-color: #ffffff;
+  border-bottom: 1rpx solid #f0f0f0;
+  .tab-item {
+    flex: 1;
+    text-align: center;
+    padding: 25rpx 0;
+    font-size: 28rpx;
+    color: #666;
+    position: relative;
+    &.active {
+      color: #3c82f8;
+      font-weight: 500;
+      &::after {
+        content: '';
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        transform: translateX(-50%);
+        width: 60rpx;
+        height: 6rpx;
+        background-color: #3c82f8;
+        border-radius: 3rpx;
+      }
+    }
+  }
+}
+
+.list-container {
+  flex: 1;
+  height: 100%;
+}
+.empty-list { text-align: center; color: #999; padding-top: 150rpx; }
+
+.record-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  margin: 20rpx;
+  padding: 30rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+  position: relative;
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  padding-bottom: 20rpx;
+  border-bottom: 1rpx solid #f5f5f5;
+  .record-id {
+    font-size: 30rpx;
+    font-weight: bold;
+    color: #333;
+    margin-left: 15rpx;
+  }
+}
+
+.status-badge {
+  position: absolute;
+  top: 0;
+  right: 0;
+  padding: 8rpx 20rpx;
+  font-size: 24rpx;
+  color: #fff;
+  border-top-right-radius: 16rpx;
+  border-bottom-left-radius: 16rpx;
+
+  &.pending { background-color: #3c82f8; }
+  &.verifying { background-color: #ff9900; }
+  &.verified { background-color: #909399; }
+}
+
+.card-body {
+  padding-top: 10rpx;
+}
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12rpx 0;
+  font-size: 28rpx;
+  .detail-label { color: #666; }
+  .detail-value { color: #333; }
+  &.product-item {
+    .detail-label { color: #333; }
+    .detail-value { color: #999; }
+  }
+}
+
+.card-footer {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 20rpx;
+}
+.verify-btn {
+  background-color: #3c82f8;
+  color: #fff;
+  font-size: 26rpx;
+  padding: 0 30rpx;
+  height: 60rpx;
+  line-height: 60rpx;
+  border-radius: 30rpx;
+  margin: 0;
+  &::after { border: none; }
+}
+
+.query-btn {
+  background-color: #409eff;
+  color: #fff;
+  padding: 20rpx;
+  border-radius: 10rpx;
+  height: 34rpx;
+  display: flex;
+  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>

+ 255 - 0
pages/cjx/hexiao/ywy/patrol_record.vue

@@ -0,0 +1,255 @@
+<template>
+  <view class="page-container">
+<!--    <view class="search-wrapper">-->
+<!--      <view class="search-bar">-->
+<!--        <uni-icons type="search" size="20" color="#999"></uni-icons>-->
+<!--        <input-->
+<!--            class="search-input"-->
+<!--            v-model="searchQuery"-->
+<!--            placeholder="输入门店名称/上货日期搜索"-->
+<!--            placeholder-class="placeholder"-->
+<!--            confirm-type="search"-->
+<!--            @confirm="handleSearch"-->
+<!--        />-->
+<!--      </view>-->
+<!--    </view>-->
+
+    <scroll-view class="list-container" scroll-y="true" @scrolltolower="loadMore">
+      <view v-if="recordList.length === 0 && loadStatus !== 'loading'" class="empty-list">
+        <text>暂无上货记录</text>
+      </view>
+
+      <view v-for="record in recordList" :key="record.id" class="record-card">
+        <view class="card-decorator"></view>
+        <view class="card-content">
+          <view class="card-header">
+            <uni-icons type="shop-filled" size="20" color="#3c82f8"></uni-icons>
+            <text class="record-id">{{ record.recordNo }}</text>
+          </view>
+
+          <view class="details-section">
+            <view class="detail-row">
+              <text class="detail-label">上货商品总数</text>
+              <text class="detail-value">{{ record.totalNum }}</text>
+            </view>
+
+            <view class="detail-row" v-for="item in record.queryActiveRecordDetailVos">
+              <text class="detail-label">{{ item.productName }}</text>
+              <text class="detail-value">{{ item.num }}</text>
+            </view>
+
+            <view class="detail-row product-item" v-for="(product, index) in record.products" :key="index">
+              <text class="detail-label product-name">{{ product.name }}</text>
+              <text class="detail-value">{{ product.quantity }}{{ product.unit }}</text>
+            </view>
+
+            <view class="detail-row">
+              <text class="detail-label">上货门店</text>
+              <text class="detail-value">{{ record.storeName }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">上货时间</text>
+              <text class="detail-value">{{ record.activeTime }}</text>
+            </view>
+            <view class="detail-row">
+              <text class="detail-label">上货奖励</text>
+              <text class="detail-value reward">{{ record.reward==null?"0":record.reward }}积分</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <uni-load-more :status="loadStatus"></uni-load-more>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import {queryActiveRecord} from "@/api/hexiao";
+import {xundianRecord} from "../../../../api/hexiao";
+
+export default {
+  data() {
+    return {
+      searchQuery: '',
+      recordList: [],
+      pagination: {
+        page: 1,
+        limit: 10,
+      },
+      loadStatus: 'more', // 'more'-加载前, 'loading'-加载中, 'noMore'-没有更多了
+      isLoading: false,
+    };
+  },
+  onLoad() {
+    // 页面加载时获取第一页数据
+    this.fetchRecords(true);
+  },
+  // 监听下拉刷新
+  onPullDownRefresh() {
+    this.fetchRecords(true);
+  },
+  methods: {
+    // 核心:获取记录列表数据
+    async fetchRecords(isRefresh = false) {
+      if (this.isLoading || (this.loadStatus === 'noMore' && !isRefresh)) {
+        return;
+      }
+
+      if (isRefresh) {
+        this.pagination.page = 1;
+        this.recordList = [];
+        this.loadStatus = 'more';
+      }
+
+      this.isLoading = true;
+      this.loadStatus = 'loading';
+
+      // 在这里替换成您真实的 uni.request API 调用
+      console.log(`正在请求第 ${this.pagination.page} 页数据...`);
+
+      xundianRecord(this.pagination.page,this.pagination.limit).then(res=>{
+        let mockData = res.data.records;
+        if (mockData.length > 0) {
+          this.recordList = [...this.recordList, ...mockData];
+          this.pagination.page++;
+          this.loadStatus = 'more';
+        } else {
+          this.loadStatus = 'noMore';
+        }
+        this.isLoading = false;
+        uni.stopPullDownRefresh(); // 停止下拉刷新动画
+      });
+
+
+      // --- [模拟API请求结束] ---
+
+
+
+
+    },
+
+    // 上拉加载更多
+    loadMore() {
+      this.fetchRecords();
+    },
+
+    // 搜索
+    handleSearch() {
+      // 在实际项目中,搜索应该调用API,这里为简单起见只做前端筛选
+      // this.fetchRecords(true);
+      uni.showToast({ title: '触发搜索', icon: 'none' });
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.page-container {
+  display: flex;
+  flex-direction: column;
+  height: 100vh;
+  background-color: #f5f6fa;
+}
+
+.search-wrapper {
+  padding: 20rpx;
+  background-color: #ffffff;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.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;
+}
+
+.list-container {
+  flex: 1;
+  height: 100%; // 必须给scroll-view一个明确的高度
+}
+
+.empty-list {
+  text-align: center;
+  color: #999;
+  padding-top: 150rpx;
+}
+
+.record-card {
+  background-color: #ffffff;
+  border-radius: 16rpx;
+  margin: 20rpx;
+  position: relative;
+  overflow: hidden;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
+}
+
+.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;
+  padding-bottom: 20rpx;
+  border-bottom: 1rpx solid #f5f5f5;
+
+  .record-id {
+    font-size: 30rpx;
+    font-weight: bold;
+    color: #333;
+    margin-left: 15rpx;
+  }
+}
+
+.details-section {
+  padding-top: 10rpx;
+}
+
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 15rpx 0;
+  font-size: 28rpx;
+
+  .detail-label {
+    color: #666;
+  }
+
+  .detail-value {
+    color: #333;
+  }
+
+  &.product-item .detail-label {
+    color: #333; // 商品名称颜色深一些
+    font-weight: 400;
+  }
+
+  .reward {
+    color: #ff9900;
+    font-weight: 500;
+  }
+}
+</style>