uni-forms-item.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. <template>
  2. <view class="uni-forms-item" :class="{'uni-forms-item-custom':custom}" :style="[fieldStyle]">
  3. <template v-if="!custom">
  4. <view class="uni-forms-item-inner" :class="[ 'uni-label-postion-' + labelPos]">
  5. <view :class="errorTop ? 'uni-error-in-label' : ''">
  6. <view class="uni-forms-item-label" :class="[required ? 'uni-required' : '']" :style="{
  7. justifyContent: justifyContent,
  8. width: labelWid +'px',
  9. marginBottom: labelMarginBottom,
  10. }">
  11. <view class="uni-icon-wrap" v-if="leftIcon">
  12. <uni-icons size="16" :type="leftIcon" :color="iconColor" />
  13. </view>
  14. <slot name="leftIcon"></slot>
  15. <text class="uni-label-text" :class="[leftIcon ? 'uni-label-left-gap' : '']">{{ label }}</text>
  16. </view>
  17. <view v-if="errorTop && showMessage" class="uni-error-message" :style="{paddingLeft: '4px'}">{{ showMsg === 'undertext' ? msg:'' }}</view>
  18. </view>
  19. <view class="fild-body">
  20. <slot></slot>
  21. </view>
  22. </view>
  23. <view v-if="errorBottom && showMessage" class="uni-error-message" :style="{
  24. paddingLeft: Number(labelWid) + 4 + 'px'
  25. }">{{ showMsg === 'undertext' ? msg:'' }}</view>
  26. </template>
  27. <template v-else>
  28. <slot></slot>
  29. </template>
  30. </view>
  31. </template>
  32. <script>
  33. /**
  34. * Field 输入框
  35. * @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
  36. * @tutorial https://ext.dcloud.net.cn/plugin?id=21001
  37. * @property {Boolean} required 是否必填,左边显示红色"*"号(默认false)
  38. * @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
  39. * @value bind 发生变化时触发
  40. * @value submit 提交时触发
  41. * @property {String } leftIcon label左边的图标,限uni-ui的图标名称
  42. * @property {String } iconColor 左边通过icon配置的图标的颜色(默认#606266)
  43. * @property {String } label 输入框左边的文字提示
  44. * @property {Number } labelWidth label的宽度,单位px(默认65)
  45. * @property {String } labelAlign = [left|center|right] label的文字对齐方式(默认left)
  46. * @value left label 左侧显示
  47. * @value center label 居中
  48. * @value right label 右侧对齐
  49. * @property {String } labelPosition = [top|left] label的文字的位置(默认left)
  50. * @value top 顶部显示 label
  51. * @value left 左侧显示 label
  52. * @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
  53. * @property {String } name 表单域的属性名,在使用校验规则时必填
  54. */
  55. export default {
  56. name: "uniFormsItem",
  57. props: {
  58. // 自定义内容
  59. custom: {
  60. type: Boolean,
  61. default: false
  62. },
  63. // 是否显示报错信息
  64. showMessage: {
  65. type: Boolean,
  66. default: true
  67. },
  68. name: String,
  69. required: Boolean,
  70. validateTrigger: {
  71. type: String,
  72. default: ''
  73. },
  74. leftIcon: String,
  75. iconColor: {
  76. type: String,
  77. default: '#606266'
  78. },
  79. label: String,
  80. // 左边标题的宽度单位px
  81. labelWidth: {
  82. type: [Number, String],
  83. default: ''
  84. },
  85. // 对齐方式,left|center|right
  86. labelAlign: {
  87. type: String,
  88. default: ''
  89. },
  90. // lable的位置,可选为 left-左边,top-上边
  91. labelPosition: {
  92. type: String,
  93. default: ''
  94. },
  95. errorMessage: {
  96. type: [String, Boolean],
  97. default: ''
  98. }
  99. },
  100. data() {
  101. return {
  102. errorTop: false,
  103. errorBottom: false,
  104. labelMarginBottom: '',
  105. errorWidth: '',
  106. errMsg: '',
  107. val: '',
  108. labelPos: '',
  109. labelWid: '',
  110. labelAli: '',
  111. showMsg: 'undertext'
  112. };
  113. },
  114. computed: {
  115. msg() {
  116. return this.errorMessage || this.errMsg;
  117. },
  118. fieldStyle() {
  119. let style = {}
  120. if (this.labelPos == 'top') {
  121. style.padding = '10px 14px'
  122. this.labelMarginBottom = '6px'
  123. }
  124. if (this.labelPos == 'left' && this.msg !== false && this.msg != '') {
  125. style.paddingBottom = '0px'
  126. this.errorBottom = true
  127. this.errorTop = false
  128. } else if (this.labelPos == 'top' && this.msg !== false && this.msg != '') {
  129. this.errorBottom = false
  130. this.errorTop = true
  131. } else {
  132. // style.paddingBottom = ''
  133. this.errorTop = false
  134. this.errorBottom = false
  135. }
  136. return style
  137. },
  138. // uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
  139. justifyContent() {
  140. if (this.labelAli === 'left') return 'flex-start';
  141. if (this.labelAli === 'center') return 'center';
  142. if (this.labelAli === 'right') return 'flex-end';
  143. }
  144. },
  145. watch: {
  146. validateTrigger(trigger) {
  147. this.formTrigger = trigger
  148. }
  149. },
  150. created() {
  151. this.form = this.getForm()
  152. this.formRules = []
  153. this.formTrigger = this.validateTrigger
  154. if (this.form) {
  155. this.form.childrens.push(this)
  156. }
  157. this.init()
  158. },
  159. destroyed() {
  160. // if (this.name) {
  161. // delete this.form.formData[this.name]
  162. // }
  163. if (this.form) {
  164. this.form.childrens.forEach((item, index) => {
  165. if (item === this) {
  166. this.form.childrens.splice(index, 1)
  167. }
  168. })
  169. }
  170. },
  171. methods: {
  172. init() {
  173. if (this.form) {
  174. const {
  175. formRules,
  176. validator,
  177. formData,
  178. value,
  179. labelPosition,
  180. labelWidth,
  181. labelAlign,
  182. errShowType
  183. } = this.form
  184. this.labelPos = this.labelPosition ? this.labelPosition : labelPosition
  185. this.labelWid = this.labelWidth ? this.labelWidth : labelWidth
  186. this.labelAli = this.labelAlign ? this.labelAlign : labelAlign
  187. this.showMsg = errShowType
  188. if (formRules) {
  189. this.formRules = formRules[this.name] || {}
  190. }
  191. this.validator = validator
  192. if (this.name) {
  193. formData[this.name] = value.hasOwnProperty(this.name) ? value[this.name] : null
  194. }
  195. } else {
  196. this.labelPos = this.labelPosition || 'left'
  197. this.labelWid = this.labelWidth || 65
  198. this.labelAli = this.labelAlign || 'left'
  199. }
  200. },
  201. /**
  202. * 获取父元素实例
  203. */
  204. getForm() {
  205. let parent = this.$parent;
  206. let parentName = parent.$options.name;
  207. while (parentName !== 'uniForms') {
  208. parent = parent.$parent;
  209. if (!parent) return false
  210. parentName = parent.$options.name;
  211. }
  212. return parent;
  213. },
  214. /**
  215. * 移除该表单项的校验结果
  216. */
  217. clearValidate() {
  218. this.errMsg = ''
  219. },
  220. /**
  221. * 父组件处理函数
  222. * @param {Object} callback
  223. */
  224. // parentVal(callback) {
  225. // typeof(callback) === 'function' && callback({
  226. // [this.name]: this.form.formData[this.name]
  227. // }, this.name)
  228. // },
  229. /**
  230. * 校验规则
  231. * @param {Object} value
  232. */
  233. triggerCheck(value, callback) {
  234. let promise = null;
  235. this.errMsg = ''
  236. // if no callback, return promise
  237. if (callback && typeof callback !== 'function' && Promise) {
  238. promise = new Promise((resolve, reject) => {
  239. callback = function(valid) {
  240. !valid ? resolve(valid) : reject(valid)
  241. };
  242. });
  243. }
  244. if (!this.validator) {
  245. typeof callback === 'function' && callback(null);
  246. if (promise) return promise
  247. }
  248. const isNoField = this.isRequired(this.formRules.rules || [])
  249. // const rules = this.formRules.rules || []
  250. // const rule = rules.find(item => item.format && this.type_filter(item.format))
  251. // // 输入值为 number
  252. // if (rule) {
  253. // value = value === '' ? null : Number(value)
  254. // }
  255. // this.form.formData[this.name] = value
  256. let result = this.validator && this.validator.validateUpdate({
  257. [this.name]: value
  258. })
  259. // 判断是否必填
  260. if (!isNoField && !value) {
  261. result = null
  262. }
  263. let isTrigger = this.isTrigger(this.formRules.validateTrigger, this.validateTrigger, this.form.validateTrigger)
  264. if (!isTrigger) {
  265. result = null
  266. }
  267. if (isTrigger && result && result.errorMessage) {
  268. if (this.form.errShowType === 'toast') {
  269. uni.showToast({
  270. title: result.errorMessage || '校验错误',
  271. icon: 'none'
  272. })
  273. }
  274. if (this.form.errShowType === 'modal') {
  275. uni.showModal({
  276. title: '提示',
  277. content: result.errorMessage || '校验错误'
  278. })
  279. }
  280. }
  281. this.errMsg = !result ? '' : result.errorMessage
  282. this.form.validateCheck(result ? result : null)
  283. typeof callback === 'function' && callback(result ? result : null);
  284. if (promise) return promise
  285. },
  286. /**
  287. * 触发时机
  288. * @param {Object} event
  289. */
  290. isTrigger(rule, itemRlue, parentRule) {
  291. let rl = true;
  292. // bind submit
  293. if (rule === 'submit' || !rule) {
  294. if (rule === undefined) {
  295. if (itemRlue !== 'bind') {
  296. if (!itemRlue) {
  297. return parentRule === 'bind' ? true : false
  298. }
  299. return false
  300. }
  301. return true
  302. }
  303. return false
  304. }
  305. return true;
  306. },
  307. // 是否有必填字段
  308. isRequired(rules) {
  309. let isNoField = false
  310. for (let i = 0; i < rules.length; i++) {
  311. const ruleData = rules[i]
  312. if (ruleData.required) {
  313. isNoField = true
  314. break
  315. }
  316. }
  317. return isNoField
  318. }
  319. }
  320. };
  321. </script>
  322. <style lang="scss" scoped>
  323. .uni-forms-item {
  324. position: relative;
  325. // padding: 10px 14px;
  326. text-align: left;
  327. color: #333;
  328. font-size: 14px;
  329. margin-bottom: 22px;
  330. }
  331. .uni-forms-item-inner {
  332. display: flex;
  333. align-items: center;
  334. }
  335. .uni-textarea-inner {
  336. align-items: flex-start;
  337. }
  338. .uni-textarea-class {
  339. min-height: 48px;
  340. width: auto;
  341. font-size: 14px;
  342. }
  343. .fild-body {
  344. width: 100%;
  345. // display: flex;
  346. // flex: 1;
  347. // align-items: center;
  348. }
  349. .uni-arror-right {
  350. margin-left: 4px;
  351. }
  352. .uni-label-text {
  353. display: inline-block;
  354. }
  355. .uni-label-left-gap {
  356. margin-left: 3px;
  357. }
  358. .uni-label-postion-top {
  359. flex-direction: column;
  360. align-items: flex-start;
  361. flex: 1;
  362. }
  363. .uni-forms-item-label {
  364. width: 65px;
  365. flex: 1 1 65px;
  366. text-align: left;
  367. position: relative;
  368. display: flex;
  369. align-items: center;
  370. }
  371. .uni-required::before {
  372. content: '*';
  373. position: absolute;
  374. left: -8px;
  375. font-size: 14px;
  376. color: $uni-color-error;
  377. height: 9px;
  378. line-height: 1;
  379. }
  380. .uni-forms-item__input-wrap {
  381. position: relative;
  382. overflow: hidden;
  383. font-size: 14px;
  384. height: 24px;
  385. flex: 1;
  386. width: auto;
  387. }
  388. .uni-clear-icon {
  389. display: flex;
  390. align-items: center;
  391. }
  392. .uni-error-message {
  393. position: absolute;
  394. bottom: -17px;
  395. left: 0;
  396. line-height: 12px;
  397. // padding-top: 2px;
  398. // padding-bottom: 2px;
  399. color: $uni-color-error;
  400. font-size: 12px;
  401. text-align: left;
  402. }
  403. .uni-input-error-border {
  404. border-color: $uni-color-error;
  405. }
  406. .placeholder-style {
  407. color: rgb(150, 151, 153);
  408. }
  409. .uni-input-class {
  410. font-size: 14px;
  411. }
  412. .uni-button-wrap {
  413. margin-left: 4px;
  414. }
  415. /* start--Retina 屏幕下的 1px 边框--start */
  416. .uni-border,
  417. .uni-border-bottom,
  418. .uni-border-left,
  419. .uni-border-right,
  420. .uni-border-top,
  421. .uni-border-top-bottom {
  422. position: relative
  423. }
  424. .uni-border-bottom:after,
  425. .uni-border-left:after,
  426. .uni-border-right:after,
  427. .uni-border-top-bottom:after,
  428. .uni-border-top:after,
  429. .uni-border:after {
  430. /* #ifndef APP-NVUE */
  431. content: ' ';
  432. /* #endif */
  433. position: absolute;
  434. left: 0;
  435. top: 0;
  436. pointer-events: none;
  437. box-sizing: border-box;
  438. -webkit-transform-origin: 0 0;
  439. transform-origin: 0 0;
  440. // 多加0.1%,能解决有时候边框缺失的问题
  441. width: 199.8%;
  442. height: 199.7%;
  443. transform: scale(0.5, 0.5);
  444. border: 0 solid $uni-border-color;
  445. z-index: 2;
  446. }
  447. .uni-input-border {
  448. min-height: 34px;
  449. padding-left: 4px;
  450. border: 1px solid $uni-border-color;
  451. border-radius: 6px;
  452. box-sizing: border-box;
  453. }
  454. .uni-border-top:after {
  455. border-top-width: 1px
  456. }
  457. .uni-border-left:after {
  458. border-left-width: 1px
  459. }
  460. .uni-border-right:after {
  461. border-right-width: 1px
  462. }
  463. .uni-border-bottom:after {
  464. border-bottom-width: 1px
  465. }
  466. .uni-border-top-bottom:after {
  467. border-width: 1px 0
  468. }
  469. .uni-border:after {
  470. border-width: 1px
  471. }
  472. /* end--Retina 屏幕下的 1px 边框--end */
  473. .uni-icon-wrap {
  474. padding-left: 3px;
  475. padding-right: 3px;
  476. display: flex;
  477. align-items: center;
  478. justify-content: center;
  479. }
  480. .uni-button-wrap {
  481. display: flex;
  482. align-items: right;
  483. justify-content: center;
  484. }
  485. .uni-clear-icon {
  486. display: flex;
  487. align-items: center;
  488. margin-left: 4px;
  489. }
  490. .uni-flex {
  491. /* #ifndef APP-NVUE */
  492. display: flex;
  493. /* #endif */
  494. flex-direction: row;
  495. align-items: center;
  496. }
  497. .uni-flex-1 {
  498. flex: 1;
  499. }
  500. .uni-error-in-label {
  501. display: flex;
  502. flex-direction: row;
  503. }
  504. .uni-forms-item-custom {
  505. padding: 0;
  506. border: none;
  507. }
  508. </style>