Преглед изворни кода

feat: 感冒灵大批量页面调整

贺智杰 пре 5 дана
родитељ
комит
9086d7b803

+ 7 - 0
pages.json

@@ -69,6 +69,13 @@
 					"style": {
 						"navigationBarTitleText": ""
 					}
+				},
+				{
+					"path": "traceabilityReport/pages/ganmaoling/detail/index",
+					"style": {
+						"navigationBarTitleText": "",
+						"navigationStyle": "custom"
+					}
 				}
 			]
 		}

+ 316 - 0
traceCodePackages/traceabilityReport/pages/ganmaoling/detail/index.vue

@@ -0,0 +1,316 @@
+<template>
+	<Water></Water>
+	<view class="nav" :style="{ paddingTop: statusBarHeight + 'px' }">
+		<text class="nav-back" :style="{ top: statusBarHeight + 'px' }" @click="onBack"></text>
+		<view class="nav-title">{{ title }}</view>
+	</view>
+	<view class="page">
+		<view class="header-card" :style="{ paddingTop: statusBarHeight + 60 + 'px' }">
+			<view class="meta">
+				<view class="dot"></view>
+				<text class="meta-text">所在省份:{{ regionName }}</text>
+			</view>
+			<view class="meta">
+				<view class="dot"></view>
+				<text class="meta-text">客户等级:{{
+					scanType == 1 ? "VIP" : scanType == 2 ? "二级" : "三级"
+				}}客户</text>
+			</view>
+			<view class="meta">
+				<view class="dot"></view>
+				<text class="meta-text">客户性质:{{ customerType || '--' }}</text>
+			</view>
+			<view class="meta">
+				<view class="dot"></view>
+				<text class="meta-text">责任经理:{{ managerName || '--' }}</text>
+			</view>
+			<view class="meta">
+				<view class="dot"></view>
+				<text class="meta-text">累计预警次数:{{ warningCount || warningCount == 0 ? warningCount : '--' }}</text>
+			</view>
+		</view>
+
+		<view class="section-title">流转信息</view>
+
+		<view class="card table-card">
+			<scroll-view scroll-x="true" class="table-scroll">
+				<view class="flow-table">
+					<view class="flow-header">
+						<view class="th col-time">单据时间</view>
+						<view class="th col-type">单据类型</view>
+						<view class="th col-product">产品名称</view>
+						<view class="th col-batch">批号</view>
+						<view class="th col-source">货源地</view>
+						<view class="th col-qty">数量</view>
+						<view class="th col-chain">链路</view>
+					</view>
+					<view class="flow-body">
+						<view class="flow-row" v-for="(item, index) in flowList" :key="index">
+							<view class="td col-time">{{ item.billTime }}</view>
+							<view class="td col-type">{{ item.billType }}</view>
+							<view class="td col-product">{{ item.productName }}</view>
+							<view class="td-group">
+								<view class="sub-row" v-for="(sub, sIdx) in item.items" :key="sIdx">
+									<view class="td col-batch">{{ sub.batchNo }}</view>
+									<view class="td col-source">{{ sub.source }}</view>
+									<view class="td col-qty">{{ sub.quantity }}</view>
+								</view>
+							</view>
+							<view class="td col-chain">{{ item.chain }}</view>
+						</view>
+					</view>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+import Water from "@/components/water/water.vue";
+export default {
+	components: {
+		Water,
+	},
+	data() {
+		return {
+			title: '',
+			managerName: '夜神月',
+			warningCount: 0,
+			scanType: "",
+			statusBarHeight: 20,
+			regionName: "广东",
+			customerType: "协议客户",
+			flowList: [
+				{
+					billTime: '2023-01-01 12:00',
+					billType: '销售出库',
+					productName: '感冒灵颗粒',
+					items: [
+						{ batchNo: 'A20230101', source: '广州工厂', quantity: '1000' },
+						{ batchNo: 'A20230102', source: '深圳工厂', quantity: '500' }
+					],
+					chain: '经销商A -> 药店B'
+				},
+				{
+					billTime: '2023-01-02 14:30',
+					billType: '调拨入库',
+					productName: '三九胃泰',
+					items: [
+						{ batchNo: 'B20230105', source: '北京仓库', quantity: '200' },
+						{ batchNo: 'B20230106', source: '上海仓库', quantity: '300' },
+						{ batchNo: 'B20230107', source: '武汉仓库', quantity: '150' }
+					],
+					chain: '总仓 -> 分仓'
+				}
+			]
+		}
+	},
+	onLoad(options) {
+		const info = uni.getSystemInfoSync();
+		this.statusBarHeight = info.statusBarHeight || 20;
+		this.title =
+			options && options.name ? decodeURIComponent(options.name) : "报表详情";
+	},
+	methods: {
+		onBack() {
+			try {
+				uni.navigateBack();
+			} catch (e) { }
+		},
+	},
+}
+</script>
+<style scoped>
+.nav {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	height: 44px;
+	background-color: #2c69ff;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.nav-title {
+	font-size: 36rpx;
+	color: #fff;
+	font-weight: 700;
+}
+
+.nav-back {
+	position: absolute;
+	left: 40rpx;
+	top: 12rpx;
+	width: 20rpx;
+	height: 20rpx;
+	color: #fff;
+	border-left: 3rpx solid #fff;
+	border-bottom: 3rpx solid #fff;
+	transform: rotate(45deg) translateY(56rpx);
+	margin-left: 40rpx;
+	margin-top: -6rpx;
+}
+
+.page {
+	height: 100vh;
+}
+
+.card {
+	margin: 24rpx;
+	background: #fff;
+	border-radius: 16rpx;
+	font-size: 32rpx;
+}
+
+.header-card {
+	background: #2c69ff;
+	border-bottom-left-radius: 32rpx;
+	border-bottom-right-radius: 32rpx;
+	padding: 36rpx;
+	color: #fff;
+}
+
+.meta {
+	display: flex;
+	align-items: center;
+	margin: 14rpx 0;
+}
+
+.dot {
+	width: 8rpx;
+	height: 8rpx;
+	border-radius: 50%;
+	background: #fff;
+	margin-right: 20rpx;
+}
+
+.meta-text {
+	font-size: 32rpx;
+}
+
+/* .section-title {
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+	margin: 30rpx 24rpx 20rpx;
+	padding-left: 16rpx;
+	border-left: 8rpx solid #2c69ff;
+	line-height: 1;
+} */
+
+.section-title {
+	position: relative;
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #333;
+	margin: 30rpx 30rpx 20rpx;
+}
+
+.section-title::after {
+	content: "";
+	position: absolute;
+	left: -20rpx;
+	bottom: 8rpx;
+	width: 8rpx;
+	height: 50%;
+	background: #2c69ff;
+	border-radius: 10px;
+}
+
+.table-card {
+	padding: 0;
+	overflow: hidden;
+}
+
+.table-scroll {
+	width: 100%;
+}
+
+.flow-table {
+	min-width: 1300rpx;
+	border-top: 1rpx solid #eee;
+	border-left: 1rpx solid #eee;
+}
+
+.flow-header {
+	display: flex;
+	background: #f5f7fa;
+	font-size: 26rpx;
+	font-weight: bold;
+	color: #333;
+}
+
+.flow-body {
+	font-size: 26rpx;
+	color: #666;
+}
+
+.flow-row {
+	display: flex;
+	border-bottom: 1rpx solid #eee;
+}
+
+.th,
+.td {
+	padding: 16rpx 10rpx;
+	border-right: 1rpx solid #eee;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	text-align: center;
+	word-break: break-all;
+	box-sizing: border-box;
+	flex-shrink: 0;
+}
+
+.th {
+	border-bottom: 1rpx solid #eee;
+}
+
+.col-time {
+	width: 220rpx;
+}
+
+.col-type {
+	width: 160rpx;
+}
+
+.col-product {
+	width: 200rpx;
+}
+
+.col-batch {
+	width: 180rpx;
+}
+
+.col-source {
+	width: 180rpx;
+}
+
+.col-qty {
+	width: 120rpx;
+}
+
+.col-chain {
+	width: 240rpx;
+}
+
+.td-group {
+	display: flex;
+	flex-direction: column;
+	width: 480rpx;
+	flex-shrink: 0;
+}
+
+.sub-row {
+	display: flex;
+	flex: 1;
+	border-bottom: 1rpx solid #eee;
+}
+
+.sub-row:last-child {
+	border-bottom: none;
+}
+</style>

