Jelajahi Sumber

优化个人充值/退费页面及环境变更确认页面

apple 1 Minggu lalu
induk
melakukan
0c1a604638

+ 365 - 163
page/biz/grcz_grczAdd.ss.jsp

@@ -1,163 +1,365 @@
-<%@ page import="java.util.Map" %>
-<%@ page import="java.util.TreeMap" %>
-<html>
-<head>
-
-	<style>
-		.table-container>tr>th{
-			width:130px !important;
-		}
-		/* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
-		.form-container .content-box {
-			height: calc(100% - 80px) !important;
-		}
-		td{
-			display: flex;
-			align-items: center;
-			justify-content: flex-start;
-		}
-	</style>
-
-</head>
-<body class="env-input-body">
-<form method="post" id="app" class="form-container">
-<div class="content-box fit-height-content">
-
-<div class="content-div" ssFith="true">
-	<table class='form'>
-
-		<tr>
-			<th style="width: 120px">充值类别</th>
-			<td >
-				<onoff.ss name="grczlbm" mode="edit" rad="true" null="false" val="11" />
-
-				<%
-					Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
-					for (Integer key : grczlbMap.keySet()) {
-						pageContext.setAttribute("k",key);
-						pageContext.setAttribute("v",grczlbMap.get(key));
-				%>
-
-				<input name="grczlbm" value="${v}" ssVal="${k}" />
-
-				<%
-					}
-				%>
-			</td>
-		</tr>
-<%-- 再改,合并到 "班级" 里。Lin
-		<tr>
-			<th>人员类别</th>
-			<td >
-				<onoff@ss name="rylbm" mode="edit" rad="true" null="false" val="1100" />
-				<@input name="rylbm" value="学员" ssVal="1100" />
-				<@input name="rylbm" value="职工亲属" ssVal="1000" />
-			</td>
-		</tr>
---%>
-		<tr>
-			<th>班级/亲属</th>
-			<td >
-				<objp.ss name="bjid" cb="bj" inp="true" width="200px"/>
-				<onoff.ss name="rylbm" mode="edit" rad="false" null="false" val="1100" />
-				<input name="rylbm" value="职工亲属" ssVal="1000" />
-			</td>
-		</tr>
-
-<%-- 先去掉,接入读卡器时再加。Lin
-		<tr>
-			<th>卡号</th>
-			<td >
-				<@input name="kah"/>
-			</td>
-		</tr>
---%>
-
-		<tr>
-			<th>人员</th>
-			<td>
-				<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
-
-				<objp.ss name="ryid" cb="ryByBjOrRylb" inp="true" onChange="selBaseInfoByRyid" filterField="bjid,rylbm" />
-			</td>
-		</tr>
-		<tr>
-			<th>部门/班级</th>
-			<td id='bmbj'></td>
-		</tr>
-		<tr>
-			<th>姓名</th>
-			<td id='xm'></td>
-		</tr>
-		<tr>
-			<th>人员号</th>
-			<td id='ryh'></td>
-		</tr>
-		<tr>
-			<th>金额</th>
-			<td><input name="je"/></td>
-		</tr>
-		<tr>
-			<th>描述</th>
-			<td><input name="ms"/></td>
-		</tr>
-	</table>
-</div>
-
-
-	<div class='bottom-div'>
-
-		<ss-bottom-button
-				id="saveAndCommit"
-				text="保存并提交"
-				onclick='ss.form.submit({url:"<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:"grcz",dataType:"update"}'/>",width:881,height:361,minHeight:515,maxHeight:515,targetWin:null});'<%-- 最后一个参数原来为targetWin:parent导致刷新了顶层窗口,改为null Ben(20251215) --%>
-				icon-class="bottom-div-save"
-		></ss-bottom-button>
-
-
-		<ss-bottom-button
-				text="关闭"
-				onclick='ss.display.closeDialog();'
-				icon-class="bottom-div-close"
-		></ss-bottom-button>
-
-	</div>
-
-</div>
-</form>
-</body>
-
-</html>
-
-<script>
-	function selBaseInfoByRyid(value){
-		$.ajax({
-			url:"<serv.ss name='ry_selBaseInfoByRyid'/>",
-			type:"post",
-			data:{
-				ryid:value
-			},
-			dataType:"json",
-			success:function(data){
-				if (data.ssCode != 0) {
-					alert(data.ssMsg);
-					return;
-				}
-				var d = data.ssData;
-				document.getElementById('xm').innerHTML = d.xm;
-				document.getElementById('ryh').innerHTML = d.ryh;
-				if (d.rylbm != 1100) {	// 不是学员。Lin
-					if (d.bmmc)
-						document.getElementById('bmbj').innerHTML = d.bmmc;
-					else
-						document.getElementById('bmbj').innerHTML = "(无)";
-				} else {
-					if (d.bjmc)
-						document.getElementById('bmbj').innerHTML = d.bjmc;
-					else
-						document.getElementById('bmbj').innerHTML = "(无)";
-				}
-			}
-		});
-	}
-</script>
+<%@ page import="java.util.Map" %>
+<%@ page import="java.util.TreeMap" %>
+<html>
+<head>
+
+	<style>
+		.table-container>tr>th{
+			width:130px !important;
+		}
+		/* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
+		.form-container .content-box {
+			height: calc(100% - 80px) !important;
+		}
+
+	</style>
+
+</head>
+<body class="env-input-body">
+<form method="post" id="app" class="form-container">
+<div class="content-box fit-height-content">
+
+<div class="content-div" ssFith="true">
+	<table class='form'>
+
+		<tr>
+			<th style="width: 120px">充值类别</th>
+			<td >
+				<%-- 功能说明:个人充值页充值类别切换时联动重跑金额校验 by xu 20260323 --%>
+				<onoff.ss name="grczlbm" mode="edit" rad="true" null="false" val="11" onChange="handleGrczlbmChange" />
+
+				<%
+					Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
+					for (Integer key : grczlbMap.keySet()) {
+						pageContext.setAttribute("k",key);
+						pageContext.setAttribute("v",grczlbMap.get(key));
+				%>
+
+				<input name="grczlbm" value="${v}" ssVal="${k}" />
+
+				<%
+					}
+				%>
+			</td>
+		
+			<th>班级/亲属</th>
+			<td  style="display: flex;align-items: center;">
+				<objp.ss name="bjid" cb="bj" inp="true" width="200px"/>
+				<onoff.ss name="rylbm" mode="edit" rad="false" null="false" val="1100" />
+				<input name="rylbm" value="职工亲属" ssVal="1000" />
+			</td>
+		</tr>
+
+<%-- 先去掉,接入读卡器时再加。Lin
+		<tr>
+			<th>卡号</th>
+			<td >
+				<@input name="kah"/>
+			</td>
+		</tr>
+--%>
+
+		<tr>
+			<th>人员</th>
+			<td>
+				<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
+
+				<objp.ss name="ryid" cb="ryByBjOrRylb" inp="true" onChange="selBaseInfoByRyid" filterField="bjid,rylbm" />
+			</td>
+		
+			<th>部门/班级</th>
+			<td id='bmbj'></td>
+		</tr>
+		<tr>
+			<th>姓名</th>
+			<td id='xm'></td>
+		
+			<th>人员号</th>
+			<td id='ryh'></td>
+		</tr>
+		<tr>
+			<th>消费余额</th>
+			<td  colspan="3"><input name="xfye"/></td>
+		</tr>
+		<tr>
+			<th>金额</th>
+			<td>
+				<script>
+				// 功能说明:个人充值页金额字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+				ss.dom.formElemConfig.je={val:'',type:window.ss.dom.TYPE.INPUT};
+				</script>
+				<ss-inp v-model="je" name="je" placeholder="请输入金额"></ss-inp>
+			</td>
+		
+			<th>描述</th>
+			<td>
+				<script>
+				// 功能说明:个人充值页描述字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+				ss.dom.formElemConfig.ms={val:'',type:window.ss.dom.TYPE.INPUT};
+				</script>
+				<ss-inp v-model="ms" name="ms" placeholder="请输入描述"></ss-inp>
+			</td>
+		</tr>
+		
+	</table>
+</div>
+
+
+	<div class='bottom-div'>
+
+		<ss-bottom-button
+				id="saveAndCommit"
+				text="保存并提交"
+				onclick='submitGrczForm();'<%-- 功能说明:个人充值页提交前先校验“反向充值”金额必须为负数 by xu 20260323 --%>
+				icon-class="bottom-div-save"
+		></ss-bottom-button>
+
+
+		<ss-bottom-button
+				text="关闭"
+				onclick='ss.display.closeDialog();'
+				icon-class="bottom-div-close"
+		></ss-bottom-button>
+
+	</div>
+
+</div>
+</form>
+</body>
+
+</html>
+
+<script>
+	function getFormAppVm(){
+		var appEl = document.getElementById("app");
+		if (!appEl || !appEl.__vue_app__ || !appEl.__vue_app__._instance) {
+			return null;
+		}
+		return appEl.__vue_app__._instance.proxy || null;
+	}
+
+	function normalizeRylbmValues(value){
+		if (Array.isArray(value)) {
+			return value.map(function(item){
+				return item == null ? "" : item.toString();
+			}).filter(Boolean);
+		}
+		if (value == null || value === "") {
+			return [];
+		}
+		var cleanValue = value.toString().replace(/^,+/, "");
+		if (!cleanValue) {
+			return [];
+		}
+		return cleanValue.split(/[,|]/).filter(Boolean);
+	}
+
+	function hasRelativeRylbmValue(value){
+		return normalizeRylbmValues(value).indexOf("1000") !== -1;
+	}
+
+	function clearRyDisplay(){
+		var bmbjEl = document.getElementById('bmbj');
+		var xmEl = document.getElementById('xm');
+		var ryhEl = document.getElementById('ryh');
+		if (bmbjEl) bmbjEl.innerHTML = "";
+		if (xmEl) xmEl.innerHTML = "";
+		if (ryhEl) ryhEl.innerHTML = "";
+	}
+
+	function clearRySelection(vm){
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		vm.ryid = "";
+		clearRyDisplay();
+	}
+
+	function handleRylbmChange(groupValue){
+		if (!hasRelativeRylbmValue(groupValue)) {
+			return;
+		}
+		var vm = getFormAppVm();
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		vm.bjid = "";
+		clearRySelection(vm);
+	}
+
+	function handleBjChange(value){
+		if (value == null || value === "") {
+			return;
+		}
+		var vm = getFormAppVm();
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		if (hasRelativeRylbmValue(vm.rylbm)) {
+			vm.rylbm = "1100";
+		}
+		clearRySelection(vm);
+	}
+	
+	function selBaseInfoByRyid(value){
+		$.ajax({
+			url:"<serv.ss name='ry_selBaseInfoByRyid'/>",
+			type:"post",
+			data:{
+				ryid:value
+			},
+			dataType:"json",
+			success:function(data){
+				if (data.ssCode != 0) {
+					alert(data.ssMsg);
+					return;
+				}
+				var d = data.ssData;
+				document.getElementById('xm').innerHTML = d.xm;
+				document.getElementById('ryh').innerHTML = d.ryh;
+				if (d.rylbm != 1100) {	// 不是学员。Lin
+					if (d.bmmc)
+						document.getElementById('bmbj').innerHTML = d.bmmc;
+					else
+						document.getElementById('bmbj').innerHTML = "(无)";
+				} else {
+					if (d.bjmc)
+						document.getElementById('bmbj').innerHTML = d.bjmc;
+					else
+						document.getElementById('bmbj').innerHTML = "(无)";
+				}
+			}
+		});
+	}
+</script>
+<script type="text/javascript" src="/js/validate/validator-rules.js"></script><%-- 功能说明:个人充值页接入桌面端校验规则,支持 SsInp 红线提示 by xu 20260323 --%>
+<script type="text/javascript" src="/js/validate/validation-manager.js"></script><%-- 功能说明:个人充值页接入桌面端校验管理器,支持 SsInp 红线提示 by xu 20260323 --%>
+
+<script>
+	// 功能说明:维护个人充值页中需要强制输入负数的充值类别编码,供金额校验复用 by xu 20260323
+	var NEGATIVE_ONLY_GRCZLBM_MAP = {
+		"21": "反向充值"
+	};
+
+	// 功能说明:充值类别变化后延后一拍重跑金额校验,确保隐藏字段值更新后再清理红线状态 by xu 20260323
+	function handleGrczlbmChange(groupValue, value, label){
+		setTimeout(function(){
+			if (window.ssVm && typeof window.ssVm.validateField === "function") {
+				window.ssVm.validateField("je");
+				return;
+			}
+			validateNegativeAmountByCategory(false);
+		}, 0);
+	}
+
+	// 功能说明:初始化金额负数校验规则,接入 ssVm 统一红线与底部提示 by xu 20260323
+	function initNegativeAmountValidation(){
+		if (!window.ssVm || typeof window.ssVm.add !== "function" || window.ss.dom._grczNegativeAmountValidationInited) {
+			return;
+		}
+		window.ss.dom._grczNegativeAmountValidationInited = true;
+		window.ssVm.add("ss.commonValidator.custom", ["je"], {
+			msgPrfx: "金额",
+			relField: "grczlbm",
+			validate: function(value, categoryValue){
+				if (!isNegativeOnlyGrczlbm(categoryValue)) {
+					return true;
+				}
+				if (value == null || value.toString().trim() === "") {
+					return true;
+				}
+				if (isValidNegativeAmountText(value)) {
+					return true;
+				}
+				return {
+					valid: false,
+					message: NEGATIVE_ONLY_GRCZLBM_MAP[categoryValue] + "金额只能输入负数"
+				};
+			}
+		}, {
+			je: window.ss.dom.formElemConfig.je ? window.ss.dom.formElemConfig.je.val : ""
+		});
+	}
+
+	// 功能说明:根据充值类别判断当前金额是否必须为负数 by xu 20260323
+	function isNegativeOnlyGrczlbm(categoryValue){
+		var currentValue = categoryValue == null ? "" : categoryValue.toString();
+		return !!NEGATIVE_ONLY_GRCZLBM_MAP[currentValue];
+	}
+
+	// 功能说明:统一判断金额文本是否为合法负数,供提交校验和 ssVm 规则复用 by xu 20260323
+	function isValidNegativeAmountText(value){
+		var amountText = value == null ? "" : value.toString().trim();
+		if (!amountText) {
+			return false;
+		}
+		var amountNumber = Number(amountText);
+		return !isNaN(amountNumber) && amountNumber < 0;
+	}
+
+	// 功能说明:根据当前充值类别判断是否必须录入负数金额 by xu 20260323
+	function getCurrentGrczlbmValue(){
+		var vm = getFormAppVm();
+		if (vm && vm.grczlbm != null && vm.grczlbm !== "") {
+			return vm.grczlbm.toString();
+		}
+		var categoryElem = document.querySelector('[name="grczlbm"]');
+		return categoryElem && categoryElem.value != null ? categoryElem.value.toString() : "";
+	}
+
+	// 功能说明:统一校验金额字段在指定充值类别下是否为负数 by xu 20260323
+	function validateNegativeAmountByCategory(showMsg){
+		var categoryValue = getCurrentGrczlbmValue();
+		if (!isNegativeOnlyGrczlbm(categoryValue)) {
+			return true;
+		}
+
+		var jeElem = document.querySelector('[name="je"]');
+		if (!jeElem) {
+			return true;
+		}
+
+		if (isValidNegativeAmountText(jeElem.value)) {
+			return true;
+		}
+
+		if (showMsg !== false) {
+			alert(NEGATIVE_ONLY_GRCZLBM_MAP[categoryValue] + "金额只能输入负数");
+			jeElem.focus();
+		}
+		return false;
+	}
+
+	// 功能说明:个人充值页提交前先走 ssVm 全量校验,确保显示 SsInp 左侧红线与底部提示 by xu 20260323
+	function submitGrczForm(){
+		if (window.ssVm && window.ssVm.validations && window.ssVm.validations.size > 0) {
+			var validateResult = window.ssVm.validateAll();
+			if (!validateResult.valid) {
+				return false;
+			}
+		} else if (!validateNegativeAmountByCategory(true)) {
+			return false;
+		}
+
+		var formElem = document.querySelector("form");
+		if (!formElem) {
+			alert("表单不存在");
+			return false;
+		}
+
+		formElem.action = "<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:\"grcz\",dataType:\"update\"}'/>";
+		ss.display.resizeComponent(881,361,515,515);
+		formElem.submit();
+		return true;
+	}
+
+	if (window.SS && typeof SS.ready === "function") {
+		SS.ready(function(){
+			// 功能说明:页面初始化后注册金额负数校验规则,保证 SsInp 输入时直接出现红线提示 by xu 20260323
+			initNegativeAmountValidation();
+		});
+	}
+</script>

+ 383 - 163
page/biz/grcz_grtfAdd.ss.jsp

@@ -1,163 +1,383 @@
-<%@ page import="java.util.Map" %>
-<%@ page import="java.util.TreeMap" %>
-<html>
-<head>
-
-	<style>
-	  .table-container>tr>th{
-		  width:130px !important;
-	  }
-	  /* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
-	  .form-container .content-box {
-		  height: calc(100% - 80px) !important;
-	  }
-	  td{
-		display: flex;
-		align-items: center;
-		justify-content: flex-start;
-		}
-	</style>
-
-</head>
-<body class="env-input-body">
-<form method="post" id="app" class="form-container">
-<div class="content-box fit-height-content">
-
-	<div class="content-div" ssFith="true">
-		<table class='form'>
-
-			<tr>
-				<th style="width: 120px">退费类别</th>
-				<td >
-					<onoff.ss name="grczlbm" mode="edit" rad="true" null="false" val="11" />
-
-					<%
-						Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
-						for (Integer key : grczlbMap.keySet()) {
-							pageContext.setAttribute("k",key);
-							pageContext.setAttribute("v",grczlbMap.get(key));
-					%>
-
-					<input name="grczlbm" value="${v}" ssVal="${k}" />
-
-					<%
-						}
-					%>
-				</td>
-			</tr>
-			<%-- 再改,合并到 "班级" 里。Lin
-					<tr>
-						<th>人员类别</th>
-						<td >
-							<onoff@ss name="rylbm" mode="edit" rad="true" null="false" val="1100" />
-							<@input name="rylbm" value="学员" ssVal="1100" />
-							<@input name="rylbm" value="职工亲属" ssVal="1000" />
-						</td>
-					</tr>
-			--%>
-			<tr>
-				<th>班级/亲属</th>
-				<td >
-					<objp.ss name="bjid" cb="bj" inp="true" width="200px"/>
-					<onoff.ss name="rylbm" mode="edit" rad="false" null="false" val="1100" />
-					<input name="rylbm" value="职工亲属" ssVal="1000" />
-				</td>
-			</tr>
-
-<%-- 先去掉,接入读卡器时再加。Lin
-			<tr>
-				<th>卡号</th>
-				<td >
-					<@input name="kah"/>
-				</td>
-			</tr>
---%>
-
-			<tr>
-				<th>人员</th>
-				<td>
-					<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
-
-					<objp.ss name="ryid" cb="ryByBjOrRylb" inp="true" onChange="selBaseInfoByRyid" filterField="bjid,rylbm" />
-				</td>
-			</tr>
-			<tr>
-				<th>部门/班级</th>
-				<td id='bmbj'></td>
-			</tr>
-			<tr>
-				<th>姓名</th>
-				<td id='xm'></td>
-			</tr>
-			<tr>
-				<th>人员号</th>
-				<td id='ryh'></td>
-			</tr>
-			<tr>
-				<th>金额</th>
-				<td><input name="je"/></td>
-			</tr>
-			<tr>
-				<th>描述</th>
-				<td><input name="ms"/></td>
-			</tr>
-		</table>
-	</div>
-
-
-	<div class='bottom-div'>
-
-		<ss-bottom-button
-				id="saveAndCommit"
-				text="保存并提交"
-				onclick='ss.form.submit({url:"<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:"grcz",dataType:"update"}'/>",width:881,height:361,minHeight:515,maxHeight:515,targetWin:null});'<%-- 最后一个参数原来为targetWin:parent导致刷新了顶层窗口,改为null Ben(20251215) --%>
-				icon-class="bottom-div-save"
-		></ss-bottom-button>
-
-
-		<ss-bottom-button
-				text="关闭"
-				onclick='ss.display.closeDialog();'
-				icon-class="bottom-div-close"
-		></ss-bottom-button>
-
-	</div>
-</div>
-
-</form>
-</body>
-
-</html>
-
-<script>
-	function selBaseInfoByRyid(value){
-		$.ajax({
-			url:"<serv.ss name='ry_selBaseInfoByRyid'/>",
-			type:"post",
-			data:{
-				ryid:value
-			},
-			dataType:"json",
-			success:function(data){
-				if (data.ssCode != 0) {
-					alert(data.ssMsg);
-					return;
-				}
-				var d = data.ssData;
-				document.getElementById('xm').innerHTML = d.xm;
-				document.getElementById('ryh').innerHTML = d.ryh;
-				if (d.rylbm != 1100) {	// 不是学员。Lin
-					if (d.bmmc)
-						document.getElementById('bmbj').innerHTML = d.bmmc;
-					else
-						document.getElementById('bmbj').innerHTML = "(无)";
-				} else {
-					if (d.bjmc)
-						document.getElementById('bmbj').innerHTML = d.bjmc;
-					else
-						document.getElementById('bmbj').innerHTML = "(无)";
-				}
-			}
-		});
-	}
-</script>
+<%@ page import="java.util.Map" %>
+<%@ page import="java.util.TreeMap" %>
+<html>
+<head>
+
+	<style>
+	  .table-container>tr>th{
+		  width:130px !important;
+	  }
+	  /* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
+	  .form-container .content-box {
+		  height: calc(100% - 80px) !important;
+	  }
+	  td{
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		}
+	</style>
+
+</head>
+<body class="env-input-body">
+<form method="post" id="app" class="form-container">
+<div class="content-box fit-height-content">
+
+	<div class="content-div" ssFith="true">
+		<table class='form'>
+
+			<tr>
+				<th style="width: 120px">退费类别</th>
+				<td >
+					<%-- 功能说明:个人退费页退费类别切换时联动重跑金额校验 by xu 20260323 --%>
+					<onoff.ss name="grczlbm" mode="edit" rad="true" null="false" val="11" onChange="handleGrczlbmChange" />
+
+					<%
+						Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
+						for (Integer key : grczlbMap.keySet()) {
+							pageContext.setAttribute("k",key);
+							pageContext.setAttribute("v",grczlbMap.get(key));
+					%>
+
+					<input name="grczlbm" value="${v}" ssVal="${k}" />
+
+					<%
+						}
+					%>
+				</td>
+			
+				<th>班级/亲属</th>
+				<td  style="display: flex;align-items: center;">
+					<objp.ss name="bjid" cb="bj" inp="true" width="200px"/>
+					<onoff.ss name="rylbm" mode="edit" rad="false" null="false" val="1100" />
+					<input name="rylbm" value="职工亲属" ssVal="1000" />
+				</td>
+			</tr>
+
+<%-- 先去掉,接入读卡器时再加。Lin
+			<tr>
+				<th>卡号</th>
+				<td >
+					<@input name="kah"/>
+				</td>
+			</tr>
+--%>
+
+			<tr>
+				<th>人员</th>
+				<td>
+					<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
+
+					<objp.ss name="ryid" cb="ryByBjOrRylb" inp="true" onChange="selBaseInfoByRyid" filterField="bjid,rylbm" />
+				</td>
+			
+				<th>部门/班级</th>
+				<td id='bmbj'></td>
+			</tr>
+			<tr>
+				<th>姓名</th>
+				<td id='xm'></td>
+			
+				<th>人员号</th>
+				<td id='ryh'></td>
+			</tr>
+			<tr>
+				<th>消费余额</th>
+				<td  colspan="3"><input name="xfye"/></td>
+			</tr>
+			<tr>
+				<th>金额</th>
+				<td>
+					<script>
+					// 功能说明:个人退费页金额字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+					ss.dom.formElemConfig.je={val:'',type:window.ss.dom.TYPE.INPUT};
+					</script>
+					<ss-inp v-model="je" name="je" placeholder="请输入金额"></ss-inp>
+				</td>
+			
+				<th>描述</th>
+				<td>
+					<script>
+					// 功能说明:个人退费页描述字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+					ss.dom.formElemConfig.ms={val:'',type:window.ss.dom.TYPE.INPUT};
+					</script>
+					<ss-inp v-model="ms" name="ms" placeholder="请输入描述"></ss-inp>
+				</td>
+			</tr>
+		</table>
+	</div>
+
+
+	<div class='bottom-div'>
+
+		<ss-bottom-button
+				id="saveAndCommit"
+				text="保存并提交"
+				onclick='submitGrtfForm();'<%-- 功能说明:个人退费页提交前先校验金额必须为负数 by xu 20260323 --%>
+				icon-class="bottom-div-save"
+		></ss-bottom-button>
+
+
+		<ss-bottom-button
+				text="关闭"
+				onclick='ss.display.closeDialog();'
+				icon-class="bottom-div-close"
+		></ss-bottom-button>
+
+	</div>
+</div>
+
+</form>
+</body>
+
+</html>
+
+<script>
+
+	function getFormAppVm(){
+		var appEl = document.getElementById("app");
+		if (!appEl || !appEl.__vue_app__ || !appEl.__vue_app__._instance) {
+			return null;
+		}
+		return appEl.__vue_app__._instance.proxy || null;
+	}
+
+	function normalizeRylbmValues(value){
+		if (Array.isArray(value)) {
+			return value.map(function(item){
+				return item == null ? "" : item.toString();
+			}).filter(Boolean);
+		}
+		if (value == null || value === "") {
+			return [];
+		}
+		var cleanValue = value.toString().replace(/^,+/, "");
+		if (!cleanValue) {
+			return [];
+		}
+		return cleanValue.split(/[,|]/).filter(Boolean);
+	}
+
+	function hasRelativeRylbmValue(value){
+		return normalizeRylbmValues(value).indexOf("1000") !== -1;
+	}
+
+	function clearRyDisplay(){
+		var bmbjEl = document.getElementById('bmbj');
+		var xmEl = document.getElementById('xm');
+		var ryhEl = document.getElementById('ryh');
+		if (bmbjEl) bmbjEl.innerHTML = "";
+		if (xmEl) xmEl.innerHTML = "";
+		if (ryhEl) ryhEl.innerHTML = "";
+	}
+
+	function clearRySelection(vm){
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		vm.ryid = "";
+		clearRyDisplay();
+	}
+
+	function handleRylbmChange(groupValue){
+		if (!hasRelativeRylbmValue(groupValue)) {
+			return;
+		}
+		var vm = getFormAppVm();
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		vm.bjid = "";
+		clearRySelection(vm);
+	}
+
+	function handleBjChange(value){
+		if (value == null || value === "") {
+			return;
+		}
+		var vm = getFormAppVm();
+		if (!vm) {
+			clearRyDisplay();
+			return;
+		}
+		if (hasRelativeRylbmValue(vm.rylbm)) {
+			vm.rylbm = "1100";
+		}
+		clearRySelection(vm);
+	}
+	
+	function selBaseInfoByRyid(value){
+		$.ajax({
+			url:"<serv.ss name='ry_selBaseInfoByRyid'/>",
+			type:"post",
+			data:{
+				ryid:value
+			},
+			dataType:"json",
+			success:function(data){
+				if (data.ssCode != 0) {
+					alert(data.ssMsg);
+					return;
+				}
+				var d = data.ssData;
+				document.getElementById('xm').innerHTML = d.xm;
+				document.getElementById('ryh').innerHTML = d.ryh;
+				if (d.rylbm != 1100) {	// 不是学员。Lin
+					if (d.bmmc)
+						document.getElementById('bmbj').innerHTML = d.bmmc;
+					else
+						document.getElementById('bmbj').innerHTML = "(无)";
+				} else {
+					if (d.bjmc)
+						document.getElementById('bmbj').innerHTML = d.bjmc;
+					else
+						document.getElementById('bmbj').innerHTML = "(无)";
+				}
+			}
+		});
+	}
+</script>
+<script type="text/javascript" src="/js/validate/validator-rules.js"></script><%-- 功能说明:个人退费页接入桌面端校验规则,支持 SsInp 红线提示 by xu 20260323 --%>
+<script type="text/javascript" src="/js/validate/validation-manager.js"></script><%-- 功能说明:个人退费页接入桌面端校验管理器,支持 SsInp 红线提示 by xu 20260323 --%>
+
+<script>
+	// 功能说明:根据当前退费类别渲染 code-label 映射,供金额负数校验复用 by xu 20260323
+	var GRTF_LABEL_MAP = {
+<%
+	for (Integer key : grczlbMap.keySet()) {
+%>
+		"<%=key%>": "<%=grczlbMap.get(key)%>",
+<%
+	}
+%>
+	};
+
+	// 功能说明:记录当前退费类别的最新 value/label,避免 onoff 隐藏值切换时机导致金额校验判断失真 by xu 20260323
+	var CURRENT_GRTF_CATEGORY_STATE = {
+		value: "",
+		label: ""
+	};
+
+	// 功能说明:退费类别变化后延后一拍重跑金额校验,确保隐藏字段值更新后再清理红线状态 by xu 20260323
+	function handleGrczlbmChange(groupValue, value, label){
+		CURRENT_GRTF_CATEGORY_STATE.value = groupValue == null ? "" : groupValue.toString();
+		CURRENT_GRTF_CATEGORY_STATE.label = label == null ? "" : label.toString();
+		setTimeout(function(){
+			if (window.ssVm && typeof window.ssVm.validateField === "function") {
+				window.ssVm.validateField("je");
+				return;
+			}
+			validateNegativeAmountByCategory(false);
+		}, 0);
+	}
+
+	// 功能说明:初始化个人退费页金额负数校验规则,整页金额都必须输入负数 by xu 20260323
+	function initNegativeAmountValidation(){
+		if (!window.ssVm || typeof window.ssVm.add !== "function" || window.ss.dom._grczGrtfNegativeAmountValidationInited) {
+			return;
+		}
+		window.ss.dom._grczGrtfNegativeAmountValidationInited = true;
+		window.ssVm.add("ss.commonValidator.custom", ["je"], {
+			msgPrfx: "金额",
+			relField: "grczlbm",
+			validate: function(value, categoryValue){
+				var currentLabel = getCurrentGrczlbmLabel(categoryValue);
+				if (value == null || value.toString().trim() === "") {
+					return true;
+				}
+				if (isValidNegativeAmountText(value)) {
+					return true;
+				}
+				return {
+					valid: false,
+					message: (currentLabel || "退费") + "金额只能输入负数"
+				};
+			}
+		}, {
+			je: window.ss.dom.formElemConfig.je ? window.ss.dom.formElemConfig.je.val : ""
+		});
+	}
+
+	// 功能说明:根据当前退费类别值读取显示文案,供联动校验和错误提示复用 by xu 20260323
+	function getCurrentGrczlbmLabel(categoryValue){
+		var currentValue = categoryValue == null ? getCurrentGrczlbmValue() : categoryValue.toString();
+		if (CURRENT_GRTF_CATEGORY_STATE.label && (!currentValue || CURRENT_GRTF_CATEGORY_STATE.value === currentValue)) {
+			return CURRENT_GRTF_CATEGORY_STATE.label;
+		}
+		return GRTF_LABEL_MAP[currentValue] || "";
+	}
+
+	// 功能说明:统一判断金额文本是否为合法负数,供提交校验和 ssVm 规则复用 by xu 20260323
+	function isValidNegativeAmountText(value){
+		var amountText = value == null ? "" : value.toString().trim();
+		if (!amountText) {
+			return false;
+		}
+		var amountNumber = Number(amountText);
+		return !isNaN(amountNumber) && amountNumber < 0;
+	}
+
+	// 功能说明:根据当前退费类别值读取隐藏字段值,供金额校验复用 by xu 20260323
+	function getCurrentGrczlbmValue(){
+		var vm = getFormAppVm();
+		if (vm && vm.grczlbm != null && vm.grczlbm !== "") {
+			return vm.grczlbm.toString();
+		}
+		var categoryElem = document.querySelector('[name="grczlbm"]');
+		return categoryElem && categoryElem.value != null ? categoryElem.value.toString() : "";
+	}
+
+	// 功能说明:统一校验个人退费金额必须为负数,不再区分具体退费类别 by xu 20260323
+	function validateNegativeAmountByCategory(showMsg){
+		var currentLabel = getCurrentGrczlbmLabel();
+		var jeElem = document.querySelector('[name="je"]');
+		if (!jeElem) {
+			return true;
+		}
+
+		if (isValidNegativeAmountText(jeElem.value)) {
+			return true;
+		}
+
+		if (showMsg !== false) {
+			alert((currentLabel || "退费") + "金额只能输入负数");
+			jeElem.focus();
+		}
+		return false;
+	}
+
+	// 功能说明:个人退费页提交前先走 ssVm 全量校验,确保显示 SsInp 左侧红线与底部提示 by xu 20260323
+	function submitGrtfForm(){
+		if (window.ssVm && window.ssVm.validations && window.ssVm.validations.size > 0) {
+			var validateResult = window.ssVm.validateAll();
+			if (!validateResult.valid) {
+				return false;
+			}
+		} else if (!validateNegativeAmountByCategory(true)) {
+			return false;
+		}
+
+		var formElem = document.querySelector("form");
+		if (!formElem) {
+			alert("表单不存在");
+			return false;
+		}
+
+		formElem.action = "<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:\"grcz\",dataType:\"update\"}'/>";
+		ss.display.resizeComponent(881,361,515,515);
+		formElem.submit();
+		return true;
+	}
+
+	if (window.SS && typeof SS.ready === "function") {
+		SS.ready(function(){
+			// 功能说明:页面初始化后同步当前退费类别状态,避免首次校验读取不到最新类别文案 by xu 20260323
+			CURRENT_GRTF_CATEGORY_STATE.value = getCurrentGrczlbmValue();
+			CURRENT_GRTF_CATEGORY_STATE.label = GRTF_LABEL_MAP[CURRENT_GRTF_CATEGORY_STATE.value] || "";
+			// 功能说明:页面初始化后注册个人退费金额负数校验规则,保证 SsInp 输入时直接出现红线提示 by xu 20260323
+			initNegativeAmountValidation();
+		});
+	}
+</script>

+ 161 - 0
page/env/addSure.jsp

@@ -0,0 +1,161 @@
+<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
+<%@ taglib uri="/ssTag" prefix="ss"%>
+<%pageContext.setAttribute("wdpageinformation","{'hastab':'0'}");%>
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="pragma" content="no-cache">
+<meta http-equiv="cache-control" content="no-cache">
+<meta http-equiv="expires" content="0">
+<script>window.loginStatus="${empty sessionScope['ssUser']?'0':'1'}"</script>
+<ss:skin file='main.css'/>
+<script type="text/javascript" src="/ss/jquery/jquery.js"></script>
+<script type="text/javascript" src="/ss/artdialog/artDialogUtil.js"></script>
+<script type="text/javascript" src="/ss/js/base.js"></script>
+<script> if(!window.wd) var wd={}; if(!wd.display) wd.display={}; wd.display.wdDialogId="addSure"; if(!wd.app) wd.app={};  wd.app.name='pms';</script>
+<script type="text/javascript" src="/ss/js/masklayer.js"></script>
+<script type="text/javascript" src="/ss/js/wdDialogInit.js"></script>
+<script type="text/javascript" src="/ss/js/common.js"></script>
+<script type="text/javascript" src="/ss/js/display.js"></script>
+<script type="text/javascript" src="/ss/js/edit.js"></script>
+<script type="text/javascript" src="/ss/nicescroll/jquery.nicescroll.js"></script>
+<script type="text/javascript" src="/ss/nicescroll/jquery.nicescroll.iframehelper.min.js"></script>
+<script type="text/javascript" src="/ss/js/common.js"></script>
+<script type="text/javascript" src="/ss/datePicker/WdatePicker.js"></script>
+<script type="text/javascript" src="/ss/js/edit.js"></script>
+<link rel="stylesheet" type="text/css" href="/ss/window/theme/dhtmlxwindows.css">
+<link rel="stylesheet" type="text/css" href="/ss/window/theme/dhx_blue/dhtmlxwindows_dhx_blue.css">
+<script type="text/javascript" src="/ss/window/dhtmlxcommon.js"></script>
+<script type="text/javascript" src="/ss/window/dhtmlxwindows.js"></script>
+<script type="text/javascript" src="/ss/window/dhtmlxcontainer.js"></script>
+<script type="text/javascript" src="/ss/js/display.js"></script>
+	<script type="text/javascript" src="/ss/js/growHeight.js"></script>
+	<script>
+		function addWdFitHeight(selecter,param){
+			console.log(selecter);
+			console.log(param);
+			var aa="";
+			if(param.minHeight){
+				aa+="min:"+param.minHeight+",";
+			}
+			if(param.maxHeight){
+				aa+="max:"+param.maxHeight+",";
+			}
+			if(param&&aa){
+				aa=aa.substr(0,aa.length-1)
+				document.querySelector(selecter).setAttribute("ssFith",aa);	// ("wdFitHeight",。Lin
+			}
+		}
+	</script>
+	<style type="text/css">
+		.longCardList-highlight a{
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			width: 90%;
+			display: inline-block;
+		}
+	</style>
+</head>
+<body style="padding-top: 12px;">
+<%--用于计算dxidName--%>
+<%-- 改为 <data@ss name="obj"/>。Lin
+<tab@ss name=""/> --%>
+<%-- <data@ss name="obj"/> 再去掉,好像不需要。Lin --%>
+
+<div ssFith='{min:111}'>
+<%-- 改为 <data@ss name="miniInfo"/> + <jsp@ss file="/ss/miniInfo.jsp"/>。Lin
+	<tab@ss name="miniInfo"/> --%>
+	<ss:data name='miniInfo'/>
+	<%@include file="/ss/clip/miniInfo.jsp"%>
+</div>
+
+<form name="tjform"
+<%-- 再改为 WebC.REQ_objName、WebC.REQ_objId、WebC.REQ_objIdName,避免与数据表里的 对象名、对象ID 字段重名。Lin
+		action="<func@ss name='sureAdd' parm='{dxm:"${dxm}",${dxidName}:"${dxid}"}'/>" --%>
+		action="<ss:func name='sureAdd' parm='{ssObjName:"${ssObjName}","${ssObjIdName}":"${ssObjId}"}'/>"
+		method="post" id="form">
+	<div class="content-div" style="height: 351px;">
+		<table class='form'>
+			<tr id="bpmtjgwid_tr">
+				<th>岗位</th>
+				<td>
+					<input name="bpmtjgwid" type="hidden" value="" placeholder="提交岗位"/>
+					<ss:objp name='bpmtjgwid' cb='bpmtjgwid'/>
+				</td>
+			</tr>
+
+			<script>
+				try{
+					if(wd.edit.objectPicker.getInstance("bpmtjgwid").getOptionsSize()==0)
+						$("#bpmtjgwid_tr").remove();
+				}catch(e){
+					console.log(e);
+				}
+			</script>
+
+			<tr>
+<%-- 再改为 WebC.REQ_objName,避免与数据表里的 对象名 字段重名。用到再确认。Lin
+				<equal@ss val="${dxm}" val2="ws"> // 改 equal:val1='${dxm=="ws"}' val2='true'。Lin --%>
+				<ss:equal val='${ssObjName}' val2='ws'>
+					<th width="117px">拟办意见</th>
+				</ss:equal>
+<%-- 再改为 WebC.REQ_objName,避免与数据表里的 对象名 字段重名。用到再确认。Lin
+				<notEqual@ss val="${dxm}" val2="ws"> // 改 equal:val1='${dxm=="ws"}' val2='false'。Lin --%>
+				<ss:notEqual val='${ssObjName}' val2='ws'>
+					<th width="88px">说明</th>
+				</ss:notEqual>
+				<td style="padding:0;">
+					<div id="sqmsEdit" name="msEdit" type="text/plain" class="editplus"  style="width:calc(100%);min-height: 185px;"></div>
+					<input type="hidden" name="sqmswj" value="${sqmswj}" />
+					<input type='hidden' name='ueditorpath' value='sqmswj'/>
+<script type="text/javascript" charset="utf-8" src="/ss/js/edit.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/wdEditor.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/wdEditor.config.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/ueditor.config.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/ueditor.all.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/zh-cn.js"></script>
+<script type="text/javascript" charset="utf-8" src="/ss/ueditor/editorgenerator.js"></script>
+<script type="text/javascript">
+EditorManager.getWDEditor("sqms",<ss:editor name='toolbars' parm='{"toolbars":[[{"key":"sqfjid","value":"${sqfjid}"}]],"mode":"edit"}'/>);
+</script>
+				</td>
+			</tr>
+		</table>
+	</div>
+	<div class="bottom-div">
+		<div class="bottom-down-div border-top">
+			<ss:equal val='${empty print}' val2='false'> <%-- 这里的 print},是否需要改为 prt}。和 打印功能 有关吗???Lin --%>
+				<input type="button" value="打印" class="bottom-button" onclick='document.querySelector("#printFrame iframe").contentWindow.wd.display.printArea(".content-div");' style="letter-spacing: 6px;"/>
+			</ss:equal>
+			<input type="submit" value="确定" class="bottom-button" onclick="wd.display.submitToTopTab(this);" style=""/>
+		</div>
+	</div>
+<input name='wdComponentID' type='hidden' value='addSure'/></form>
+<script type="text/javascript">var wdRecordValue='${wdRecordValue}';</script>
+<script type="text/javascript" src="/ss/js/wdRecord.js"></script>
+<script type="text/javascript">(function(){wdRecord("addSure");})();</script>
+<script type="text/javascript" src="/ss/js/wdFitHeight.js"></script>
+<script type="text/javascript">initWdFitHeight(404)</script>
+<script type="text/javascript">initWdFitHeightFunction=function(){initWdFitHeight(404);};</script>
+<ss:equal val="${empty resizeComponent}" val2="false">
+<script>{var iframe=wd.display.getFrameOfWindow();
+if(iframe&&iframe.contentWindow==window)
+wd.display.resizeComponent(${resizeComponent.width}, ${resizeComponent.height}, ${empty resizeComponent.minHeight?'null':resizeComponent.minHeight}, ${empty resizeComponent.maxHeight?'null':resizeComponent.maxHeight});}</script>
+</ss:equal>
+<ss:help/>
+</body>
+<script type="text/javascript">
+try{wd.display.showMsgPopup('${msg}');
+}catch(err){console.error(err);}
+</script>
+<ss:equal val="${empty wdclosewindowparam}" val2="false">
+<script type="text/javascript">
+try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
+}catch(err){console.error(err);}
+</script>
+</ss:equal>
+</html>
+<script type="text/javascript">
+tokenCleanser("<ss:serv name='ss.clearPageToken'/>", {tokenList:"<%= pageContext.getAttribute(ss.page.PageC.PAGE_tokenList)%>"});
+</script>

+ 1 - 1
page/env/chgSure.jsp

@@ -159,7 +159,7 @@ function showPhoto(path1,path2){
 <%--用于计算dxidName--%>
 <%-- 改为 <data@ss name="obj"/>。Lin
 <tab@ss name=""/> --%>
-<ss:data name='obj'/> <%-- 不能去掉,这里需要。Lin --%>
+<ss:data name='objId'/> <%-- 不能去掉,这里需要。Lin --%>
 
 <%-- 再改为 WebC.REQ_objName、WebC.REQ_objId、WebC.REQ_objIdName,避免与数据表里的 对象名、对象ID 字段重名。Lin
 <form name="tjform" action="<func@ss name='sureChg' parm='{dxm:"${dxm}",${dxidName}:"${dxid}"}'/>" method="post" id="form"> --%>

+ 1 - 1
page/env/chgSure.ss.jsp

@@ -130,7 +130,7 @@ function showPhoto(path1,path2){
 <%--用于计算dxidName--%>
 <%-- 改为 <data@ss name="obj"/>。Lin
 <tab@ss name=""/> --%>
-<data.ss name="obj"/> <%-- 不能去掉,这里需要。Lin --%>
+<ss:data name='objId'/> <%-- 不能去掉,这里需要。Lin --%>
 
 <%-- 再改为 WebC.REQ_objName、WebC.REQ_objId、WebC.REQ_objIdName,避免与数据表里的 对象名、对象ID 字段重名。Lin
 <form name="tjform" action="<func@ss name='sureChg' parm='{dxm:"${dxm}",${dxidName}:"${dxid}"}'/>" method="post" id="form"> --%>

File diff ditekan karena terlalu besar
+ 0 - 0
page/env/stateChgSure.ss.jsp


+ 307 - 175
page/grcz_grczAdd.jsp

@@ -1,85 +1,71 @@
-<%@ page import="java.util.Map" %>
-<%@ page import="java.util.TreeMap" %>
-<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
-<%@ taglib uri="/ssTag" prefix="ss"%>
-
-<% pageContext.setAttribute(ss.page.PageC.PAGE_objName,"grcz");%>
-<%pageContext.setAttribute("wdpageinformation","{'hastab':'0'}");%>
-<!DOCTYPE html>
-<html>
-<head>
-<%@ include file="/page/clip/header.jsp" %>
-
-	<style>
-		.table-container>tr>th{
-			width:130px !important;
-		}
-		/* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
-		.form-container .content-box {
-			height: calc(100% - 80px) !important;
-		}
-		td{
-			display: flex;
-			align-items: center;
-			justify-content: flex-start;
-		}
-	</style>
-
-</head>
-<body class="env-input-body">
-<form method="post" id="app" class="form-container">
-<div class="content-box fit-height-content">
-<div class="content-div" ssFith="true">
-	<table class='form'>
-
-		<tr>
-			<th style="width: 120px">充值类别</th>
-			<td >
-				
-<script>
-ss.dom.formElemConfig.grczlbm={val:'11',type:window.ss.dom.TYPE.ONOFFBTN};
-</script>
-
-
-				<%
-					Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
-					for (Integer key : grczlbMap.keySet()) {
-						pageContext.setAttribute("k",key);
-						pageContext.setAttribute("v",grczlbMap.get(key));
-				%>
-
-				<ss-onoff
-v-model="grczlbm"
-name="grczlbm"
-label="${v}"
-value="${k}"
-:multiple="false"
-:null="false"
-placeholder="${v}"
-v-model="grczlbm"
-:readonly="false"
-></ss-onoff>
-
-
-				<%
-					}
-				%>
-			</td>
-		</tr>
-<%-- 再改,合并到 "班级" 里。Lin
-		<tr>
-			<th>人员类别</th>
-			<td >
-				<onoff@ss name="rylbm" mode="edit" rad="true" null="false" val="1100" />
-				<@input name="rylbm" value="学员" ssVal="1100" />
-				<@input name="rylbm" value="职工亲属" ssVal="1000" />
-			</td>
-		</tr>
---%>
-		<tr>
-			<th>班级/亲属</th>
-			<td >
-				
+<%@ page import="java.util.Map" %>
+<%@ page import="java.util.TreeMap" %>
+<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
+<%@ taglib uri="/ssTag" prefix="ss"%>
+
+<% pageContext.setAttribute(ss.page.PageC.PAGE_objName,"grcz");%>
+<%pageContext.setAttribute("wdpageinformation","{'hastab':'0'}");%>
+<!DOCTYPE html>
+<html>
+<head>
+<%@ include file="/page/clip/header.jsp" %>
+
+	<style>
+		.table-container>tr>th{
+			width:130px !important;
+		}
+		/* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
+		.form-container .content-box {
+			height: calc(100% - 80px) !important;
+		}
+
+	</style>
+
+</head>
+<body class="env-input-body">
+<form method="post" id="app" class="form-container">
+<div class="content-box fit-height-content">
+<div class="content-div" ssFith="true">
+	<table class='form'>
+
+		<tr>
+			<th style="width: 120px">充值类别</th>
+			<td >
+				
+<script>
+ss.dom.formElemConfig.grczlbm={val:'11',type:window.ss.dom.TYPE.ONOFFBTN};
+</script>
+
+
+				<%
+					Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
+					for (Integer key : grczlbMap.keySet()) {
+						pageContext.setAttribute("k",key);
+						pageContext.setAttribute("v",grczlbMap.get(key));
+				%>
+
+<ss-onoff
+v-model="grczlbm"
+name="grczlbm"
+label="${v}"
+value="${k}"
+:multiple="false"
+:null="false"
+placeholder="${v}"
+v-model="grczlbm"
+:readonly="false"
+onchange="handleGrczlbmChange"
+></ss-onoff>
+
+
+				<%
+					}
+				%>
+			</td>
+		
+			<th>班级/亲属</th>
+			<td style="display: flex;align-items: center;">
+				
 <script>
 ss.dom.formElemConfig.bjid={val:null,type:window.ss.dom.TYPE.OBJP};
 </script>
@@ -112,98 +98,120 @@ v-model="rylbm"
 :readonly="false"
 onchange="handleRylbmChange"
 ></ss-onoff>
-
-			</td>
-		</tr>
-
-<%-- 先去掉,接入读卡器时再加。Lin
-		<tr>
-			<th>卡号</th>
-			<td >
-				<@input name="kah"/>
-			</td>
-		</tr>
---%>
-
-		<tr>
-			<th>人员</th>
-			<td>
-				<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
-
-				
-<script>
-ss.dom.formElemConfig.ryid={val:null,type:window.ss.dom.TYPE.OBJP};
-</script>
-<ss-objp
-:opt="ryidOption"
-:inp="true"
-url="<ss:serv name='loadObjpOpt' parm='{"objectpickerdropdown":"1","objectpickerfilterField":"bjid,rylbm"}' />"
-cb="ryByBjOrRylb"
-v-model="ryid"
-name="ryid"
-:readonly="false"
-filterField="bjid,rylbm"
-onChange="selBaseInfoByRyid"
-></ss-objp>
-
-			</td>
-		</tr>
-		<tr>
-			<th>部门/班级</th>
-			<td id='bmbj'></td>
-		</tr>
-		<tr>
-			<th>姓名</th>
-			<td id='xm'></td>
-		</tr>
-		<tr>
-			<th>人员号</th>
-			<td id='ryh'></td>
-		</tr>
-		<tr>
-			<th>金额</th>
-			<td><input name="je"/></td>
-		</tr>
-		<tr>
-			<th>描述</th>
-			<td><input name="ms"/></td>
-		</tr>
-	</table>
-</div>
-
-
-	<div class='bottom-div'>
-
-		<ss-bottom-button
-				id="saveAndCommit"
-				text="保存并提交"
-				onclick='ss.form.submit({url:"<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:"grcz",dataType:"update"}'/>",width:881,height:361,minHeight:515,maxHeight:515,targetWin:null});'<%-- 最后一个参数原来为targetWin:parent导致刷新了顶层窗口,改为null Ben(20251215) --%>
-				icon-class="bottom-div-save"
-		></ss-bottom-button>
-
-
-		<ss-bottom-button
-				text="关闭"
-				onclick='ss.display.closeDialog();'
-				icon-class="bottom-div-close"
-		></ss-bottom-button>
-
-	</div>
-
-
-<input name='wdComponentID' type='hidden' value='grcz_grczAdd'/>
-
-</div></form>
-<script type="text/javascript">var wdRecordValue='${wdRecordValue}';</script>
-<script type="text/javascript" src="/ss/js/wdRecord.js"></script>
-<script type="text/javascript">(function(){wdRecord("grcz_grczAdd");})();</script>
-<script type="text/javascript" src="/ss/js/wdFitHeight.js"></script>
-<script type="text/javascript">initWdFitHeight(0)</script>
-<script type="text/javascript">initWdFitHeightFunction=function(){initWdFitHeight(0);};</script>
-<ss:equal val="${empty resizeComponent}" val2="false">
-<script>{var iframe=wd.display.getFrameOfWindow();
-if(iframe&&iframe.contentWindow==window)
-wd.display.resizeComponent(${resizeComponent.width}, ${resizeComponent.height}, ${empty resizeComponent.minHeight?'null':resizeComponent.minHeight}, ${empty resizeComponent.maxHeight?'null':resizeComponent.maxHeight});}</script>
+
+			</td>
+		</tr>
+
+<%-- 先去掉,接入读卡器时再加。Lin
+		<tr>
+			<th>卡号</th>
+			<td >
+				<@input name="kah"/>
+			</td>
+		</tr>
+--%>
+
+		<tr>
+			<th>人员</th>
+			<td>
+				<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
+
+				
+<script>
+ss.dom.formElemConfig.ryid={val:null,type:window.ss.dom.TYPE.OBJP};
+</script>
+<ss-objp
+:opt="ryidOption"
+:inp="true"
+url="<ss:serv name='loadObjpOpt' parm='{"objectpickerdropdown":"1","objectpickerfilterField":"bjid,rylbm"}' />"
+cb="ryByBjOrRylb"
+v-model="ryid"
+name="ryid"
+:readonly="false"
+filterField="bjid,rylbm"
+onChange="selBaseInfoByRyid"
+></ss-objp>
+
+			</td>
+		
+			<th>部门/班级</th>
+			<td id='bmbj'></td>
+		</tr>
+		<tr>
+			<th>姓名</th>
+			<td id='xm'></td>
+		
+			<th>人员号</th>
+			<td id='ryh'></td>
+		</tr>
+		<tr>
+			<th>金额</th>
+			<td>
+				<script>
+				// 功能说明:金额字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+				ss.dom.formElemConfig.je={val:'',type:window.ss.dom.TYPE.INPUT};
+				</script>
+				<ss-inp
+	v-model="je"
+	name="je"
+	placeholder="请输入金额"
+				></ss-inp>
+			</td>
+		
+			<th>描述</th>
+			<td>
+				<script>
+				// 功能说明:描述字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+				ss.dom.formElemConfig.ms={val:'',type:window.ss.dom.TYPE.INPUT};
+				</script>
+				<ss-inp
+	v-model="ms"
+	name="ms"
+	placeholder="请输入描述"
+				></ss-inp>
+			</td>
+		</tr>
+		<tr>
+			<th>消费余额</th>
+			<td id='xfye' colspan="3"></td>
+		</tr>
+		
+	</table>
+</div>
+
+
+	<div class='bottom-div'>
+
+		<ss-bottom-button
+				id="saveAndCommit"
+				text="保存并提交"
+				onclick='submitGrczForm();'<%-- 功能说明:个人充值页提交前先校验“反向充值”金额必须为负数 by xu 20260323 --%>
+				icon-class="bottom-div-save"
+		></ss-bottom-button>
+
+
+		<ss-bottom-button
+				text="关闭"
+				onclick='ss.display.closeDialog();'
+				icon-class="bottom-div-close"
+		></ss-bottom-button>
+
+	</div>
+
+
+<input name='wdComponentID' type='hidden' value='grcz_grczAdd'/>
+
+</div></form>
+<script type="text/javascript">var wdRecordValue='${wdRecordValue}';</script>
+<script type="text/javascript" src="/ss/js/wdRecord.js"></script>
+<script type="text/javascript">(function(){wdRecord("grcz_grczAdd");})();</script>
+<script type="text/javascript" src="/ss/js/wdFitHeight.js"></script>
+<script type="text/javascript">initWdFitHeight(0)</script>
+<script type="text/javascript">initWdFitHeightFunction=function(){initWdFitHeight(0);};</script>
+<ss:equal val="${empty resizeComponent}" val2="false">
+<script>{var iframe=wd.display.getFrameOfWindow();
+if(iframe&&iframe.contentWindow==window)
+wd.display.resizeComponent(${resizeComponent.width}, ${resizeComponent.height}, ${empty resizeComponent.minHeight?'null':resizeComponent.minHeight}, ${empty resizeComponent.maxHeight?'null':resizeComponent.maxHeight});}</script>
 </ss:equal>
 <ss:help/>
 </body>
@@ -217,11 +225,18 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 }catch(err){console.error(err);}
 </script>
 </ss:equal>
+<script type="text/javascript" src="/js/validate/validator-rules.js"></script><%-- 功能说明:个人充值页接入桌面端校验规则,支持 SsInp 红线提示 by xu 20260323 --%>
+<script type="text/javascript" src="/js/validate/validation-manager.js"></script><%-- 功能说明:个人充值页接入桌面端校验管理器,支持 SsInp 红线提示 by xu 20260323 --%>
 
 </html>
 <%@ include file="/page/clip/footer.jsp" %>
 
 <script>
+	// 功能说明:维护个人充值页中需要强制输入负数的充值类别编码,供金额校验复用 by xu 20260323
+	var NEGATIVE_ONLY_GRCZLBM_MAP = {
+		"21": "反向充值"
+	};
+
 	function getFormAppVm(){
 		var appEl = document.getElementById("app");
 		if (!appEl || !appEl.__vue_app__ || !appEl.__vue_app__._instance) {
@@ -281,6 +296,123 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 		clearRySelection(vm);
 	}
 
+	// 功能说明:充值类别变化后延后一拍重跑金额校验,确保隐藏字段值更新后再清理红线状态 by xu 20260323
+	function handleGrczlbmChange(groupValue, value, label){
+		setTimeout(function(){
+			if (window.ssVm && typeof window.ssVm.validateField === "function") {
+				window.ssVm.validateField("je");
+				return;
+			}
+			validateNegativeAmountByCategory(false);
+		}, 0);
+	}
+
+	// 功能说明:初始化金额负数校验规则,接入 ssVm 统一红线与底部提示 by xu 20260323
+	function initNegativeAmountValidation(){
+		if (!window.ssVm || typeof window.ssVm.add !== "function" || window.ss.dom._grczNegativeAmountValidationInited) {
+			return;
+		}
+		window.ss.dom._grczNegativeAmountValidationInited = true;
+		window.ssVm.add("ss.commonValidator.custom", ["je"], {
+			msgPrfx: "金额",
+			relField: "grczlbm",
+			validate: function(value, categoryValue){
+				if (!isNegativeOnlyGrczlbm(categoryValue)) {
+					return true;
+				}
+				if (value == null || value.toString().trim() === "") {
+					return true;
+				}
+				if (isValidNegativeAmountText(value)) {
+					return true;
+				}
+				return {
+					valid: false,
+					message: NEGATIVE_ONLY_GRCZLBM_MAP[categoryValue] + "金额只能输入负数"
+				};
+			}
+		}, {
+			je: window.ss.dom.formElemConfig.je ? window.ss.dom.formElemConfig.je.val : ""
+		});
+	}
+
+	// 功能说明:根据充值类别判断当前金额是否必须为负数 by xu 20260323
+	function isNegativeOnlyGrczlbm(categoryValue){
+		var currentValue = categoryValue == null ? "" : categoryValue.toString();
+		return !!NEGATIVE_ONLY_GRCZLBM_MAP[currentValue];
+	}
+
+	// 功能说明:统一判断金额文本是否为合法负数,供提交校验和 ssVm 规则复用 by xu 20260323
+	function isValidNegativeAmountText(value){
+		var amountText = value == null ? "" : value.toString().trim();
+		if (!amountText) {
+			return false;
+		}
+		var amountNumber = Number(amountText);
+		return !isNaN(amountNumber) && amountNumber < 0;
+	}
+
+	// 功能说明:根据当前充值类别判断是否必须录入负数金额 by xu 20260323
+	function getCurrentGrczlbmValue(){
+		var vm = getFormAppVm();
+		if (vm && vm.grczlbm != null && vm.grczlbm !== "") {
+			return vm.grczlbm.toString();
+		}
+		var categoryElem = document.querySelector('[name="grczlbm"]');
+		return categoryElem && categoryElem.value != null ? categoryElem.value.toString() : "";
+	}
+
+	// 功能说明:统一校验金额字段在指定充值类别下是否为负数 by xu 20260323
+	function validateNegativeAmountByCategory(showMsg){
+		var categoryValue = getCurrentGrczlbmValue();
+		if (!isNegativeOnlyGrczlbm(categoryValue)) {
+			return true;
+		}
+
+		var jeElem = document.querySelector('[name="je"]');
+		if (!jeElem) {
+			return true;
+		}
+
+		if (isValidNegativeAmountText(jeElem.value)) {
+			return true;
+		}
+
+		if (showMsg !== false) {
+			alert(NEGATIVE_ONLY_GRCZLBM_MAP[categoryValue] + "金额只能输入负数");
+			jeElem.focus();
+		}
+		return false;
+	}
+
+	// 功能说明:个人充值页提交前先走 ssVm 全量校验,确保显示 SsInp 左侧红线与底部提示 by xu 20260323
+	function submitGrczForm(){
+		if (window.ssVm && window.ssVm.validations && window.ssVm.validations.size > 0) {
+			var validateResult = window.ssVm.validateAll();
+			if (!validateResult.valid) {
+				return false;
+			}
+		} else if (!validateNegativeAmountByCategory(true)) {
+			return false;
+		}
+
+		var formElem = document.querySelector("form");
+		if (!formElem) {
+			alert("表单不存在");
+			return false;
+		}
+
+		formElem.action = "<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:\"grcz\",dataType:\"update\"}'/>";
+		ss.display.resizeComponent(881,361,515,515);
+		formElem.submit();
+		return true;
+	}
+
+	SS.ready(function(){
+		// 功能说明:页面初始化后注册金额负数校验规则,保证 SsInp 输入时直接出现红线提示 by xu 20260323
+		initNegativeAmountValidation();
+	});
+
 	function handleBjChange(value){
 		if (value == null || value === "") {
 			return;
@@ -326,4 +458,4 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 			}
 		});
 	}
-</script>
+</script>

+ 332 - 182
page/grcz_grtfAdd.jsp

@@ -1,89 +1,75 @@
-<%@ page import="java.util.Map" %>
-<%@ page import="java.util.TreeMap" %>
-<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
-<%@ taglib uri="/ssTag" prefix="ss"%>
-
-<% pageContext.setAttribute(ss.page.PageC.PAGE_objName,"grcz");%>
-<%pageContext.setAttribute("wdpageinformation","{'hastab':'0'}");%>
-<!DOCTYPE html>
-<html>
-<head>
-<%@ include file="/page/clip/header.jsp" %>
-
-	<style>
-	  .table-container>tr>th{
-		  width:130px !important;
-	  }
-	  /* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
-	  .form-container .content-box {
-		  height: calc(100% - 80px) !important;
-	  }
-	  td{
-			display: flex;
-			align-items: center;
-			justify-content: flex-start;
-		}
-	</style>
-
-</head>
-<body class="env-input-body">
-<form method="post" id="app" class="form-container">
-<div class="content-box fit-height-content">
-
-	<div class="content-div" ssFith="true">
-		<table class='form'>
-
-			<tr>
-				<th style="width: 120px">退费类别</th>
-				<td >
-					
-<script>
-ss.dom.formElemConfig.grczlbm={val:'11',type:window.ss.dom.TYPE.ONOFFBTN};
-</script>
-
-
-					<%
-						Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
-						for (Integer key : grczlbMap.keySet()) {
-							pageContext.setAttribute("k",key);
-							pageContext.setAttribute("v",grczlbMap.get(key));
-					%>
-
-					<ss-onoff
-v-model="grczlbm"
-name="grczlbm"
-label="${v}"
-value="${k}"
-:multiple="false"
-:null="false"
-placeholder="${v}"
-v-model="grczlbm"
-:readonly="false"
-></ss-onoff>
-
-
-					<%
-						}
-					%>
-				</td>
-			</tr>
-			<%-- 再改,合并到 "班级" 里。Lin
-					<tr>
-						<th>人员类别</th>
-						<td >
-							<onoff@ss name="rylbm" mode="edit" rad="true" null="false" val="1100" />
-							<@input name="rylbm" value="学员" ssVal="1100" />
-							<@input name="rylbm" value="职工亲属" ssVal="1000" />
-						</td>
-					</tr>
-			--%>
-			<tr>
-				<th>班级/亲属</th>
-				<td >
-					
-<script>
-ss.dom.formElemConfig.bjid={val:null,type:window.ss.dom.TYPE.OBJP};
-</script>
+<%@ page import="java.util.Map" %>
+<%@ page import="java.util.TreeMap" %>
+<%@ page language="java" pageEncoding="UTF-8" isELIgnored="false" %>
+<%@ taglib uri="/ssTag" prefix="ss"%>
+
+<% pageContext.setAttribute(ss.page.PageC.PAGE_objName,"grcz");%>
+<%pageContext.setAttribute("wdpageinformation","{'hastab':'0'}");%>
+<!DOCTYPE html>
+<html>
+<head>
+<%@ include file="/page/clip/header.jsp" %>
+
+	<style>
+	  .table-container>tr>th{
+		  width:130px !important;
+	  }
+	  /* 把content-box的高度限制 从公共css 抽到具体有需要的页面 by xu 20251215 */
+	  .form-container .content-box {
+		  height: calc(100% - 80px) !important;
+	  }
+
+	</style>
+
+</head>
+<body class="env-input-body">
+<form method="post" id="app" class="form-container">
+<div class="content-box fit-height-content">
+
+	<div class="content-div" ssFith="true">
+		<table class='form'>
+
+			<tr>
+				<th style="width: 120px">退费类别</th>
+				<td >
+					
+<script>
+ss.dom.formElemConfig.grczlbm={val:'11',type:window.ss.dom.TYPE.ONOFFBTN};
+</script>
+
+
+					<%
+						Map<Integer,String > grczlbMap = (Map)(request.getAttribute("grczlbMap"));
+						for (Integer key : grczlbMap.keySet()) {
+							pageContext.setAttribute("k",key);
+							pageContext.setAttribute("v",grczlbMap.get(key));
+					%>
+
+					<ss-onoff
+v-model="grczlbm"
+name="grczlbm"
+label="${v}"
+value="${k}"
+:multiple="false"
+:null="false"
+placeholder="${v}"
+v-model="grczlbm"
+:readonly="false"
+onchange="handleGrczlbmChange"
+></ss-onoff>
+
+
+					<%
+						}
+					%>
+				</td>
+			
+				<th style="width: 120px">班级/亲属</th>
+				<td style="display: flex;align-items: center;">
+					
+<script>
+ss.dom.formElemConfig.bjid={val:null,type:window.ss.dom.TYPE.OBJP};
+</script>
 <ss-objp
 :opt="bjidOption"
 :inp="true"
