mixin.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586
  1. import XEUtils from 'xe-utils';
  2. import { VxeUI } from '../../../ui';
  3. import { eqEmptyValue, getFuncText } from '../../../ui/src/utils';
  4. import { scrollToView } from '../../../ui/src/dom';
  5. import { handleFieldOrColumn, getRowid } from '../../src/util';
  6. import { warnLog, errLog } from '../../../ui/src/log';
  7. const { getConfig, validators } = VxeUI;
  8. /**
  9. * 校验规则
  10. */
  11. class Rule {
  12. constructor(rule) {
  13. Object.defineProperty(this, "$options", {
  14. enumerable: true,
  15. configurable: true,
  16. writable: true,
  17. value: void 0
  18. });
  19. Object.assign(this, {
  20. $options: rule,
  21. required: rule.required,
  22. min: rule.min,
  23. max: rule.max,
  24. type: rule.type,
  25. pattern: rule.pattern,
  26. validator: rule.validator,
  27. trigger: rule.trigger,
  28. maxWidth: rule.maxWidth
  29. });
  30. }
  31. /**
  32. * 获取校验不通过的消息
  33. * 支持国际化翻译
  34. */
  35. get content() {
  36. return getFuncText(this.$options.content || this.$options.message);
  37. }
  38. get message() {
  39. return this.content;
  40. }
  41. }
  42. // 如果存在 pattern,判断正则
  43. function validREValue(pattern, val) {
  44. if (pattern && !(XEUtils.isRegExp(pattern) ? pattern : new RegExp(pattern)).test(val)) {
  45. return false;
  46. }
  47. return true;
  48. }
  49. // 如果存在 max,判断最大值
  50. function validMaxValue(max, num) {
  51. if (!XEUtils.eqNull(max) && num > XEUtils.toNumber(max)) {
  52. return false;
  53. }
  54. return true;
  55. }
  56. // 如果存在 min,判断最小值
  57. function validMinValue(min, num) {
  58. if (!XEUtils.eqNull(min) && num < XEUtils.toNumber(min)) {
  59. return false;
  60. }
  61. return true;
  62. }
  63. function validRuleValue(rule, val, required) {
  64. const { type, min, max, pattern } = rule;
  65. const isArrType = type === 'array';
  66. const isNumType = type === 'number';
  67. const isStrType = type === 'string';
  68. const strVal = `${val}`;
  69. if (!validREValue(pattern, strVal)) {
  70. return false;
  71. }
  72. if (isArrType) {
  73. if (!XEUtils.isArray(val)) {
  74. return false;
  75. }
  76. if (required) {
  77. if (!val.length) {
  78. return false;
  79. }
  80. }
  81. if (!validMinValue(min, val.length)) {
  82. return false;
  83. }
  84. if (!validMaxValue(max, val.length)) {
  85. return false;
  86. }
  87. }
  88. else if (isNumType) {
  89. const numVal = Number(val);
  90. if (isNaN(numVal)) {
  91. return false;
  92. }
  93. if (!validMinValue(min, numVal)) {
  94. return false;
  95. }
  96. if (!validMaxValue(max, numVal)) {
  97. return false;
  98. }
  99. }
  100. else {
  101. if (isStrType) {
  102. if (!XEUtils.isString(val)) {
  103. return false;
  104. }
  105. }
  106. if (required) {
  107. if (!strVal) {
  108. return false;
  109. }
  110. }
  111. if (!validMinValue(min, strVal.length)) {
  112. return false;
  113. }
  114. if (!validMaxValue(max, strVal.length)) {
  115. return false;
  116. }
  117. }
  118. return true;
  119. }
  120. function checkRuleStatus(rule, val) {
  121. const { required } = rule;
  122. const isEmptyVal = XEUtils.isArray(val) ? !val.length : eqEmptyValue(val);
  123. if (required) {
  124. if (isEmptyVal) {
  125. return false;
  126. }
  127. if (!validRuleValue(rule, val, required)) {
  128. return false;
  129. }
  130. }
  131. else {
  132. if (!isEmptyVal) {
  133. if (!validRuleValue(rule, val, required)) {
  134. return false;
  135. }
  136. }
  137. }
  138. return true;
  139. }
  140. export default {
  141. methods: {
  142. /**
  143. * 完整校验,和 validate 的区别就是会给有效数据中的每一行进行校验
  144. */
  145. _fullValidate(rows, cb) {
  146. if (XEUtils.isFunction(cb)) {
  147. warnLog('vxe.error.notValidators', ['fullValidate(rows, callback)', 'fullValidate(rows)']);
  148. }
  149. return this.beginValidate(rows, null, cb, true);
  150. },
  151. /**
  152. * 快速校验,如果存在记录不通过的记录,则返回不再继续校验(异步校验除外)
  153. */
  154. _validate(rows, cb) {
  155. if (XEUtils.isFunction(cb)) {
  156. warnLog('vxe.error.notValidators', ['validate(rows, callback)', 'validate(rows)']);
  157. }
  158. return this.beginValidate(rows, null, cb);
  159. },
  160. /**
  161. * 完整校验单元格,和 validateField 的区别就是会给有效数据中的每一行进行校验
  162. */
  163. _fullValidateField(rows, fieldOrColumn) {
  164. const colList = (XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : (fieldOrColumn ? [fieldOrColumn] : [])).map(column => handleFieldOrColumn(this, column));
  165. if (colList.length) {
  166. return this.beginValidate(rows, colList, null, true);
  167. }
  168. return this.$nextTick();
  169. },
  170. /**
  171. * 快速校验单元格,如果存在记录不通过的记录,则返回不再继续校验(异步校验除外)
  172. */
  173. _validateField(rows, fieldOrColumn) {
  174. const colList = (XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : (fieldOrColumn ? [fieldOrColumn] : [])).map(column => handleFieldOrColumn(this, column));
  175. if (colList.length) {
  176. return this.beginValidate(rows, colList, null);
  177. }
  178. return this.$nextTick();
  179. },
  180. /**
  181. * 聚焦到校验通过的单元格并弹出校验错误提示
  182. */
  183. handleValidError(params) {
  184. const $xeTable = this;
  185. const { validOpts } = this;
  186. return new Promise(resolve => {
  187. if (validOpts.autoPos === false) {
  188. $xeTable.dispatchEvent('valid-error', params, null);
  189. resolve();
  190. }
  191. else {
  192. this.handleEdit(params, { type: 'valid-error', trigger: 'call' }).then(() => {
  193. setTimeout(() => {
  194. resolve(this.showValidTooltip(params));
  195. }, 10);
  196. });
  197. }
  198. });
  199. },
  200. handleErrMsgMode(validErrMaps) {
  201. const { validOpts } = this;
  202. if (validOpts.msgMode === 'single') {
  203. const keys = Object.keys(validErrMaps);
  204. const resMaps = {};
  205. if (keys.length) {
  206. const firstKey = keys[0];
  207. resMaps[firstKey] = validErrMaps[firstKey];
  208. }
  209. return resMaps;
  210. }
  211. return validErrMaps;
  212. },
  213. /**
  214. * 对表格数据进行校验
  215. * 如果不指定数据,则默认只校验临时变动的数据,例如新增或修改
  216. * 如果传 true 则校验当前表格数据
  217. * 如果传 row 指定行记录,则只验证传入的行
  218. * 如果传 rows 为多行记录,则只验证传入的行
  219. * 如果只传 callback 否则默认验证整个表格数据
  220. * 返回 Promise 对象,或者使用回调方式
  221. */
  222. beginValidate(rows, cols, cb, isFull) {
  223. const $xeTable = this;
  224. const props = $xeTable;
  225. const reactData = $xeTable;
  226. const internalData = $xeTable;
  227. const validRest = {};
  228. const { editRules, treeConfig } = props;
  229. const { isRowGroupStatus } = reactData;
  230. const { afterFullData, pendingRowMaps, removeRowMaps } = internalData;
  231. const treeOpts = $xeTable.computeTreeOpts;
  232. const aggregateOpts = $xeTable.computeAggregateOpts;
  233. let validList;
  234. if (rows === true) {
  235. validList = afterFullData;
  236. }
  237. else if (rows) {
  238. if (XEUtils.isFunction(rows)) {
  239. cb = rows;
  240. }
  241. else {
  242. validList = XEUtils.isArray(rows) ? rows : [rows];
  243. }
  244. }
  245. if (!validList) {
  246. validList = this.getInsertRecords().concat(this.getUpdateRecords());
  247. }
  248. const rowValidErrs = [];
  249. this.lastCallTime = Date.now();
  250. this.validRuleErr = false; // 如果为快速校验,当存在某列校验不通过时将终止执行
  251. this.clearValidate();
  252. const validErrMaps = {};
  253. if (editRules) {
  254. const columns = cols && cols.length ? cols : this.getColumns();
  255. const handleVaild = (row) => {
  256. const rowid = getRowid($xeTable, row);
  257. // 是否删除
  258. if (removeRowMaps[rowid]) {
  259. return;
  260. }
  261. // 是否标记删除
  262. if (pendingRowMaps[rowid]) {
  263. return;
  264. }
  265. if ($xeTable.isAggregateRecord(row)) {
  266. return;
  267. }
  268. if (isFull || !this.validRuleErr) {
  269. const colVailds = [];
  270. columns.forEach((column) => {
  271. const field = XEUtils.isString(column) ? column : column.field;
  272. if ((isFull || !this.validRuleErr) && XEUtils.has(editRules, field)) {
  273. colVailds.push(this.validCellRules('all', row, column)
  274. .catch(({ rule, rules }) => {
  275. const rest = {
  276. rule,
  277. rules,
  278. rowIndex: this.getRowIndex(row),
  279. row,
  280. columnIndex: this.getColumnIndex(column),
  281. column,
  282. field,
  283. $table: this
  284. };
  285. if (!validRest[field]) {
  286. validRest[field] = [];
  287. }
  288. validErrMaps[`${getRowid(this, row)}:${column.id}`] = {
  289. column,
  290. row,
  291. rule,
  292. content: rule.content
  293. };
  294. validRest[field].push(rest);
  295. if (!isFull) {
  296. this.validRuleErr = true;
  297. return Promise.reject(rest);
  298. }
  299. }));
  300. }
  301. });
  302. rowValidErrs.push(Promise.all(colVailds));
  303. }
  304. };
  305. if (isRowGroupStatus) {
  306. XEUtils.eachTree(validList, handleVaild, { children: aggregateOpts.mapChildrenField });
  307. }
  308. else if (treeConfig) {
  309. const childrenField = treeOpts.children || treeOpts.childrenField;
  310. XEUtils.eachTree(validList, handleVaild, { children: childrenField });
  311. }
  312. else {
  313. validList.forEach(handleVaild);
  314. }
  315. return Promise.all(rowValidErrs).then(() => {
  316. const ruleProps = Object.keys(validRest);
  317. this.validErrorMaps = this.handleErrMsgMode(validErrMaps);
  318. return this.$nextTick().then(() => {
  319. if (ruleProps.length) {
  320. return Promise.reject(validRest[ruleProps[0]][0]);
  321. }
  322. if (cb) {
  323. cb();
  324. }
  325. });
  326. }).catch(firstErrParams => {
  327. return new Promise((resolve, reject) => {
  328. const finish = () => {
  329. this.$nextTick(() => {
  330. if (cb) {
  331. cb(validRest);
  332. resolve();
  333. }
  334. else {
  335. if (getConfig().validToReject === 'obsolete') {
  336. // 已废弃,校验失败将不会执行catch
  337. reject(validRest);
  338. }
  339. else {
  340. resolve(validRest);
  341. }
  342. }
  343. });
  344. };
  345. const posAndFinish = () => {
  346. firstErrParams.cell = this.getCellElement(firstErrParams.row, firstErrParams.column);
  347. scrollToView(firstErrParams.cell);
  348. this.handleValidError(firstErrParams).then(finish);
  349. };
  350. /**
  351. * 当校验不通过时
  352. * 将表格滚动到可视区
  353. * 由于提示信息至少需要占一行,定位向上偏移一行
  354. */
  355. if (this.validOpts.autoPos === false) {
  356. finish();
  357. }
  358. else {
  359. const row = firstErrParams.row;
  360. const column = firstErrParams.column;
  361. this.scrollToRow(row, column).then(posAndFinish);
  362. }
  363. });
  364. });
  365. }
  366. else {
  367. this.validErrorMaps = {};
  368. }
  369. return this.$nextTick().then(() => {
  370. if (cb) {
  371. cb();
  372. }
  373. });
  374. },
  375. hasCellRules(type, row, column) {
  376. const { editRules } = this;
  377. const { property } = column;
  378. if (property && editRules) {
  379. const rules = XEUtils.get(editRules, property);
  380. return rules && XEUtils.find(rules, rule => type === 'all' || !rule.trigger || type === rule.trigger);
  381. }
  382. return false;
  383. },
  384. /**
  385. * 校验数据
  386. * 按表格行、列顺序依次校验(同步或异步)
  387. * 校验规则根据索引顺序依次校验,如果是异步则会等待校验完成才会继续校验下一列
  388. * 如果校验失败则,触发回调或者Promise<不通过列的错误消息>
  389. * 如果是传回调方式这返回一个校验不通过列的错误消息
  390. *
  391. * rule 配置:
  392. * required=Boolean 是否必填
  393. * min=Number 最小长度
  394. * max=Number 最大长度
  395. * validator=Function({ cellValue, rule, rules, row, column, rowIndex, columnIndex }) 自定义校验,接收一个 Promise
  396. * trigger=blur|change 触发方式(除非特殊场景,否则默认为空就行)
  397. */
  398. validCellRules(validType, row, column, val) {
  399. const { editRules } = this;
  400. const { property } = column;
  401. const errorRules = [];
  402. const syncValidList = [];
  403. if (property && editRules) {
  404. const rules = XEUtils.get(editRules, property);
  405. if (rules) {
  406. const cellValue = XEUtils.isUndefined(val) ? XEUtils.get(row, property) : val;
  407. rules.forEach((rule) => {
  408. const { trigger, validator } = rule;
  409. if (validType === 'all' || !trigger || validType === trigger) {
  410. if (validator) {
  411. const validParams = {
  412. cellValue,
  413. rule,
  414. rules,
  415. row,
  416. rowIndex: this.getRowIndex(row),
  417. column,
  418. columnIndex: this.getColumnIndex(column),
  419. field: column.property,
  420. $table: this
  421. };
  422. let customValid;
  423. if (XEUtils.isString(validator)) {
  424. const gvItem = validators.get(validator);
  425. if (gvItem) {
  426. const tcvMethod = gvItem.tableCellValidatorMethod || gvItem.cellValidatorMethod;
  427. if (tcvMethod) {
  428. customValid = tcvMethod(validParams);
  429. }
  430. else {
  431. errLog('vxe.error.notValidators', [validator]);
  432. }
  433. }
  434. else {
  435. errLog('vxe.error.notValidators', [validator]);
  436. }
  437. }
  438. else {
  439. customValid = validator(validParams);
  440. }
  441. if (customValid) {
  442. if (XEUtils.isError(customValid)) {
  443. this.validRuleErr = true;
  444. errorRules.push(new Rule({ type: 'custom', trigger, content: customValid.message, rule: new Rule(rule) }));
  445. }
  446. else if (customValid.catch) {
  447. // 如果为异步校验(注:异步校验是并发无序的)
  448. syncValidList.push(customValid.catch((e) => {
  449. this.validRuleErr = true;
  450. errorRules.push(new Rule({ type: 'custom', trigger, content: e && e.message ? e.message : (rule.content || rule.message), rule: new Rule(rule) }));
  451. }));
  452. }
  453. }
  454. }
  455. else {
  456. if (!checkRuleStatus(rule, cellValue)) {
  457. this.validRuleErr = true;
  458. errorRules.push(new Rule(rule));
  459. }
  460. }
  461. }
  462. });
  463. }
  464. }
  465. return Promise.all(syncValidList).then(() => {
  466. if (errorRules.length) {
  467. const rest = { rules: errorRules, rule: errorRules[0] };
  468. return Promise.reject(rest);
  469. }
  470. });
  471. },
  472. _clearValidate(rows, fieldOrColumn) {
  473. const { validOpts, validErrorMaps } = this;
  474. const validTip = this.$refs.refValidTooltip;
  475. const rowList = XEUtils.isArray(rows) ? rows : (rows ? [rows] : []);
  476. const colList = (XEUtils.isArray(fieldOrColumn) ? fieldOrColumn : (fieldOrColumn ? [fieldOrColumn] : [])).map(column => handleFieldOrColumn(this, column));
  477. let validErrMaps = {};
  478. if (validTip && validTip.visible) {
  479. validTip.close();
  480. }
  481. // 如果是单个提示模式
  482. if (validOpts.msgMode === 'single') {
  483. this.validErrorMaps = {};
  484. return this.$nextTick();
  485. }
  486. if (rowList.length && colList.length) {
  487. validErrMaps = Object.assign({}, validErrorMaps);
  488. rowList.forEach(row => {
  489. colList.forEach((column) => {
  490. const validKey = `${getRowid(this, row)}:${column.id}`;
  491. if (validErrMaps[validKey]) {
  492. delete validErrMaps[validKey];
  493. }
  494. });
  495. });
  496. }
  497. else if (rowList.length) {
  498. const rowIdList = rowList.map(row => `${getRowid(this, row)}`);
  499. XEUtils.each(validErrorMaps, (item, key) => {
  500. if (rowIdList.indexOf(key.split(':')[0]) > -1) {
  501. validErrMaps[key] = item;
  502. }
  503. });
  504. }
  505. else if (colList.length) {
  506. const colidList = colList.map(column => `${column.id}`);
  507. XEUtils.each(validErrorMaps, (item, key) => {
  508. if (colidList.indexOf(key.split(':')[1]) > -1) {
  509. validErrMaps[key] = item;
  510. }
  511. });
  512. }
  513. this.validErrorMaps = validErrMaps;
  514. return this.$nextTick();
  515. },
  516. /**
  517. * 触发校验
  518. */
  519. triggerValidate(type) {
  520. const { editConfig, editStore, editRules, editOpts, validOpts } = this;
  521. const { actived } = editStore;
  522. // 检查清除校验消息
  523. if (editRules && validOpts.msgMode === 'single') {
  524. this.validErrorMaps = {};
  525. }
  526. // 校验单元格
  527. if (editConfig && editRules && actived.row) {
  528. const { row, column, cell } = actived.args;
  529. if (this.hasCellRules(type, row, column)) {
  530. return this.validCellRules(type, row, column).then(() => {
  531. if (editOpts.mode === 'row') {
  532. this.clearValidate(row, column);
  533. }
  534. }).catch(({ rule }) => {
  535. // 如果校验不通过与触发方式一致,则聚焦提示错误,否则跳过并不作任何处理
  536. if (!rule.trigger || type === rule.trigger) {
  537. const rest = { rule, row, column, cell };
  538. this.showValidTooltip(rest);
  539. return Promise.reject(rest);
  540. }
  541. return Promise.resolve();
  542. });
  543. }
  544. }
  545. return Promise.resolve();
  546. },
  547. /**
  548. * 弹出校验错误提示
  549. */
  550. showValidTooltip(params) {
  551. const $xeTable = this;
  552. const { $refs, height, validStore, validErrorMaps, tableData, validOpts } = this;
  553. const { rule, row, column, cell } = params;
  554. const validTip = $refs.refValidTooltip;
  555. const content = rule.content;
  556. validStore.visible = true;
  557. if (validOpts.msgMode === 'single') {
  558. this.validErrorMaps = {
  559. [`${getRowid(this, row)}:${column.id}`]: {
  560. column,
  561. row,
  562. rule,
  563. content
  564. }
  565. };
  566. }
  567. else {
  568. this.validErrorMaps = Object.assign({}, validErrorMaps, {
  569. [`${getRowid(this, row)}:${column.id}`]: {
  570. column,
  571. row,
  572. rule,
  573. content
  574. }
  575. });
  576. }
  577. $xeTable.dispatchEvent('valid-error', params, null);
  578. if (validTip) {
  579. if (validTip && (validOpts.message === 'tooltip' || (validOpts.message === 'default' && !height && tableData.length < 2))) {
  580. return validTip.open(cell, content);
  581. }
  582. }
  583. return this.$nextTick();
  584. }
  585. }
  586. };