validation-manager.js 11 KB

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