@@ -95,12 +81,12 @@ name="bjid"
 width="200px"
 onChange="handleBjChange"
 ></ss-objp>
-
-					
-<script>
-ss.dom.formElemConfig.rylbm={val:'1100',type:window.ss.dom.TYPE.ONOFFBTN};
-</script>
-
+
+					
+<script>
+ss.dom.formElemConfig.rylbm={val:'1100',type:window.ss.dom.TYPE.ONOFFBTN};
+</script>
+
 					<ss-onoff
 v-model="rylbm"
 name="rylbm"
@@ -113,96 +99,117 @@ v-model="rylbm"
 :readonly="false"
 onchange="handleRylbmChange"
 ></ss-onoff>
-
-				</td>
-			</tr>
-
-<%-- 先去掉,接入读卡器时再加。Lin
-			<tr>
-				<th>卡号</th>
-				<td >
-					<@input name="kah"/>
-				</td>
-			</tr>
---%>
-
-			<tr>
-				<th>人员</th>
-				<td>
-					<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
-
-					
-<script>
-ss.dom.formElemConfig.ryid={val:null,type:window.ss.dom.TYPE.OBJP};
-</script>
-<ss-objp
-:opt="ryidOption"
-:inp="true"
-url="<ss:serv name='loadObjpOpt' parm='{"objectpickerdropdown":"1","objectpickerfilterField":"bjid,rylbm"}' />"
-cb="ryByBjOrRylb"
-v-model="ryid"
-name="ryid"
-:readonly="false"
-filterField="bjid,rylbm"
-onChange="selBaseInfoByRyid"
-></ss-objp>
-
-				</td>
-			</tr>
-			<tr>
-				<th>部门/班级</th>
-				<td id='bmbj'></td>
-			</tr>
-			<tr>
-				<th>姓名</th>
-				<td id='xm'></td>
-			</tr>
-			<tr>
-				<th>人员号</th>
-				<td id='ryh'></td>
-			</tr>
-			<tr>
-				<th>金额</th>
-				<td><input name="je"/></td>
-			</tr>
-			<tr>
-				<th>描述</th>
-				<td><input name="ms"/></td>
-			</tr>
-		</table>
-	</div>
-
-
-	<div class='bottom-div'>
-
-		<ss-bottom-button
-				id="saveAndCommit"
-				text="保存并提交"
-				onclick='ss.form.submit({url:"<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:"grcz",dataType:"update"}'/>",width:881,height:361,minHeight:515,maxHeight:515,targetWin:null});'<%-- 最后一个参数原来为targetWin:parent导致刷新了顶层窗口,改为null Ben(20251215) --%>
-				icon-class="bottom-div-save"
-		></ss-bottom-button>
-
-
-		<ss-bottom-button
-				text="关闭"
-				onclick='ss.display.closeDialog();'
-				icon-class="bottom-div-close"
-		></ss-bottom-button>
-
-	</div>
-
-
-<input name='wdComponentID' type='hidden' value='grcz_grtfAdd'/></div></form>
-<script type="text/javascript">var wdRecordValue='${wdRecordValue}';</script>
-<script type="text/javascript" src="/ss/js/wdRecord.js"></script>
-<script type="text/javascript">(function(){wdRecord("grcz_grtfAdd");})();</script>
-<script type="text/javascript" src="/ss/js/wdFitHeight.js"></script>
-<script type="text/javascript">initWdFitHeight(0)</script>
-<script type="text/javascript">initWdFitHeightFunction=function(){initWdFitHeight(0);};</script>
-<ss:equal val="${empty resizeComponent}" val2="false">
-<script>{var iframe=wd.display.getFrameOfWindow();
-if(iframe&&iframe.contentWindow==window)
-wd.display.resizeComponent(${resizeComponent.width}, ${resizeComponent.height}, ${empty resizeComponent.minHeight?'null':resizeComponent.minHeight}, ${empty resizeComponent.maxHeight?'null':resizeComponent.maxHeight});}</script>
+
+				</td>
+			</tr>
+
+<%-- 先去掉,接入读卡器时再加。Lin
+			<tr>
+				<th>卡号</th>
+				<td >
+					<@input name="kah"/>
+				</td>
+			</tr>
+--%>
+
+			<tr>
+				<th>人员</th>
+				<td>
+					<input name="czryid" type="hidden" value='${sessionScope.ssUser.ryid}'/> <%-- 操作人员ID。Lin --%>
+
+					
+<script>
+ss.dom.formElemConfig.ryid={val:null,type:window.ss.dom.TYPE.OBJP};
+</script>
+<ss-objp
+:opt="ryidOption"
+:inp="true"
+url="<ss:serv name='loadObjpOpt' parm='{"objectpickerdropdown":"1","objectpickerfilterField":"bjid,rylbm"}' />"
+cb="ryByBjOrRylb"
+v-model="ryid"
+name="ryid"
+:readonly="false"
+filterField="bjid,rylbm"
+onChange="selBaseInfoByRyid"
+></ss-objp>
+
+				</td>
+			
+				<th>部门/班级</th>
+				<td id='bmbj'></td>
+			</tr>
+			<tr>
+				<th>姓名</th>
+				<td id='xm'></td>
+			
+				<th>人员号</th>
+				<td id='ryh'></td>
+			</tr>
+			<tr>
+				<th>金额</th>
+				<td>
+					<script>
+					// 功能说明:退费金额字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+					ss.dom.formElemConfig.je={val:'',type:window.ss.dom.TYPE.INPUT};
+					</script>
+					<ss-inp
+	v-model="je"
+	name="je"
+	placeholder="请输入金额"
+					></ss-inp>
+				</td>
+			
+				<th>描述</th>
+				<td>
+					<script>
+					// 功能说明:退费描述字段改为 SsInp 组件,并声明 Vue 表单模型 by xu 20260323
+					ss.dom.formElemConfig.ms={val:'',type:window.ss.dom.TYPE.INPUT};
+					</script>
+					<ss-inp
+	v-model="ms"
+	name="ms"
+	placeholder="请输入描述"
+					></ss-inp>
+				</td>
+			</tr>
+			<tr>
+				<th>消费余额</th>
+				<td id='xfye' colspan="3"></td>
+			</tr>
+		</table>
+	</div>
+
+
+	<div class='bottom-div'>
+
+		<ss-bottom-button
+				id="saveAndCommit"
+				text="保存并提交"
+				onclick='submitGrtfForm();'<%-- 功能说明:个人退费页提交前先校验“个人退费”金额必须为负数 by xu 20260323 --%>
+				icon-class="bottom-div-save"
+		></ss-bottom-button>
+
+
+		<ss-bottom-button
+				text="关闭"
+				onclick='ss.display.closeDialog();'
+				icon-class="bottom-div-close"
+		></ss-bottom-button>
+
+	</div>
+
+
+<input name='wdComponentID' type='hidden' value='grcz_grtfAdd'/></div></form>
+<script type="text/javascript">var wdRecordValue='${wdRecordValue}';</script>
+<script type="text/javascript" src="/ss/js/wdRecord.js"></script>
+<script type="text/javascript">(function(){wdRecord("grcz_grtfAdd");})();</script>
+<script type="text/javascript" src="/ss/js/wdFitHeight.js"></script>
+<script type="text/javascript">initWdFitHeight(0)</script>
+<script type="text/javascript">initWdFitHeightFunction=function(){initWdFitHeight(0);};</script>
+<ss:equal val="${empty resizeComponent}" val2="false">
+<script>{var iframe=wd.display.getFrameOfWindow();
+if(iframe&&iframe.contentWindow==window)
+wd.display.resizeComponent(${resizeComponent.width}, ${resizeComponent.height}, ${empty resizeComponent.minHeight?'null':resizeComponent.minHeight}, ${empty resizeComponent.maxHeight?'null':resizeComponent.maxHeight});}</script>
 </ss:equal>
 <ss:help/>
 </body>