+ 209 - 456
traceCodePackages/traceabilityReport/pages/ganmaoling/index.vue

@@ -1,554 +1,307 @@
 <template>
 	<view class="detail-page">
 		<view class="tip">数据更新时间:{{ formatDate(new Date().setDate(new Date().getDate() - 1), 'YYYY-MM-DD') || '--' }}</view>
-		<view class="card" :style="{ height: tableBodyHeight + 'rpx' }">
-			<scroll-view class="grid-scroll" scroll-y="true" scroll-x="true" :show-scrollbar="false"
-				:style="{ maxHeight: tableBodyHeight + 'rpx' }" enhanced @scrolltolower="onTableScrollToLower">
-				<view :style="{
-					width: '2685rpx',
-				}">
-					<view class="grid-header">
-						<!-- Row 1 -->
-						<text class="hcell header-group" style="grid-column: 1 / 6; grid-row: 1 / 2;">客户基础信息</text>
-						<text class="hcell header-group" style="grid-column: 6 / 15; grid-row: 1 / 2;">流转信息</text>
-						<text class="hcell header-group" style="grid-column: 15 / 16; grid-row: 1 / 2;">该企业累计预警次数</text>
-
-						<!-- Row 2 -->
-						<text class="hcell" style="grid-column: 1 / 2; grid-row: 2 / 3;">客户名称</text>
-						<text class="hcell" style="grid-column: 2 / 3; grid-row: 2 / 3;">客户所在省份</text>
-						<text class="hcell" style="grid-column: 3 / 4; grid-row: 2 / 3;">客户级别</text>
-						<text class="hcell" style="grid-column: 4 / 5; grid-row: 2 / 3;">客户性质</text>
-						<text class="hcell" style="grid-column: 5 / 6; grid-row: 2 / 3;">责任经理</text>
-						<text class="hcell" style="grid-column: 6 / 7; grid-row: 2 / 3;">收货企业名称</text>
-						<text class="hcell" style="grid-column: 7 / 8; grid-row: 2 / 3;">收货企业所在省份</text>
-						<text class="hcell" style="grid-column: 8 / 9; grid-row: 2 / 3;">单据时间</text>
-						<text class="hcell" style="grid-column: 9 / 10; grid-row: 2 / 3;">单据类型</text>
-						<text class="hcell" style="grid-column: 10 / 11; grid-row: 2 / 3;">产品名称</text>
-						<text class="hcell" style="grid-column: 11 / 12; grid-row: 2 / 3;">批号</text>
-						<text class="hcell" style="grid-column: 12 / 13; grid-row: 2 / 3;">发货地</text>
-						<text class="hcell" style="grid-column: 13 / 14; grid-row: 2 / 3;">数量</text>
-						<text class="hcell" style="grid-column: 14 / 15; grid-row: 2 / 3;">链路</text>
-						<text class="hcell" style="grid-column: 15 / 16; grid-row: 2 / 3;">累计预警次数</text>
-					</view>
-
-					<view v-if="!loading && rows.length > 0" class="grid-body">
-						<view v-for="(row, idx) in rows" :key="idx" class="grow">
-							<!-- Common Columns (merged) -->
-							<view v-if="row.flags.common" class="gcell gcol-1 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerName }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-2 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerProvince }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-3 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerLevel }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-4 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.customerNature }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-5 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.manager }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-6 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.receiverName }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-7 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.receiverProvince }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-8 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.docTime }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-9 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.docType }}</view>
-							<view v-if="row.flags.common" class="gcell gcol-10 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.productName }}</view>
-							
-							<!-- Detail Columns (not merged) -->
-							<view class="gcell gcol-11">{{ row.batchNo }}</view>
-							<view class="gcell gcol-12">{{ row.sourceRegion }}</view>
-							<view class="gcell gcol-13">{{ row.quantity }}</view>
-
-							<!-- Link Column (merged) -->
-							<view v-if="row.flags.common" class="gcell gcol-14 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common }">{{ row.traceLink }}</view>
-
-							<view v-if="row.flags.common" class="gcell gcol-15 gspan"
-								:style="{ gridRowEnd: 'span ' + row.spans.common, borderRight: 'none' }">{{ row.alertCount }}</view>
-						</view>
-						<view class="gcell gcell-full loading-row" v-if="hasMore">
-							<view class="loading-wrapper">
-								<image class="loading-icon" src="../../../static/images/loading.png" />
+		
+		<scroll-view 
+			class="list-scroll" 
+			scroll-y="true" 
+			refresher-enabled 
+			:refresher-triggered="isRefreshing"
+			@refresherrefresh="onRefresh"
+			@scrolltolower="onLoadMore"
+		>
+			<view class="list-container">
+				<view 
+					class="card-item" 
+					v-for="(item, index) in rows" 
+					:key="index"
+					@click="toDetail(item)"
+				>
+					<view class="left-info">
+						<view class="row1-name">{{ item.receiverName }}</view>
+						<view class="row2-info">
+							<text class="province">{{ item.receiverProvince }}</text>
+							<view class="level-tag" :class="getLevelClass(item.customerLevel)">
+								{{ item.customerLevel }}
 							</view>
 						</view>
