uni-forms.vue 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. <template>
  2. <view class="uni-forms">
  3. <form @submit.stop="submitForm" @reset="resetForm">
  4. <slot></slot>
  5. </form>
  6. </view>
  7. </template>
  8. <script>
  9. /**
  10. * Forms 表单
  11. * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
  12. * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
  13. * @property {Object} rules 表单校验规则
  14. * @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
  15. * @value bind 发生变化时触发
  16. * @value submit 提交时触发
  17. * @property {String} labelPosition = [top|left] label 位置 默认 left 可选
  18. * @value top 顶部显示 label
  19. * @value left 左侧显示 label
  20. * @property {String} labelWidth label 宽度,默认 65px
  21. * @property {String} labelAlign = [left|center|right] label 居中方式 默认 left 可选
  22. * @value left label 左侧显示
  23. * @value center label 居中
  24. * @value right label 右侧对齐
  25. * @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式
  26. * @value undertext 错误信息在底部显示
  27. * @value toast 错误信息toast显示
  28. * @value modal 错误信息modal显示
  29. */
  30. import Vue from 'vue'
  31. Vue.prototype.binddata = function(name, value, formName) {
  32. if (formName) {
  33. this.$refs[formName].setValue(name, value)
  34. } else {
  35. let refName = null
  36. for (let i in this.$refs) {
  37. if (this.$refs[i] && this.$refs[i].$options.name === 'uniForms') {
  38. refName = i
  39. break
  40. }
  41. }
  42. if (!refName) return console.error('当前 uni-froms 组件缺少 ref 属性')
  43. this.$refs[refName].setValue(name, value)
  44. }
  45. }
  46. import Validator from './validate.js'
  47. export default {
  48. name: 'uniForms',
  49. props: {
  50. value: {
  51. type: Object,
  52. default () {
  53. return {}
  54. }
  55. },
  56. // 表单校验规则
  57. rules: {
  58. type: Object,
  59. default () {
  60. return {}
  61. }
  62. },
  63. // 校验触发器方式,默认 关闭
  64. validateTrigger: {
  65. type: String,
  66. default: ''
  67. },
  68. // label 位置,可选值 top/left
  69. labelPosition: {
  70. type: String,
  71. default: 'left'
  72. },
  73. // label 宽度,单位 px
  74. labelWidth: {
  75. type: [String, Number],
  76. default: 65
  77. },
  78. // label 居中方式,可选值 left/center/right
  79. labelAlign: {
  80. type: String,
  81. default: 'left'
  82. },
  83. errShowType: {
  84. type: String,
  85. default: 'undertext'
  86. }
  87. },
  88. data() {
  89. return {
  90. formData: {}
  91. };
  92. },
  93. watch: {
  94. rules(newVal) {
  95. this.init(newVal)
  96. },
  97. trigger(trigger) {
  98. this.formTrigger = trigger
  99. },
  100. value: {
  101. handler(newVal) {
  102. if (this.isChildEdit) {
  103. this.isChildEdit = false
  104. return
  105. }
  106. this.childrens.forEach((item) => {
  107. if (item.name) {
  108. this.formData[item.name] = this._getValue(item, newVal[item.name])
  109. }
  110. })
  111. },
  112. deep: true
  113. }
  114. },
  115. created() {
  116. let _this = this
  117. this.childrens = []
  118. this.formRules = []
  119. this.init(this.rules)
  120. },
  121. methods: {
  122. init(formRules) {
  123. if (Object.keys(formRules).length > 0) {
  124. this.formTrigger = this.trigger
  125. this.formRules = formRules
  126. if (!this.validator) {
  127. this.validator = new Validator(formRules)
  128. }
  129. this.childrens.forEach((item) => {
  130. item.init()
  131. })
  132. }
  133. },
  134. /**
  135. * 设置校验规则
  136. * @param {Object} formRules
  137. */
  138. setRules(formRules) {
  139. this.init(formRules)
  140. },
  141. /**
  142. * 公开给用户使用
  143. * 设置自定义表单组件 value 值
  144. * @param {String} name 字段名称
  145. * @param {String} value 字段值
  146. */
  147. setValue(name, value, callback) {
  148. let example = this.childrens.find(child => child.name === name)
  149. if (!example) return null
  150. this.isChildEdit = true
  151. console.log(value);
  152. value = this._getValue(example, value)
  153. this.formData[name] = value
  154. example.val = value
  155. console.log(value,Object.assign({}, this.value, this.formData));
  156. this.$emit('input', Object.assign({}, this.value, this.formData))
  157. return example.triggerCheck(value, callback)
  158. },
  159. /**
  160. * TODO 表单提交, 小程序暂不支持这种用法
  161. * @param {Object} event
  162. */
  163. submitForm(event) {
  164. const value = event.detail.value
  165. return this.validateAll(value || this.formData, 'submit')
  166. },
  167. /**
  168. * 表单重置
  169. * @param {Object} event
  170. */
  171. resetForm(event) {
  172. this.childrens.forEach(item => {
  173. item.errMsg = ''
  174. item.val = ''
  175. item.$emit('input', '')
  176. })
  177. this.isChildEdit = true
  178. this.childrens.forEach((item) => {
  179. if (item.name) {
  180. this.formData[item.name] = this._getValue(item, '')
  181. }
  182. })
  183. this.$emit('input', this.formData)
  184. this.$emit('reset', event)
  185. },
  186. /**
  187. * 触发表单校验,通过 @validate 获取
  188. * @param {Object} validate
  189. */
  190. validateCheck(validate) {
  191. if (validate === null) validate = null
  192. this.$emit('validate', validate)
  193. },
  194. /**
  195. * 校验所有或者部分表单
  196. */
  197. async validateAll(invalidFields, type, callback) {
  198. if (!this.validator) {
  199. this.$emit('submit', {
  200. detail: {
  201. value: invalidFields,
  202. errors: null
  203. }
  204. })
  205. return
  206. }
  207. this.childrens.forEach(item => {
  208. item.errMsg = ''
  209. })
  210. let promise;
  211. if (callback && typeof callback !== 'function' && Promise) {
  212. promise = new Promise((resolve, reject) => {
  213. callback = function(valid, invalidFields) {
  214. !valid ? resolve(invalidFields) : reject(valid);
  215. };
  216. });
  217. }
  218. let fieldsValue = {}
  219. let tempInvalidFields = Object.assign({}, invalidFields)
  220. Object.keys(this.formRules).forEach(item => {
  221. const values = this.formRules[item]
  222. const rules = (values && values.rules) || []
  223. let isNoField = false
  224. for (let i = 0; i < rules.length; i++) {
  225. const rule = rules[i]
  226. if (rule.required) {
  227. isNoField = true
  228. break
  229. }
  230. }
  231. // 如果存在 required 才会将内容插入校验对象
  232. if (!isNoField && (!tempInvalidFields[item] && tempInvalidFields[item] !== false)) {
  233. delete tempInvalidFields[item]
  234. }
  235. })
  236. // 循环字段是否存在于校验规则中
  237. for (let i in this.formRules) {
  238. for (let j in tempInvalidFields) {
  239. if (i === j) {
  240. fieldsValue[i] = tempInvalidFields[i]
  241. }
  242. }
  243. }
  244. let result = await this.validator.invokeValidateUpdate(fieldsValue, true)
  245. if (Array.isArray(result)) {
  246. if (result.length === 0) result = null
  247. }
  248. let example = null
  249. if (result) {
  250. for (let i = 0; i < result.length; i++) {
  251. const item = result[i]
  252. example = this.childrens.find(child => child.name === item.key)
  253. if (this.errShowType === 'undertext') {
  254. if (example) example.errMsg = item.errorMessage
  255. } else {
  256. if (this.errShowType === 'toast') {
  257. uni.showToast({
  258. title: item.errorMessage || '校验错误',
  259. icon: 'none'
  260. })
  261. break
  262. } else if (this.errShowType === 'modal') {
  263. uni.showModal({
  264. title: '提示',
  265. content: item.errorMessage || '校验错误'
  266. })
  267. break
  268. } else {
  269. if (example) example.errMsg = item.errorMessage
  270. }
  271. }
  272. }
  273. }
  274. if (type === 'submit') {
  275. this.$emit('submit', {
  276. detail: {
  277. value: invalidFields,
  278. errors: result
  279. }
  280. })
  281. } else {
  282. this.$emit('validate', result)
  283. }
  284. callback && typeof callback === 'function' && callback(result ? false : true, result ? result : invalidFields)
  285. if (promise && callback) return promise
  286. },
  287. /**
  288. * 外部调用方法
  289. * 手动提交校验表单
  290. * 对整个表单进行校验的方法,参数为一个回调函数。
  291. */
  292. submit() {
  293. return this.validateAll(this.formData, 'submit')
  294. },
  295. /**
  296. * 外部调用方法
  297. * 校验表单
  298. * 对整个表单进行校验的方法,参数为一个回调函数。
  299. */
  300. validate(callback) {
  301. return this.validateAll(this.formData, '', callback)
  302. },
  303. /**
  304. * 部分表单校验
  305. * @param {Object} props
  306. * @param {Object} cb
  307. */
  308. validateField(props, callback) {
  309. props = [].concat(props);
  310. let invalidFields = {}
  311. this.childrens.forEach(item => {
  312. // item.parentVal((val, name) => {
  313. if (props.indexOf(item.name) !== -1) {
  314. invalidFields = Object.assign({}, invalidFields, {
  315. [item.name]: this.formData[item.name]
  316. })
  317. }
  318. // })
  319. })
  320. return this.validateAll(invalidFields, '', callback)
  321. },
  322. /**
  323. * 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
  324. */
  325. resetFields() {
  326. this.resetForm()
  327. },
  328. /**
  329. * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
  330. */
  331. clearValidate(props) {
  332. props = [].concat(props);
  333. this.childrens.forEach(item => {
  334. if (props.length === 0) {
  335. item.errMsg = ''
  336. } else {
  337. if (props.indexOf(item.name) !== -1) {
  338. item.errMsg = ''
  339. }
  340. }
  341. })
  342. },
  343. // 把 value 转换成指定的类型
  344. _getValue(item, value) {
  345. const rules = item.formRules.rules || []
  346. const isRuleNum = rules.find(val => val.format && this.type_filter(val.format))
  347. const isRuleBool = rules.find(val => val.format && val.format === 'boolean' || val.format === 'bool')
  348. // 输入值为 number
  349. if (isRuleNum) {
  350. value = value === '' ? null : Number(value)
  351. }
  352. // 简单判断真假值
  353. if (isRuleBool) {
  354. value = !value ? false : true
  355. }
  356. return value
  357. },
  358. // 过滤数字类型
  359. type_filter(format) {
  360. return format === 'int' || format === 'double' || format === 'number'
  361. }
  362. }
  363. }
  364. </script>
  365. <style lang="scss">
  366. </style>