@@ -216,11 +223,35 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 }catch(err){console.error(err);}
 </script>
 </ss:equal>
+<script type="text/javascript" src="/js/validate/validator-rules.js"></script><%-- 功能说明:个人退费页接入桌面端校验规则,支持 SsInp 红线提示 by xu 20260323 --%>
+<script type="text/javascript" src="/js/validate/validation-manager.js"></script><%-- 功能说明:个人退费页接入桌面端校验管理器,支持 SsInp 红线提示 by xu 20260323 --%>
 
 </html>
 <%@ include file="/page/clip/footer.jsp" %>
 
 <script>
+	// 功能说明:根据当前退费类别渲染 code-label 映射,供金额负数校验复用 by xu 20260323
+	var GRTF_LABEL_MAP = {
+<%
+	for (Integer key : grczlbMap.keySet()) {
+%>
+		"<%=key%>": "<%=grczlbMap.get(key)%>",
+<%
+	}
+%>
+	};
+
+	// 功能说明:维护个人退费页中需要强制输入负数的退费类别名称,避免依赖固定编码 by xu 20260323
+	var NEGATIVE_ONLY_GRTF_LABEL_MAP = {
+		"个人退费": true
+	};
+
+	// 功能说明:记录当前退费类别的最新 value/label,避免 onoff 隐藏值切换时机导致金额校验判断失真 by xu 20260323
+	var CURRENT_GRTF_CATEGORY_STATE = {
+		value: "",
+		label: ""
+	};
+
 	function getFormAppVm(){
 		var appEl = document.getElementById("app");
 		if (!appEl || !appEl.__vue_app__ || !appEl.__vue_app__._instance) {
@@ -280,6 +311,125 @@ try{wd.display.setCloseWindowParam('${wdclosewindowparam}');
 		clearRySelection(vm);
 	}
 
+	// 功能说明:退费类别变化后延后一拍重跑金额校验,确保隐藏字段值更新后再清理红线状态 by xu 20260323
+	function handleGrczlbmChange(groupValue, value, label){
+		CURRENT_GRTF_CATEGORY_STATE.value = groupValue == null ? "" : groupValue.toString();
+		CURRENT_GRTF_CATEGORY_STATE.label = label == null ? "" : label.toString();
+		setTimeout(function(){
+			if (window.ssVm && typeof window.ssVm.validateField === "function") {
+				window.ssVm.validateField("je");
+				return;
+			}
+			validateNegativeAmountByCategory(false);
+		}, 0);
+	}
+
+	// 功能说明:初始化个人退费页金额负数校验规则,整页金额都必须输入负数 by xu 20260323
+	function initNegativeAmountValidation(){
+		if (!window.ssVm || typeof window.ssVm.add !== "function" || window.ss.dom._grczGrtfNegativeAmountValidationInited) {
+			return;
+		}
+		window.ss.dom._grczGrtfNegativeAmountValidationInited = true;
+		window.ssVm.add("ss.commonValidator.custom", ["je"], {
+			msgPrfx: "金额",
+			relField: "grczlbm",
+			validate: function(value, categoryValue){
+				var currentLabel = getCurrentGrczlbmLabel(categoryValue);
+				if (value == null || value.toString().trim() === "") {
+					return true;
+				}
+				if (isValidNegativeAmountText(value)) {
+					return true;
+				}
+				return {
+					valid: false,
+					message: (currentLabel || "退费") + "金额只能输入负数"
+				};
+			}
+		}, {
+			je: window.ss.dom.formElemConfig.je ? window.ss.dom.formElemConfig.je.val : ""
+		});
+	}
+
+	// 功能说明:根据当前退费类别值读取显示文案,供联动校验和错误提示复用 by xu 20260323
+	function getCurrentGrczlbmLabel(categoryValue){
+		var currentValue = categoryValue == null ? getCurrentGrczlbmValue() : categoryValue.toString();
+		if (CURRENT_GRTF_CATEGORY_STATE.label && (!currentValue || CURRENT_GRTF_CATEGORY_STATE.value === currentValue)) {
+			return CURRENT_GRTF_CATEGORY_STATE.label;
+		}
+		return GRTF_LABEL_MAP[currentValue] || "";
+	}
+
+	// 功能说明:统一判断金额文本是否为合法负数,供提交校验和 ssVm 规则复用 by xu 20260323
+	function isValidNegativeAmountText(value){
+		var amountText = value == null ? "" : value.toString().trim();
+		if (!amountText) {
+			return false;
+		}
+		var amountNumber = Number(amountText);
+		return !isNaN(amountNumber) && amountNumber < 0;
+	}
+
+	// 功能说明:根据当前退费类别值读取隐藏字段值,供金额校验复用 by xu 20260323
+	function getCurrentGrczlbmValue(){
+		var vm = getFormAppVm();
+		if (vm && vm.grczlbm != null && vm.grczlbm !== "") {
+			return vm.grczlbm.toString();
+		}
+		var categoryElem = document.querySelector('[name="grczlbm"]');
+		return categoryElem && categoryElem.value != null ? categoryElem.value.toString() : "";
+	}
+
+	// 功能说明:统一校验个人退费金额必须为负数,不再区分具体退费类别 by xu 20260323
+	function validateNegativeAmountByCategory(showMsg){
+		var currentLabel = getCurrentGrczlbmLabel();
+		var jeElem = document.querySelector('[name="je"]');
+		if (!jeElem) {
+			return true;
+		}
+
+		if (isValidNegativeAmountText(jeElem.value)) {
+			return true;
+		}
+
+		if (showMsg !== false) {
+			alert((currentLabel || "退费") + "金额只能输入负数");
+			jeElem.focus();
+		}
+		return false;
+	}
+
+	// 功能说明:个人退费页提交前先走 ssVm 全量校验,确保显示 SsInp 左侧红线与底部提示 by xu 20260323
+	function submitGrtfForm(){
+		if (window.ssVm && window.ssVm.validations && window.ssVm.validations.size > 0) {
+			var validateResult = window.ssVm.validateAll();
+			if (!validateResult.valid) {
+				return false;
+			}
+		} else if (!validateNegativeAmountByCategory(true)) {
+			return false;
+		}
+
+		var formElem = document.querySelector("form");
+		if (!formElem) {
+			alert("表单不存在");
+			return false;
+		}
+
+		formElem.action = "<ss:serv name='grcz_lr_tj' dest='addSure' parm='{thisViewObject:\"grcz\",dataType:\"update\"}'/>";
+		ss.display.resizeComponent(881,361,515,515);
+		formElem.submit();
+		return true;
+	}
+
+	SS.ready(function(){
+		// 功能说明:页面初始化后同步当前退费类别状态,避免首次校验读取不到最新类别文案 by xu 20260323
+		CURRENT_GRTF_CATEGORY_STATE.value = getCurrentGrczlbmValue();
+		CURRENT_GRTF_CATEGORY_STATE.label = GRTF_LABEL_MAP[CURRENT_GRTF_CATEGORY_STATE.value] || "";
+		// 功能说明:页面初始化后注册个人退费金额负数校验规则,保证 SsInp 输入时直接出现红线提示 by xu 20260323
+		initNegativeAmountValidation();
+	});
+
 	function handleBjChange(value){
 		if (value == null || value === "") {
 			return;

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini