uni-forms.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <template>
  2. <view class="uni-forms" :class="{ 'uni-forms--top': !border }">
  3. <form @submit.stop="submitForm" @reset="resetForm">
  4. <slot></slot>
  5. </form>
  6. </view>
  7. </template>
  8. <script>
  9. // #ifndef VUE3
  10. import Vue from 'vue';
  11. import Validator from './validate.js';
  12. Vue.prototype.binddata = function (name, value, formName) {
  13. if (formName) {
  14. this.$refs[formName].setValue(name, value);
  15. } else {
  16. let formVm;
  17. for (let i in this.$refs) {
  18. const vm = this.$refs[i];
  19. if (vm && vm.$options && vm.$options.name === 'uniForms') {
  20. formVm = vm;
  21. break;
  22. }
  23. }
  24. if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
  25. formVm.setValue(name, value);
  26. }
  27. };
  28. // #endif
  29. /**
  30. * Forms 表单
  31. * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
  32. * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
  33. * @property {Object} rules 表单校验规则
  34. * @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit
  35. * @value bind 发生变化时触发
  36. * @value submit 提交时触发
  37. * @property {String} labelPosition = [top|left] label 位置 默认 left
  38. * @value top 顶部显示 label
  39. * @value left 左侧显示 label
  40. * @property {String} labelWidth label 宽度,默认 65px
  41. * @property {String} labelAlign = [left|center|right] label 居中方式 默认 left
  42. * @value left label 左侧显示
  43. * @value center label 居中
  44. * @value right label 右侧对齐
  45. * @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式
  46. * @value undertext 错误信息在底部显示
  47. * @value toast 错误信息toast显示
  48. * @value modal 错误信息modal显示
  49. * @event {Function} submit 提交时触发
  50. */
  51. export default {
  52. name: 'uniForms',
  53. components: {},
  54. emits: ['input', 'reset', 'validate', 'submit'],
  55. props: {
  56. // 即将弃用
  57. value: {
  58. type: Object,
  59. default() {
  60. return {};
  61. }
  62. },
  63. // 替换 value 属性
  64. modelValue: {
  65. type: Object,
  66. default() {
  67. return {};
  68. }
  69. },
  70. // 表单校验规则
  71. rules: {
  72. type: Object,
  73. default() {
  74. return {};
  75. }
  76. },
  77. // 校验触发器方式,默认 关闭
  78. validateTrigger: {
  79. type: String,
  80. default: ''
  81. },
  82. // label 位置,可选值 top/left
  83. labelPosition: {
  84. type: String,
  85. default: 'left'
  86. },
  87. // label 宽度,单位 px
  88. labelWidth: {
  89. type: [String, Number],
  90. default: ''
  91. },
  92. // label 居中方式,可选值 left/center/right
  93. labelAlign: {
  94. type: String,
  95. default: 'left'
  96. },
  97. errShowType: {
  98. type: String,
  99. default: 'undertext'
  100. },
  101. border: {
  102. type: Boolean,
  103. default: false
  104. }
  105. },
  106. data() {
  107. return {
  108. formData: {}
  109. };
  110. },
  111. computed: {
  112. dataValue() {
  113. if (JSON.stringify(this.modelValue) === '{}') {
  114. return this.value
  115. } else {
  116. return this.modelValue
  117. }
  118. }
  119. },
  120. watch: {
  121. rules(newVal) {
  122. // 如果规则发生变化,要初始化组件
  123. this.init(newVal);
  124. },
  125. labelPosition() {
  126. this.childrens.forEach(vm => {
  127. vm.init()
  128. })
  129. }
  130. },
  131. created() {
  132. // #ifdef VUE3
  133. let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
  134. if (!getbinddata) {
  135. getApp().$vm.$.appContext.config.globalProperties.binddata = function (name, value, formName) {
  136. if (formName) {
  137. this.$refs[formName].setValue(name, value);
  138. } else {
  139. let formVm;
  140. for (let i in this.$refs) {
  141. const vm = this.$refs[i];
  142. if (vm && vm.$options && vm.$options.name === 'uniForms') {
  143. formVm = vm;
  144. break;
  145. }
  146. }
  147. if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
  148. formVm.setValue(name, value);
  149. }
  150. }
  151. }
  152. // #endif
  153. // 存放watch 监听数组
  154. this.unwatchs = [];
  155. // 存放子组件数组
  156. this.childrens = [];
  157. // 存放 easyInput 组件
  158. this.inputChildrens = [];
  159. // 存放 dataCheckbox 组件
  160. this.checkboxChildrens = [];
  161. // 存放规则
  162. this.formRules = [];
  163. this.init(this.rules);
  164. },
  165. // mounted() {
  166. // this.init(this.rules)
  167. // },
  168. methods: {
  169. init(formRules) {
  170. // 判断是否有规则
  171. if (Object.keys(formRules).length === 0) {
  172. this.formData = this.dataValue
  173. return
  174. }
  175. ;
  176. this.formRules = formRules;
  177. this.validator = new Validator(formRules);
  178. this.registerWatch();
  179. },
  180. // 监听 watch
  181. registerWatch() {
  182. // 取消监听,避免多次调用 init 重复执行 $watch
  183. this.unwatchs.forEach(v => v());
  184. this.childrens.forEach((v) => {
  185. v.init()
  186. })
  187. // watch 每个属性 ,需要知道具体那个属性发变化
  188. Object.keys(this.dataValue).forEach(key => {
  189. let watch = this.$watch(
  190. 'dataValue.' + key,
  191. value => {
  192. if (!value) return
  193. // 如果是对象 ,则平铺内容
  194. if (value.toString() === '[object Object]') {
  195. for (let i in value) {
  196. let name = `${key}[${i}]`;
  197. this.formData[name] = this._getValue(name, value[i]);
  198. }
  199. } else {
  200. this.formData[key] = this._getValue(key, value);
  201. }
  202. },
  203. {
  204. deep: true,
  205. immediate: true
  206. }
  207. );
  208. this.unwatchs.push(watch);
  209. });
  210. },
  211. /**
  212. * 公开给用户使用
  213. * 设置校验规则
  214. * @param {Object} formRules
  215. */
  216. setRules(formRules) {
  217. this.init(formRules);
  218. },
  219. /**
  220. * 公开给用户使用
  221. * 设置自定义表单组件 value 值
  222. * @param {String} name 字段名称
  223. * @param {String} value 字段值
  224. */
  225. setValue(name, value, callback) {
  226. let example = this.childrens.find(child => child.name === name);
  227. if (!example) return null;
  228. value = this._getValue(example.name, value);
  229. this.formData[name] = value;
  230. example.val = value;
  231. return example.triggerCheck(value, callback);
  232. },
  233. /**
  234. * 表单重置
  235. * @param {Object} event
  236. */
  237. resetForm(event) {
  238. this.childrens.forEach(item => {
  239. item.errMsg = '';
  240. const inputComp = this.inputChildrens.find(child => child.rename === item.name);
  241. if (inputComp) {
  242. inputComp.errMsg = '';
  243. // fix by mehaotian 不触发其他组件的 setValue
  244. inputComp.is_reset = true
  245. inputComp.$emit('input', inputComp.multiple ? [] : '');
  246. inputComp.$emit('update:modelValue', inputComp.multiple ? [] : '');
  247. }
  248. });
  249. this.childrens.forEach(item => {
  250. if (item.name) {
  251. this.formData[item.name] = this._getValue(item.name, '');
  252. }
  253. });
  254. this.$emit('reset', event);
  255. },
  256. /**
  257. * 触发表单校验,通过 @validate 获取
  258. * @param {Object} validate
  259. */
  260. validateCheck(validate) {
  261. if (validate === null) validate = null;
  262. this.$emit('validate', validate);
  263. },
  264. /**
  265. * 校验所有或者部分表单
  266. */
  267. async validateAll(invalidFields, type, keepitem, callback) {
  268. let childrens = []
  269. for (let i in invalidFields) {
  270. const item = this.childrens.find(v => v.name === i)
  271. if (item) {
  272. childrens.push(item)
  273. }
  274. }
  275. if (!callback && typeof keepitem === 'function') {
  276. callback = keepitem;
  277. }
  278. let promise;
  279. if (!callback && typeof callback !== 'function' && Promise) {
  280. promise = new Promise((resolve, reject) => {
  281. callback = function (valid, invalidFields) {
  282. !valid ? resolve(invalidFields) : reject(valid);
  283. };
  284. });
  285. }
  286. let results = [];
  287. let newFormData = {};
  288. if (this.validator) {
  289. for (let key in childrens) {
  290. const child = childrens[key];
  291. let name = child.isArray ? child.arrayField : child.name;
  292. if (child.isArray) {
  293. if (child.name.indexOf('[') !== -1 && child.name.indexOf(']') !== -1) {
  294. const fieldData = child.name.split('[');
  295. const fieldName = fieldData[0];
  296. const fieldValue = fieldData[1].replace(']', '');
  297. if (!newFormData[fieldName]) {
  298. newFormData[fieldName] = {};
  299. }
  300. newFormData[fieldName][fieldValue] = this._getValue(name, invalidFields[name]);
  301. }
  302. } else {
  303. newFormData[name] = this._getValue(name, invalidFields[name]);
  304. }
  305. const result = await child.triggerCheck(invalidFields[name], true);
  306. if (result) {
  307. results.push(result);
  308. if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
  309. }
  310. }
  311. } else {
  312. newFormData = invalidFields
  313. }
  314. if (Array.isArray(results)) {
  315. if (results.length === 0) results = null;
  316. }
  317. if (Array.isArray(keepitem)) {
  318. keepitem.forEach(v => {
  319. newFormData[v] = this.dataValue[v];
  320. });
  321. }
  322. if (type === 'submit') {
  323. this.$emit('submit', {
  324. detail: {
  325. value: newFormData,
  326. errors: results
  327. }
  328. });
  329. } else {
  330. this.$emit('validate', results);
  331. }
  332. callback && typeof callback === 'function' && callback(results, newFormData);
  333. if (promise && callback) {
  334. return promise;
  335. } else {
  336. return null;
  337. }
  338. },
  339. submitForm() {
  340. },
  341. /**
  342. * 外部调用方法
  343. * 手动提交校验表单
  344. * 对整个表单进行校验的方法,参数为一个回调函数。
  345. */
  346. submit(keepitem, callback, type) {
  347. for (let i in this.dataValue) {
  348. const itemData = this.childrens.find(v => v.name === i);
  349. if (itemData) {
  350. if (this.formData[i] === undefined) {
  351. this.formData[i] = this._getValue(i, this.dataValue[i]);
  352. }
  353. }
  354. }
  355. if (!type) {
  356. console.warn('submit 方法即将废弃,请使用validate方法代替!');
  357. }
  358. return this.validateAll(this.formData, 'submit', keepitem, callback);
  359. },
  360. /**
  361. * 外部调用方法
  362. * 校验表单
  363. * 对整个表单进行校验的方法,参数为一个回调函数。
  364. */
  365. validate(keepitem, callback) {
  366. return this.submit(keepitem, callback, true);
  367. },
  368. /**
  369. * 部分表单校验
  370. * @param {Object} props
  371. * @param {Object} cb
  372. */
  373. validateField(props, callback) {
  374. props = [].concat(props);
  375. let invalidFields = {};
  376. this.childrens.forEach(item => {
  377. if (props.indexOf(item.name) !== -1) {
  378. invalidFields = Object.assign({}, invalidFields, {
  379. [item.name]: this.formData[item.name]
  380. });
  381. }
  382. });
  383. return this.validateAll(invalidFields, 'submit', [], callback);
  384. },
  385. /**
  386. * 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
  387. */
  388. resetFields() {
  389. this.resetForm();
  390. },
  391. /**
  392. * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
  393. */
  394. clearValidate(props) {
  395. props = [].concat(props);
  396. this.childrens.forEach(item => {
  397. const inputComp = this.inputChildrens.find(child => child.rename === item.name);
  398. if (props.length === 0) {
  399. item.errMsg = '';
  400. if (inputComp) {
  401. inputComp.errMsg = '';
  402. }
  403. } else {
  404. if (props.indexOf(item.name) !== -1) {
  405. item.errMsg = '';
  406. if (inputComp) {
  407. inputComp.errMsg = '';
  408. }
  409. }
  410. }
  411. });
  412. },
  413. /**
  414. * 把 value 转换成指定的类型
  415. * @param {Object} key
  416. * @param {Object} value
  417. */
  418. _getValue(key, value) {
  419. const rules = (this.formRules[key] && this.formRules[key].rules) || [];
  420. const isRuleNum = rules.find(val => val.format && this.type_filter(val.format));
  421. const isRuleBool = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
  422. // 输入值为 number
  423. if (isRuleNum) {
  424. value = isNaN(value) ? value : value === '' || value === null ? null : Number(value);
  425. }
  426. // 简单判断真假值
  427. if (isRuleBool) {
  428. value = !value ? false : true;
  429. }
  430. return value;
  431. },
  432. /**
  433. * 过滤数字类型
  434. * @param {Object} format
  435. */
  436. type_filter(format) {
  437. return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
  438. }
  439. }
  440. };
  441. </script>
  442. <style lang="scss">
  443. .uni-forms {
  444. // overflow: hidden;
  445. // padding: 10px 15px;
  446. }
  447. .uni-forms--top {
  448. // padding: 10px 15px;
  449. // padding-top: 22px;
  450. }
  451. </style>