shish 1 день назад
Родитель
Сommit
2e64e072ee

+ 502 - 0
hdh5/admin/ghs/balanceChange.vue

@@ -0,0 +1,502 @@
+<template>
+	<!--
+		H5 余额变动明细页
+		用途:扫码付款场景查看与供货商的余额流水;与小程序共用 /ghs-balance-change/list
+		调用方:admin/ghs/pay 的「查看余额明细」
+	-->
+	<view class="bc-page">
+		<view class="nav-bar">
+			<view class="nav-back" @click="goBack">
+				<text class="back-arrow">&lt;</text>
+			</view>
+			<view class="nav-title">余额变动</view>
+			<view class="nav-placeholder"></view>
+		</view>
+		<view class="nav-bar-placeholder"></view>
+
+		<view v-if="loading && page === 1" class="bc-status">加载中...</view>
+		<view v-else-if="loadError" class="bc-status bc-status--error">{{ loadError }}</view>
+		<template v-else>
+			<view v-if="list.length > 0" class="bc-sheet">
+				<view class="bc-head">
+					<text class="bc-th bc-th--time">时间</text>
+					<text class="bc-th bc-th--event">事项</text>
+					<text class="bc-th bc-th--amt">变动</text>
+					<text class="bc-th bc-th--bal">余额</text>
+				</view>
+				<view class="bc-body">
+					<view
+						v-for="(item, index) in list"
+						:key="index"
+						class="bc-item"
+						:class="{ 'bc-item--alt': index % 2 === 1 }"
+					>
+						<view
+							class="bc-row-main"
+							:class="{ 'bc-row-main--with-extra': item.showExtraRow }"
+						>
+							<view class="bc-td bc-td--time">
+								<text class="bc-date">{{ item.dateText }}</text>
+								<text class="bc-clock">{{ item.timeLine }}</text>
+							</view>
+							<view class="bc-td bc-td--event">
+								<text class="bc-event">{{ item.event }}</text>
+								<text v-if="item.showStaff" class="bc-meta">{{ item.staffName }}</text>
+								<text v-if="item.showRemark" class="bc-meta bc-meta--remark">{{ item.remark }}</text>
+							</view>
+							<view class="bc-td bc-td--amt">
+								<text class="bc-amount" :class="item.amountClassName">{{ item.amount }}</text>
+							</view>
+							<view class="bc-td bc-td--bal">
+								<text class="bc-balance">{{ item.balanceText }}</text>
+							</view>
+						</view>
+						<view v-if="item.showExtraRow" class="bc-row-extra">
+							<view class="bc-tags-row">
+								<text
+									v-if="item.showRefundTag"
+									class="bc-tag bc-tag--refund"
+								>{{ item.refundTagText }}</text>
+								<text
+									v-if="item.showClearLink"
+									class="bc-tag bc-tag--link"
+								>{{ item.clearLinkText }}</text>
+							</view>
+						</view>
+					</view>
+				</view>
+				<view v-if="loadingMore" class="bc-status bc-status--more">加载更多...</view>
+				<view v-else-if="finished" class="bc-status bc-status--more">没有更多了</view>
+			</view>
+			<view v-else class="bc-empty">
+				<text class="bc-empty__text">暂无余额变动记录</text>
+			</view>
+		</template>
+	</view>
+</template>
+
+<script>
+import { balanceChangeList } from '@/api/ghs'
+
+export default {
+	name: 'ghsBalanceChangeH5',
+	data() {
+		return {
+			ghsId: 0,
+			salt: '',
+			list: [],
+			page: 1,
+			pageSize: 20,
+			loading: false,
+			loadingMore: false,
+			finished: false,
+			loadError: ''
+		}
+	},
+	onLoad(options) {
+		this.ghsId = options.ghsId || options.id || 0
+		this.salt = options.salt || ''
+		if (!this.ghsId) {
+			this.loadError = '参数错误,缺少供货商信息'
+			return
+		}
+		this.fetchList(true)
+	},
+	onReachBottom() {
+		if (!this.finished && !this.loading && !this.loadingMore) {
+			this.fetchList(false)
+		}
+	},
+	onPullDownRefresh() {
+		this.fetchList(true).finally(() => {
+			uni.stopPullDownRefresh()
+		})
+	},
+	methods: {
+		goBack() {
+			uni.navigateBack({ delta: 1 })
+		},
+		formatMoney(val) {
+			const n = parseFloat(val)
+			return isNaN(n) ? '0' : String(n)
+		},
+		hasText(val) {
+			return val !== undefined && val !== null && String(val).trim() !== ''
+		},
+		/** 结账类支出行不展示(与小程序一致) */
+		shouldHideRow(item) {
+			return item.io == 0 && item.event && String(item.event).indexOf('结账') === 0
+		},
+		/** 映射列表行展示字段 */
+		mapListItem(item, amountDisplay) {
+			const row = Object.assign({}, item)
+			row.amount = amountDisplay
+			const text = String(amountDisplay || '')
+			row.amountClassName = (text.indexOf('+') === 0 || Number(row.io) === 1) ? 'bc-amount--in' : 'bc-amount--out'
+			const addTime = row.addTime || ''
+			if (addTime.length >= 16) {
+				row.dateText = addTime.substr(5, 5)
+				row.timeLine = addTime.substr(11, 5)
+			} else if (addTime.length >= 11) {
+				row.dateText = addTime.substr(0, 10)
+				row.timeLine = addTime.substr(11)
+			} else {
+				row.dateText = addTime
+				row.timeLine = ''
+			}
+			row.balanceText = this.formatMoney(row.balance)
+			const clearSn = row.clearSn || ''
+			const clearAmt = this.formatMoney(row.clearAmount)
+			row.showClearLink = Number(row.clearId) > 0 || this.hasText(clearSn)
+			row.clearLinkText = '结账单 ' + clearSn + ' · 本单销' + clearAmt + '元'
+			row.showStaff = this.hasText(row.staffName)
+			row.showRemark = this.hasText(row.remark)
+			const eventText = String(row.event || '')
+			row.showRefundTag = Number(row.capitalType) === 82 || eventText.indexOf('充值退款') >= 0
+			row.refundTagText = row.showStaff
+				? '充值退款 · 操作人 ' + row.staffName
+				: '充值退款'
+			row.showExtraRow = row.showClearLink || row.showRefundTag
+			return row
+		},
+		/**
+		 * 拉取余额变动列表
+		 * @param {boolean} reset 是否从第一页刷新
+		 */
+		async fetchList(reset) {
+			if (reset) {
+				this.page = 1
+				this.finished = false
+				this.loadError = ''
+				this.loading = true
+			} else {
+				this.loadingMore = true
+			}
+			try {
+				const res = await balanceChangeList({
+					page: this.page,
+					pageSize: this.pageSize,
+					ghsId: this.ghsId,
+					salt: this.salt
+				})
+				if (!res || res.code !== 1) {
+					if (reset) {
+						this.loadError = (res && res.msg) || '加载失败'
+						this.list = []
+					}
+					this.finished = true
+					return
+				}
+				const rawList = (res.data && res.data.list) ? res.data.list : []
+				const rows = []
+				rawList.forEach((item) => {
+					if (this.shouldHideRow(item)) {
+						return
+					}
+					const amountDisplay = item.io == 0
+						? '-' + parseFloat(item.amount)
+						: '+' + parseFloat(item.amount)
+					rows.push(this.mapListItem(item, amountDisplay))
+				})
+				this.list = reset ? rows : this.list.concat(rows)
+				const moreData = res.data && res.data.moreData
+				if (!moreData) {
+					this.finished = true
+				} else {
+					this.page += 1
+				}
+			} catch (e) {
+				if (reset) {
+					this.loadError = '网络异常,请稍后重试'
+					this.list = []
+				}
+				this.finished = true
+			} finally {
+				this.loading = false
+				this.loadingMore = false
+			}
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.bc-page {
+	min-height: 100vh;
+	background: #fff;
+	box-sizing: border-box;
+}
+
+.nav-bar {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	z-index: 100;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	padding: 20upx 24upx;
+	padding-top: calc(20upx + env(safe-area-inset-top));
+	background: #fff;
+	border-bottom: 1upx solid #f0f0f0;
+	box-sizing: border-box;
+}
+
+.nav-back,
+.nav-placeholder {
+	width: 72upx;
+	height: 72upx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.back-arrow {
+	font-size: 40upx;
+	color: #333;
+	font-weight: bold;
+	line-height: 1;
+}
+
+.nav-title {
+	flex: 1;
+	text-align: center;
+	font-size: 34upx;
+	font-weight: 600;
+	color: #222;
+}
+
+.nav-bar-placeholder {
+	height: calc(112upx + env(safe-area-inset-top));
+}
+
+.bc-status {
+	padding: 80upx 30upx;
+	text-align: center;
+	font-size: 28upx;
+	color: #999;
+}
+
+.bc-status--error {
+	color: #e64340;
+}
+
+.bc-status--more {
+	padding: 30upx;
+	font-size: 24upx;
+}
+
+.bc-empty {
+	padding: 160upx 30upx;
+	text-align: center;
+}
+
+.bc-empty__text {
+	font-size: 28upx;
+	color: #999;
+}
+
+.bc-sheet {
+	width: 100%;
+	background: #fff;
+}
+
+.bc-head {
+	display: flex;
+	align-items: center;
+	padding: 22upx 0;
+	background: #f8f9fb;
+	border-bottom: 1upx solid #eceef2;
+}
+
+.bc-th {
+	font-size: 28upx;
+	color: #8c8c8c;
+	font-weight: 500;
+	line-height: 1.2;
+}
+
+.bc-th--time {
+	width: 100upx;
+	flex-shrink: 0;
+	padding-left: 8upx;
+	box-sizing: border-box;
+}
+
+.bc-th--event {
+	flex: 1;
+	min-width: 0;
+	padding-right: 12upx;
+}
+
+.bc-th--amt {
+	width: 118upx;
+	flex-shrink: 0;
+	text-align: right;
+}
+
+.bc-th--bal {
+	width: 140upx;
+	flex-shrink: 0;
+	padding-right: 8upx;
+	text-align: right;
+	box-sizing: border-box;
+}
+
+.bc-item {
+	border-bottom: 1upx solid #f2f3f5;
+	box-sizing: border-box;
+}
+
+.bc-item:last-child {
+	border-bottom: none;
+}
+
+.bc-item--alt {
+	background: #fafbfc;
+}
+
+.bc-row-main {
+	display: flex;
+	align-items: flex-start;
+	padding: 24upx 0;
+	box-sizing: border-box;
+}
+
+.bc-row-main--with-extra {
+	padding-bottom: 0;
+}
+
+.bc-row-extra {
+	width: 100%;
+	padding: 12upx 0 20upx;
+	box-sizing: border-box;
+}
+
+.bc-tags-row {
+	display: flex;
+	flex-direction: row;
+	flex-wrap: wrap;
+	align-items: flex-start;
+	width: 100%;
+	padding-left: 8upx;
+	padding-right: 8upx;
+	box-sizing: border-box;
+}
+
+.bc-td--time {
+	width: 100upx;
+	flex-shrink: 0;
+	padding-left: 8upx;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: center;
+	box-sizing: border-box;
+}
+
+.bc-td--event {
+	flex: 1;
+	min-width: 0;
+	padding-right: 12upx;
+}
+
+.bc-td--amt {
+	width: 118upx;
+	flex-shrink: 0;
+	padding-right: 8upx;
+	display: flex;
+	justify-content: flex-end;
+	box-sizing: border-box;
+}
+
+.bc-td--bal {
+	width: 140upx;
+	flex-shrink: 0;
+	padding-right: 8upx;
+	display: flex;
+	justify-content: flex-end;
+	box-sizing: border-box;
+}
+
+.bc-date,
+.bc-clock {
+	font-size: 28upx;
+	color: #2c2c2c;
+	line-height: 1.35;
+}
+
+.bc-clock {
+	margin-top: 4upx;
+}
+
+.bc-event {
+	display: block;
+	font-size: 28upx;
+	color: #1a1a1a;
+	font-weight: 500;
+	line-height: 1.45;
+	word-break: break-all;
+}
+
+.bc-meta {
+	display: block;
+	margin-top: 8upx;
+	font-size: 22upx;
+	color: #999;
+	line-height: 1.4;
+	word-break: break-all;
+}
+
+.bc-meta--remark {
+	color: #b3b3b3;
+}
+
+.bc-tag {
+	display: block;
+	max-width: 100%;
+	padding: 6upx 14upx;
+	margin-top: 10upx;
+	margin-right: 12upx;
+	font-size: 28upx;
+	line-height: 1.4;
+	border-radius: 8upx;
+	word-break: break-all;
+	box-sizing: border-box;
+}
+
+.bc-tags-row .bc-tag:first-child {
+	margin-top: 0;
+}
+
+.bc-tag--link {
+	color: #969aa0;
+	background: #eef4ff;
+}
+
+.bc-tag--refund {
+	color: #cf1322;
+	background: #fff1f0;
+}
+
+.bc-amount {
+	font-size: 28upx;
+	font-weight: 600;
+	line-height: 1.35;
+}
+
+.bc-amount--in {
+	color: #07a84c;
+}
+
+.bc-amount--out {
+	color: #e62e45;
+}
+
+.bc-balance {
+	font-size: 28upx;
+	color: #131212;
+	line-height: 1.35;
+}
+</style>

+ 113 - 37
hdh5/admin/ghs/pay.vue

@@ -10,23 +10,35 @@
 			<button class="clear-amount-btn" @click="changeAmount(0)">清空金额</button>
 		</view>
 
-		<!-- 客户信息卡片 -->
+		<!-- 客户信息:金额独占;待结订单/余额明细沉底并排 -->
 		<view class="customer-info-card">
 			<view class="card-header">
 				<view class="customer-avatar">
 					<text class="avatar-text">{{ customInfo.name ? customInfo.name.charAt(0) : '客' }}</text>
 				</view>
 				<view class="customer-details">
-					<text class="customer-name">{{ customInfo.name || '客户' }}</text>
-					<text class="customer-status has-debt" @click="goGathering">
-						查看待结订单
-					</text>
+					<text class="customer-name">{{ displayCustomName }}</text>
 				</view>
 			</view>
-			<view class="debt-info" v-if="Number(customInfo.remainDebtAmount) > 0">
-				<view class="debt-amount" @click="changeAmount(customInfo.remainDebtAmount)">
+			<view class="debt-info" v-if="hasOutstanding">
+				<view class="debt-amount" @click="changeAmount(outstandingAmount)">
 					<text class="debt-label">待结金额</text>
-					<text class="debt-value">¥{{ customInfo.remainDebtAmount }}</text>
+					<text class="debt-value">¥{{ outstandingAmount }}</text>
+				</view>
+			</view>
+			<!-- 次级入口:有待结双按钮等宽,无待结时单按钮半宽 -->
+			<view class="pay-entry-row" :class="{ 'pay-entry-row--single': !showGatheringBtn }">
+				<view
+					v-if="showGatheringBtn"
+					class="pay-entry-btn pay-entry-btn--debt"
+					@click="goGathering">
+					<text class="pay-entry-btn__text">查看待结订单</text>
+				</view>
+				<view
+					class="pay-entry-btn"
+					:class="{ 'pay-entry-btn--disabled': !id }"
+					@click="goBalanceChange">
+					<text class="pay-entry-btn__text">查看余额明细</text>
 				</view>
 			</view>
 		</view>
@@ -134,6 +146,31 @@ export default {
 		canPay() {
 			const amount = parseFloat(this.currentAmount);
 			return amount > 0;
+		},
+		/** 客户名最多 7 字,避免挤占入口区 */
+		displayCustomName() {
+			const name = (this.customInfo && this.customInfo.name) ? String(this.customInfo.name) : '客户'
+			if (name.length > 7) {
+				return name.slice(0, 7) + '...'
+			}
+			return name
+		},
+		/** 待结金额:优先 remainDebtAmount,否则净 balance 负值取绝对值 */
+		outstandingAmount() {
+			const info = this.customInfo || {}
+			const remain = parseFloat(info.remainDebtAmount || 0)
+			if (remain > 0) {
+				return parseFloat(remain.toFixed(2))
+			}
+			const bal = parseFloat(info.balance || 0)
+			return bal < 0 ? parseFloat(Math.abs(bal).toFixed(2)) : 0
+		},
+		hasOutstanding() {
+			return this.outstandingAmount > 0
+		},
+		/** 有待结时才展示「查看待结订单」 */
+		showGatheringBtn() {
+			return this.hasOutstanding && !!this.id
 		}
 	},
 	onLoad(options) {
@@ -385,8 +422,19 @@ export default {
 				url: `/pagesPurchase/gathering?id=${this.id}&salt=${this.salt}`
 			});
 		},
-
-
+		/** 跳转余额变动明细(与小程序共用 /ghs-balance-change/list,带 salt 访客可查) */
+		goBalanceChange() {
+			if (!this.id) {
+				uni.showToast({
+					title: '供货商信息加载中,请稍后再试',
+					icon: 'none'
+				});
+				return;
+			}
+			uni.navigateTo({
+				url: `/admin/ghs/balanceChange?ghsId=${this.id}&salt=${this.salt}`
+			});
+		},
 	}
 }
 </script>
@@ -513,8 +561,8 @@ export default {
 		margin-top: 8upx;
 		
 		.customer-avatar {
-			width: 90upx;
-			height: 90upx;
+			width: 70upx;
+			height: 70upx;
 			border-radius: 50%;
 			background: linear-gradient(135deg, #1677ff 0%, #4096ff 100%);
 			display: flex;
@@ -539,47 +587,75 @@ export default {
 			
 			.avatar-text {
 				color: #fff;
-				font-size: 44upx;
+				font-size: 36upx;
 				font-weight: 600;
 			}
 		}
 		
 		.customer-details {
 			flex: 1;
+			min-width: 0;
 			display: flex;
-			align-items: center;
-			justify-content: space-between;
+			flex-direction: column;
+			justify-content: center;
 			
 			.customer-name {
 				display: block;
 				font-size: 34upx;
 				font-weight: 600;
 				color: #333;
-			}
-			
-			.customer-status {
-				display: flex;
-				align-items: center;
-				justify-content: center;
-				font-size: 24upx;
-				color: #999;
-				padding: 0 24upx;
-				height: 70upx;
-				line-height: 70upx;
-				border-radius: 36upx;
-				background: #f8f9fa;
-				border: 1upx solid #e9ecef;
-				box-sizing: border-box;
-				
-				&.has-debt {
-					color: #fff;
-					background: linear-gradient(135deg, #ff6b6b 0%, #ff5252 100%);
-					border-color: #ff6b6b;
-					box-shadow: 0 2upx 10upx rgba(255, 107, 107, 0.3);
-				}
+				line-height: 44upx;
 			}
 		}
 	}
+
+	/* 明细入口:等宽描边按钮,与待结金额纵向分层;单按钮时半宽 */
+	.pay-entry-row {
+		display: flex;
+		flex-direction: row;
+		align-items: stretch;
+		margin-top: 24upx;
+	}
+
+	.pay-entry-row--single {
+		width: 50%;
+	}
+
+	.pay-entry-btn {
+		flex: 1;
+		height: 72upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		box-sizing: border-box;
+		border: 1upx solid #d0e3ff;
+		background-color: #f5f9ff;
+		border-radius: 12upx;
+		margin-left: 16upx;
+
+		&:first-child {
+			margin-left: 0;
+		}
+	}
+
+	.pay-entry-btn--debt {
+		border-color: #ffd0d0;
+		background-color: #fff5f5;
+	}
+
+	.pay-entry-btn--debt .pay-entry-btn__text {
+		color: #ff5252;
+	}
+
+	.pay-entry-btn--disabled {
+		opacity: 0.5;
+	}
+
+	.pay-entry-btn__text {
+		font-size: 26upx;
+		color: #3385ff;
+		line-height: 36upx;
+	}
 	
 	.debt-info {
 		background: linear-gradient(135deg, #fff5f5 0%, #fef2f2 100%);

+ 10 - 1
hdh5/api/ghs/index.js

@@ -1,5 +1,14 @@
 import https from '@/plugins/luch-request_0.0.7/request'
 
+/** 未登录扫码付款:按 id+salt 拉取供货商/客户待结信息 */
 export const getGhsData = data => {
 	return https.get('/ghs/get-ghs-data', data)
-}
+}
+
+/**
+ * 供货商余额变动明细(与小程序共用接口)
+ * 登录态:校验 shop 归属;H5 访客:传 salt 校验后可查
+ */
+export const balanceChangeList = data => {
+	return https.get('/ghs-balance-change/list', data)
+}

+ 11 - 0
hdh5/pages.json

@@ -10,6 +10,17 @@
 				}
 			}
 		},
+		{
+			"path": "admin/ghs/balanceChange",
+			"style": {
+				"navigationBarTitleText": "余额变动",
+				"navigationStyle": "custom",
+				"enablePullDownRefresh": true,
+				"h5": {
+					"titleNView": false
+				}
+			}
+		},
 		{
 			"path": "pagesPurchase/gathering",
 			"style": {

+ 3 - 3
hdh5/unpackage/dist/cache/.vite/deps/_metadata.json

@@ -1,8 +1,8 @@
 {
-  "hash": "14fa295b",
-  "configHash": "c15e052d",
+  "hash": "c51d6aff",
+  "configHash": "24fd9046",
   "lockfileHash": "e3b0c442",
-  "browserHash": "5d86e942",
+  "browserHash": "9f6f0388",
   "optimized": {},
   "chunks": {}
 }