+						<view class="row3-nature">{{ item.customerNature }}</view>
+						<view class="row4-manager">{{ item.manager }}</view>
 					</view>
-				</view>
-			</scroll-view>
-			<view v-if="loading" class="loading-wrap">
-				<view class="loading-row">
-					<view class="loading-wrapper">
-						<image class="loading-icon" src="../../../static/images/loading.png" />
+					
+					<view class="right-info">
+						<text class="alert-count">{{ item.alertCount }}次预警</text>
 					</view>
 				</view>
+				
+				<view class="loading-more" v-if="loading">
+					<image class="loading-icon" src="../../../static/images/loading.png" />
+				</view>
+				
+				<view v-if="!loading && rows.length === 0" class="empty-data">
+					<EmptyView text="无相关数据" />
+				</view>
+                
+                <view v-if="!hasMore && rows.length > 0" class="no-more">
+                    <text>没有更多数据了</text>
+                </view>
 			</view>
-			<view v-if="showEmptyData" class="empty-data">
-				<EmptyView text="无相关数据" />
-			</view>
-		</view>
-		<view class="fab-btn" @click="downloadTable">
-			<text class="fab-text">下载表格</text>
-		</view>
+		</scroll-view>
 	</view>
 </template>
 
 <script>
 import EmptyView from "../../../wigets/empty.vue";
-import request, { downloadFile } from '../../../request/index.js'
+import request from '../../../request/index.js'
 import { formatDate } from '../../../utils/utils.js'
+
 export default {
 	components: {
 		EmptyView,
 	},
 	data() {
 		return {
-			isLoading: false,
-			tableBodyHeight: 0,
-			baseRowHeight: 76,
+			isRefreshing: false,
 			loading: false,
 			rows: [],
-			totalCount: 0,
-			fetchLoading: false,
-			hasMore: false,
+			totalCount: 60, // Simulated total count
+			hasMore: true,
 			pageNum: 1,
-			pageSize: 15,
+			pageSize: 20,
 		};
 	},
 	created() {
-		this.tableBodyHeight = 14 * 80;
-		// this.fetchList();
-		// this.generateFakeData();
-	},
-	computed: {
-		showEmptyData() {
-			if (this.loading) return false;
-			if (this.rows.length === 0) {
-				return true;
-			}
-			return false;
-		},
+		this.resetFetch();
 	},
 	methods: {
 		formatDate,
+		
+		getLevelClass(level) {
+			if (level === 'VIP') return 'tag-vip';
+			if (level === '二级') return 'tag-l2';
+			if (level === '三级') return 'tag-l3';
+			return 'tag-default';
+		},
+
 		generateFakeData() {
-			const fakeRows = [];
-			const baseCommonData = {
-				customerProvince: '广东',
-				customerLevel: '一级',
-				customerNature: '商业',
-				manager: '张三',
-				receiverProvince: '广西',
-				docTime: '2023-01-01',
-				docType: '销售出库',
-				productName: '感冒灵颗粒',
-				traceLink: 'A -> B -> C'
-			};
-			
-			for (let i = 0; i < 15; i++) {
-				// 模拟一个单据有 1-3 个批号明细
-				const detailCount = Math.floor(Math.random() * 3) + 1;
-				const details = [];
-				for (let j = 0; j < detailCount; j++) {
-					details.push({
-						batchNo: 'BATCH' + i + '-' + j,
-						sourceRegion: j % 2 === 0 ? '华南片区' : '华东片区',
-						quantity: 100 + j * 50
-					});
-				}
+			const newRows = [];
+			const levels = ['VIP', '二级', '三级'];
+			const provinces = ['北京市', '上海市', '广东省', '浙江省', '江苏省'];
+			const natures = ['协议客户', '非协议客户'];
+			const managers = ['张明华', '李建华', '王丽萍', '陈大文'];
+
+			const startIdx = (this.pageNum - 1) * this.pageSize;
+			const endIdx = Math.min(startIdx + this.pageSize, this.totalCount);
+
+			if (startIdx >= this.totalCount) {
+				this.hasMore = false;
+				return [];
+			}
 
-				// 公共数据
-				const commonData = {
-					...baseCommonData,
-					alertCount: String(i + 1), // 显式转为字符串
-					customerName: '测试客户' + i,
-					receiverName: '测试收货企业' + i,
-				};
-
-				// 生成扁平化行数据
-				details.forEach((detail, index) => {
-					fakeRows.push({
-						...commonData,
-						...detail,
-						spans: {
-							common: index === 0 ? detailCount : 0 // 首行 span 为明细总数,其他为 0
-						},
-						flags: {
-							common: index === 0 // 仅首行显示公共列
-						}
-					});
+			for (let i = startIdx; i < endIdx; i++) {
+				newRows.push({
+					id: i,
+					receiverName: `测试收货企业${i + 1}有限公司`,
+					receiverProvince: provinces[i % provinces.length],
+					customerLevel: levels[i % levels.length],
+					customerNature: natures[i % natures.length],
+					manager: managers[i % managers.length],
+					alertCount: Math.floor(Math.random() * 10) + 1,
 				});
 			}
-			this.rows = fakeRows;
-			this.loading = false;
+			return newRows;
 		},
-		// computeBlackLeafCount(node) {
-		// 	if (!node || !node.dataList || !node.dataList.length) return 1;
-		// 	let sum = 0;
-		// 	node.dataList.forEach((c) => {
-		// 		sum += this.computeBlackLeafCount(c);
-		// 	});
-		// 	node.leafCount = sum;
-		// 	return sum;
-		// },
-		// buildBlackRows(data) {
-		// 	const res = [];
-		// 	(data || []).forEach((companyNode) => {
-		// 		const cLeaves = this.computeBlackLeafCount(companyNode);
-		// 		let companyStarted = false;
-		// 		(companyNode.dataList || []).forEach((expNode) => {
-		// 			const eLeaves = this.computeBlackLeafCount(expNode);
-		// 			let exporterStarted = false;
-		// 			const groups = {};
-		// 			(expNode.dataList || []).forEach((item) => {
-		// 				const key = item.currentENTName || "";
-		// 				if (!groups[key]) groups[key] = [];
-		// 				groups[key].push(item);
-		// 			});
-		// 			Object.keys(groups).forEach((prodName) => {
-		// 				const group = groups[prodName];
-		// 				let productStarted = false;
-		// 				group.forEach((batch) => {
-		// 					res.push({
-		// 						entName: companyNode.entName,
-		// 						orgCode: companyNode.orgCode,
-		// 						time: batch.time,
-		// 						physicName: expNode.physicName,
-		// 						currentENTName: prodName,
-		// 						fromRegionName: batch.fromRegionName,
-		// 						fromQuantity: batch.fromQuantity,
-		// 						produceBatchNo: batch.produceBatchNo,
-		// 						tracCode: batch.tracCode,
-		// 						toQuantity: batch.toQuantity,
-		// 						spans: {
-		// 							entName: companyStarted ? 0 : cLeaves,
-		// 							physicName: exporterStarted ? 0 : eLeaves,
-		// 							currentENTName: productStarted ? 0 : group.length,
-		// 						},
-		// 						flags: {
-		// 							entName: !companyStarted,
-		// 							physicName: !exporterStarted,
-		// 							currentENTName: !productStarted,
-		// 						},
-		// 					});
-		// 					companyStarted = true;
-		// 					exporterStarted = true;
-		// 					productStarted = true;
-		// 				});
-		// 			});
-		// 		});
-		// 	});
-		// 	return res;
-		// },
-		onTableScrollToLower(e) {
-			if (e?.detail?.direction === 'right') return
-			// if (this.isLoading) return;
-			// if (this.rows.length >= this.totalCount) return;
-			// this.isLoading = true;
-			// const remain = this.totalCount - this.rows.length;
-			// const toAdd = Math.min(15, remain);
-			// setTimeout(() => {
-			//   const more = this.rows.slice(0, toAdd);
-			//   this.rows = this.rows.concat(more);
-			//   this.isLoading = false;
-			// }, 800);
-			// this.fetchList();
-		},
-		resetFetch() {
-			// this.loading = true;
-			// this.rows = [];
-			// this.totalCount = 0;
-			// this.fetchLoading = false;
-			// this.hasMore = true;
-			// this.pageNum = 1;
-			// this.fetchList();
-			this.generateFakeData();
+
+		async onRefresh() {
+			this.isRefreshing = true;
+			this.pageNum = 1;
+			this.hasMore = true;
+			// Simulate network request
+			setTimeout(() => {
+				this.rows = this.generateFakeData();
+				this.isRefreshing = false;
+			}, 1000);
 		},
-		fetchList() {
-			if (this.fetchLoading || !this.hasMore) return;
-			this.fetchLoading = true;
-			let url = `/report/getBlacklistReport`;
-			try {
-				request(url, {
-					pageNum: this.pageNum,
-					pageSize: this.pageSize,
-					path: '/blacklist/index.vue',
-				}).then(res => {
-					if (res.code == 200) {
-						const { total, list } = res.data || {};
-						this.totalCount = total || 0;
-						let _list = [];
-						if (Array.isArray(list)) {
-							// _list = this.buildBlackRows(list);
-						}
-						this.rows = [...this.rows, ..._list];
-						if (this.rows.length < this.totalCount) {
-							this.hasMore = true;
-							this.pageNum++;
-						} else {
-							this.hasMore = false;
-						}
-					}
-					this.isLoading = false;
-					this.loading = false;
-					this.fetchLoading = false;
-				}).catch(() => {
-					this.isLoading = false;
-					this.loading = false;
-					this.fetchLoading = false;
-				})
-			} catch (res) {
-				this.isLoading = false;
+
+		onLoadMore() {
+			if (this.loading || !this.hasMore) return;
+			this.loading = true;
+			this.pageNum++;
+			
+			// Simulate network request
+			setTimeout(() => {
+				const more = this.generateFakeData();
+				if (more.length > 0) {
+					this.rows = [...this.rows, ...more];
+				} else {
+					this.hasMore = false;
+				}
 				this.loading = false;
-				this.fetchLoading = false;
-			}
+			}, 800);
 		},
-		downloadTable() {
-			downloadFile('', {
-				id: '',
-				path: '/blacklist/index.vue',
-			})
+
+		resetFetch() {
+			this.loading = true;
+			this.pageNum = 1;
+			this.hasMore = true;
+			// Simulate initial load
+			setTimeout(() => {
+				this.rows = this.generateFakeData();
+				this.loading = false;
+			}, 500);
 		},
+
+		toDetail(item) {
+			uni.navigateTo({
+				url: `/traceCodePackages/traceabilityReport/pages/ganmaoling/detail/index?id=${item.id}&name=${encodeURIComponent(item.receiverName)}`
+			});
+		}
 	},
 };
 </script>
 
 <style scoped>
 .detail-page {
+	display: flex;
+	flex-direction: column;
+	height: calc(100vh - 116rpx - env(safe-area-inset-bottom));
 	box-sizing: border-box;
-	padding: 24rpx;
-	position: relative;
 	background: #f3f6f9;
-	padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
 }
 
 .tip {
 	font-size: 24rpx;
 	color: #999;
-	margin-bottom: 30rpx;
-}
-
-.card {
-	position: relative;
-	margin-top: 12rpx;
-	font-size: 30rpx;
-	overflow: hidden;
+	padding: 24rpx;
+	background: #f3f6f9;
 }
 
-	.grid-header {
-		position: sticky;
-		top: 0;
-		z-index: 10;
-		display: grid;
-		grid-template-columns: 220rpx 160rpx 140rpx 140rpx 140rpx 220rpx 160rpx 200rpx 140rpx 200rpx 180rpx 160rpx 140rpx 300rpx 200rpx;
-		grid-auto-rows: 76rpx;
-	}
-
-	.grid-header .hcell {
-		background: #eaf2ff;
-		font-weight: bold;
-		color: #2c69ff;
-		border-bottom: 1rpx solid #d0d7de;
-		border-right: 1rpx solid #d0d7de;
-	}
-
-	.header-group {
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		background: #dae8ff !important;
-	}
-
-	.hcell {
-		height: 100%;
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		text-align: center;
-		font-size: 26rpx;
-		padding: 0 10rpx;
-	}
-
-	.hcell:last-child {
-		border-right: none;
-	}
-
-	.hcell-sticky-left {
-		position: sticky;
-		left: 0;
-		z-index: 11;
-	}
-
-	.grid-scroll {
-		border-radius: 16rpx;
-		margin-top: 8rpx;
-		overflow: hidden;
-	}
-
-	.grid-body {
-		background: #fff;
-		display: grid;
-		grid-template-columns: 220rpx 160rpx 140rpx 140rpx 140rpx 220rpx 160rpx 200rpx 140rpx 200rpx 180rpx 160rpx 140rpx 300rpx 200rpx;
-		grid-auto-rows: auto;
-	}
-
-	.grow {
-		display: contents;
-	}
-
-	.gcell {
-		display: flex;
-		align-items: center;
-		justify-content: center;
-		border-bottom: 1rpx solid #d0d7de;
-		border-right: 1rpx solid #d0d7de;
-		background: #fff;
-		color: #333;
-		padding: 0 10rpx;
-		text-align: center;
-		line-height: 40rpx;
-		min-height: 76rpx;
-		font-size: 26rpx;
-		word-break: break-all;
-	}
-
-.gcell:last-child {
-	/* border-right: none; */
+.list-scroll {
+	flex: 1;
+	height: 0; /* Important for flex expansion */
+	padding: 0 24rpx;
+    box-sizing: border-box;
 }
 
-.gcell.gcell-full {
-	grid-column: 1 / -1;
-	background: transparent;
-	border: none;
+.list-container {
+	padding-bottom: calc(50rpx + env(safe-area-inset-bottom));
+    padding-left: 24rpx;
+    padding-right: 24rpx;
 }
 
-.gspan {
+.card-item {
+	display: flex;
+	justify-content: space-between;
+	align-items: flex-start; /* Align top */
 	background: #fff;
+	border-radius: 16rpx;
+	padding: 30rpx;
+	margin-bottom: 20rpx;
+	box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
 }
 
-.gcol-1 {
-	grid-column: 1;
-}
-
-.gcol-2 {
-	grid-column: 2;
-}
-
-.gcol-3 {
-	grid-column: 3;
-}
-
-.gcol-4 {
-	grid-column: 4;
-}
-
-.gcol-5 {
-	grid-column: 5;
-}
-
-.gcol-6 {
-	grid-column: 6;
+.left-info {
+	flex: 1;
+	margin-right: 20rpx;
 }
 
-.gcol-7 {
-	grid-column: 7;
+.row1-name {
+	font-size: 30rpx;
+	font-weight: bold;
+	color: #333;
+	margin-bottom: 16rpx;
 }
 
-.gcol-8 {
-	grid-column: 8;
+.row2-info {
+	display: flex;
+	align-items: center;
+	margin-bottom: 12rpx;
 }
 
-.gcol-9 {
-	grid-column: 9;
+.province {
+	font-size: 26rpx;
+	color: #666;
+	margin-right: 20rpx;
 }
 
-.gcol-10 {
-	grid-column: 10;
+.level-tag {
+	font-size: 22rpx;
+	padding: 4rpx 12rpx;
+	border-radius: 6rpx;
+	background: #f0f0f0;
+	color: #666;
 }
 
-.gcol-11 {
-	grid-column: 11;
+.tag-vip {
+	background: #e6f7ff;
+	color: #1890ff;
 }
 
-.gcol-12 {
-	grid-column: 12;
+.tag-l2 {
+	background: #f6ffed;
+	color: #52c41a;
 }
 
-.gcol-13 {
-	grid-column: 13;
+.tag-l3 {
+	background: #fff7e6;
+	color: #fa8c16;
 }
 
-.gcol-14 {
-	grid-column: 14;
+.row3-nature {
+	font-size: 26rpx;
+	color: #666;
+	margin-bottom: 12rpx;
 }
 
-.gcol-15 {
-	grid-column: 15;
+.row4-manager {
+	font-size: 26rpx;
+	color: #666;
 }
 
-.sticky-left {
-	position: sticky;
-	left: 0;
-	z-index: 9;
-	background: #fff;
+.right-info {
+	display: flex;
+	align-items: center;
+    align-self: center; /* Center vertically relative to card */
 }
 
-.loading-row {
-	justify-content: flex-start;
+.alert-count {
+	background: #fff2f0;
+	color: #ff4d4f;
+	font-size: 24rpx;
+	padding: 8rpx 20rpx;
+	border-radius: 30rpx;
+	font-weight: bold;
 }
 
-.loading-wrapper {
-	position: sticky;
-	top: 50%;
-	left: 0;
-	width: calc(100vw - 48rpx);
-	height: 76rpx;
+.loading-more {
 	display: flex;
-	align-items: center;
 	justify-content: center;
+	align-items: center;
+	padding: 20rpx 0;
 }
 
 .loading-icon {
-	width: 40rpx;
-	height: 40rpx;
+	width: 32rpx;
+	height: 32rpx;
+	margin-right: 10rpx;
 	animation: spin 1s linear infinite;
 }
 
-@keyframes spin {
-	from {
-		transform: rotate(0deg);
-	}
-
-	to {
-		transform: rotate(360deg);
-	}
-}
-
-.empty-data,
-.loading-wrap {
-	position: absolute;
-	left: 0;
-	top: 152rpx;
-	z-index: 91;
-	width: 100%;
-	background-color: #fff;
-	border-bottom-left-radius: 16rpx;
-	border-bottom-right-radius: 16rpx;
+.empty-data {
+    display: flex;
+    justify-content: center;
+    padding-top: 100rpx;
 }
 
-.fab-btn {
-	position: fixed;
-	bottom: calc(50rpx + env(safe-area-inset-bottom));
-	right: 30rpx;
-	background-color: #007aff;
-	color: #ffffff;
-	padding: 20rpx 40rpx;
-	border-radius: 50rpx;
-	box-shadow: 0 4rpx 10rpx rgba(0, 0, 0, 0.2);
-	z-index: 92;
+.no-more {
+    text-align: center;
+    color: #999;
+    font-size: 24rpx;
+    padding: 20rpx 0;
 }
 
-.fab-text {
-	font-size: 28rpx;
-	font-weight: bold;
+@keyframes spin {
+	from { transform: rotate(0deg); }
+	to { transform: rotate(360deg); }
 }
-</style>
+</style>