validation-manager.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. // 工具方法
  2. const findParentTd = (element) => {
  3. let parent = element.parentElement;
  4. while (parent && parent.tagName !== 'TD') {
  5. parent = parent.parentElement;
  6. }
  7. return parent;
  8. };
  9. const isSameTd = (element1, element2) => {
  10. const td1 = findParentTd(element1);
  11. const td2 = findParentTd(element2);
  12. return td1 && td1 === td2;
  13. };
  14. class ValidationManager {
  15. constructor() {
  16. this.validations = new Map();
  17. this.dependRules = new Map(); // 存储依赖关系
  18. }
  19. // 添加验证规则
  20. add(ruleName, fields, options = {}, fieldValMap, priority = 1) {//增加fieldValMap参数,传入表单元素在页面打开时的默认值 Ben(20260107)
  21. if (!Array.isArray(fields)) {
  22. fields = [fields];
  23. }
  24. const rule = this._parseRule(ruleName);
  25. if (!rule) return;
  26. fields.forEach(field => {
  27. if (!this.validations.has(field)) {
  28. this.validations.set(field, []);
  29. }
  30. let val = fieldValMap[field];//add by Ben(20260107)
  31. this.validations.get(field).push({
  32. rule: rule,
  33. options: {
  34. msgPrfx: options.msgPrfx || field,
  35. ...options
  36. },
  37. priority: priority,
  38. 'val':val, //传入的val值,用于页面刚打开时初始化非空红线时判断用 by Ben(20260107)
  39. 'ruleName':ruleName //传入ruleName规则名称,用于页面刚打开时初始化非空红线时判断用 by Ben(20260107)
  40. });
  41. });
  42. if (options.relField) {
  43. const relElement = document.querySelector(`[name="${options.relField}"]`);
  44. if (relElement) {
  45. relElement.addEventListener('change', () => {
  46. this.validateField(field);
  47. });
  48. }
  49. }
  50. this.initRequiredMarks();
  51. // ssVm.bindForm(".form-container");
  52. }
  53. // 验证单个字段
  54. validateField(field) {
  55. const element = document.querySelector(`[name="${field}"]`);
  56. if (!element) return { valid: true };
  57. const td = findParentTd(element);
  58. if (!td) return { valid: true };
  59. // 获取同一个 td 内的所有需要验证的字段
  60. const tdFields = Array.from(td.querySelectorAll('[name]'))
  61. .map(el => el.getAttribute('name'))
  62. .filter(name => this.validations.has(name));
  63. // 验证所有字段并合并错误信息
  64. let errors = [];
  65. let hasEmptyRequired = false; // 是否有空的必填字段
  66. for (const f of tdFields) {
  67. const element = document.querySelector(`[name="${f}"]`);
  68. const value = element.value;
  69. // 功能说明:只有存在 notNull 规则且值为空时,才认为是空的必填字段 by xu 20260402
  70. const fieldValidations = this.validations.get(f);
  71. const hasNotNullRule = fieldValidations && fieldValidations.some(v => v.ruleName && v.ruleName.includes('notNull'));
  72. if (hasNotNullRule && (!value || value.trim() === '')) {
  73. hasEmptyRequired = true;
  74. }
  75. const result = this._doValidate(f);
  76. if (!result.valid && errors.indexOf(result.message) === -1) {
  77. errors.push(result.message);
  78. }
  79. }
  80. // 检查是否有依赖规则
  81. const dependRule = this.dependRules.get(field);
  82. if (dependRule) {
  83. const { dependField, rules } = dependRule;
  84. const dependElement = document.querySelector(`[name="${dependField}"]`);
  85. if (dependElement) {
  86. const dependValue = dependElement.value;
  87. const validatorName = rules[dependValue];
  88. if (validatorName) {
  89. // 更新验证规则
  90. this.validations.set(field, [{
  91. rule: window.ValidatorRules[validatorName],
  92. options: {
  93. msgPrfx: field,
  94. required: true
  95. }
  96. }]);
  97. }
  98. }
  99. }
  100. // 管理必填标记:有错误时显示红线(包括非空错误和其他校验错误)by xu 20260402
  101. let requiredMark = td.querySelector('.required');
  102. const hasErrors = errors.length > 0;
  103. if ((hasEmptyRequired || hasErrors) && !requiredMark) {
  104. requiredMark = document.createElement('div');
  105. requiredMark.className = 'required';
  106. td.appendChild(requiredMark);
  107. } else if (!hasEmptyRequired && !hasErrors && requiredMark) {
  108. requiredMark.remove();
  109. }
  110. // 动态管理错误提示
  111. let errTip = td.querySelector('.err-tip');
  112. if (!errTip && errors.length > 0) {
  113. errTip = document.createElement('div');
  114. errTip.className = 'err-tip';
  115. errTip.style.position = 'absolute';
  116. errTip.style.zIndex = '1';
  117. errTip.innerHTML = `
  118. <div class="tip">${errors.join(';')}</div>
  119. <div class="tip-more">${errors.join(';')}</div>
  120. `;
  121. td.appendChild(errTip);
  122. } else if (errTip && errors.length === 0) {
  123. errTip.remove();
  124. } else if (errTip && errors.length > 0) {
  125. errTip.querySelector('.tip').textContent = errors.join(';');
  126. errTip.querySelector('.tip-more').textContent = errors.join(';');
  127. }
  128. return {
  129. valid: errors.length === 0,
  130. message: errors.join(';')
  131. };
  132. }
  133. // 验证所有字段
  134. validateAll() {
  135. const errors = [];
  136. for (const field of this.validations.keys()) {
  137. const result = this.validateField(field);
  138. if (!result.valid) {
  139. errors.push({
  140. field: field,
  141. message: result.message
  142. });
  143. }
  144. }
  145. console.log("errors",errors);
  146. console.log("this.validations",this.validations);
  147. return {
  148. valid: errors.length === 0,
  149. errors: errors
  150. };
  151. }
  152. // 解析规则名称并获取对应的验证规则
  153. _parseRule(ruleName) {
  154. const parts = ruleName.split('.');
  155. const actualRuleName = parts[parts.length - 1];
  156. return ValidatorRules[actualRuleName];
  157. }
  158. //执行所有表单校验,并显示校验结果
  159. validateAllAndShowMsg(){
  160. const result = this.validateAll();
  161. console.log("validateAll",result);
  162. if (!result.valid) {
  163. e.preventDefault();
  164. result.errors.forEach(error => {
  165. const element = document.querySelector(`[name="${error.field}"]`);
  166. if (element) {
  167. const validateComponent = element.closest('.input-container')?.querySelector('.err-tip');
  168. if (validateComponent) {
  169. validateComponent.querySelector('.tip').textContent = error.message;
  170. validateComponent.querySelector('.tip-more').textContent = error.message;
  171. }
  172. }
  173. });
  174. return false;
  175. }
  176. return true;
  177. }
  178. // 绑定到表单提交(本方法已暂停使用)
  179. bindForm(formClass) {
  180. // 创建一个观察器实例
  181. const observer = new MutationObserver((mutations) => {
  182. const form = document.querySelector(formClass);
  183. if (form && !form._bound) {
  184. console.log("Found form, binding submit event");
  185. $(form).on('submit', (e) => {
  186. return this.validateAllAndShowMsg();
  187. });
  188. form._bound = true; // 标记已绑定
  189. observer.disconnect(); // 停止观察
  190. }
  191. });
  192. // 开始观察
  193. observer.observe(document.body, {
  194. childList: true,
  195. subtree: true
  196. });
  197. }
  198. remove(fields) {
  199. // 如果传入单个字段,转换为数组
  200. if (!Array.isArray(fields)) {
  201. fields = [fields];
  202. }
  203. // 遍历字段并移除验证规则
  204. fields.forEach(field => {
  205. if (this.validations.has(field)) {
  206. this.validations.delete(field);
  207. }
  208. });
  209. }
  210. // 私有验证方法
  211. _doValidate(field) {
  212. const rules = this.validations.get(field);
  213. if (!rules) return { valid: true };
  214. const element = document.querySelector(`[name="${field}"]`);
  215. if (!element) return { valid: true };
  216. const value = element.value;
  217. for (const {rule, options} of rules) {
  218. // 如果设置了 required 且值为空,进行必填验证
  219. if (options.required && (!value || value.trim() === '')) {
  220. return {
  221. valid: false,
  222. message: `${options.msgPrfx}不能为空`
  223. };
  224. }
  225. // 执行规则验证,传入完整的 options 以支持关联字段验证
  226. const isValid = rule.validate(value, options);
  227. if (!isValid) {
  228. let message = rule.message;
  229. message = message.replace('{field}', options.msgPrfx);
  230. Object.keys(options).forEach(key => {
  231. message = message.replace(`{${key}}`, options[key]);
  232. });
  233. return {
  234. valid: false,
  235. message: message
  236. };
  237. }
  238. }
  239. return { valid: true };
  240. }
  241. // 初始化必填标记
  242. initRequiredMarks() {
  243. // 先收集所有需要处理的 td
  244. const processedTds = new Set();
  245. // 遍历所有带验证规则的字段
  246. for (const [field, rules] of this.validations.entries()) {
  247. const element = document.querySelector(`[name="${field}"]`);
  248. if (!element) continue;
  249. const td = findParentTd(element);
  250. if (!td || processedTds.has(td)) continue; // 跳过已处理的 td
  251. processedTds.add(td); // 标记该 td 已处理
  252. // 获取同一个 td 内的所有需要验证的字段
  253. const tdFields = Array.from(td.querySelectorAll('[name]'))
  254. .map(el => el.getAttribute('name'))
  255. .filter(name => {//条件改为:如果字段存在非空校验规则,且当前值为空,filter才通过返回tdField Ben(20260107)
  256. let vArr = this.validations.get(name);
  257. for(let i=0;i<vArr.length;i++){
  258. let v = vArr[i];
  259. console.log('@@v:'+JSON.stringify(v));
  260. if(v&&(v.ruleName==='ss.commonValidator.notNull')&&(v.val==='0'||v.val===''||v.val===undefined||v.val===null))
  261. return true;
  262. else
  263. return false;
  264. }
  265. });
  266. // 只要有验证规则就添加必填标记
  267. if (tdFields.length > 0) {
  268. let requiredMark = td.querySelector('.required');
  269. if (!requiredMark) {
  270. requiredMark = document.createElement('div');
  271. requiredMark.className = 'required';
  272. td.appendChild(requiredMark);
  273. }
  274. }
  275. }
  276. }
  277. // 验证字段但不显示错误信息
  278. validateFieldSilent(field) {
  279. const rules = this.validations.get(field);
  280. if (!rules) return { valid: true };
  281. const element = document.querySelector(`[name="${field}"]`);
  282. if (!element) return { valid: true };
  283. const value = element.value;
  284. for (const {rule, options} of rules) {
  285. if (options.required && (!value || value.trim() === '')) {
  286. return { valid: false };
  287. }
  288. if (!value && !options.required) {
  289. return { valid: true };
  290. }
  291. const isValid = rule.validate(value, options);
  292. if (!isValid) {
  293. return { valid: false };
  294. }
  295. }
  296. return { valid: true };
  297. }
  298. }
  299. // 创建全局实例
  300. window.ssVm = window.ssVm || new ValidationManager();
  301. // 在 DOM 加载完成后初始化必填标记