You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

form-builder-vue3.vue 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. <template>
  2. <!-- 表单 -->
  3. <form style="padding:10px 0" @submit="formSubmit">
  4. <view class="header-info" v-if="config.isHeader">
  5. <view class="title">
  6. 上传后请核对识别信息
  7. </view>
  8. <view class="info">
  9. 如有错误请及时手动修改
  10. </view>
  11. </view>
  12. <view v-for="(item,index) in formData" :key="index">
  13. <view v-show="!item.show">
  14. <!-- 头部标题 -->
  15. <view class="upload-car-bottom" v-if="item.type == 100">
  16. {{item.title}}
  17. </view>
  18. <!-- 是否显示-->
  19. <view style="min-height: 90rpx;" :style="item.vertical == 2 ? 'margin-top: 20rpx' : ''"
  20. :class="item.vertical == 2 ? 'as-layout-vertical' : 'as-layout-horizontal'" v-else>
  21. <!-- 标题 -->
  22. <view v-if="item.titleShow||item.titleShow===undefined&&item.type!=14" class="as-gravity-center-start"
  23. :style="'min-width:'+ config.titleWidth + 'rpx'"
  24. style="margin:0 20rpx;flex-direction: row;display: flex;flex-direction: row;">
  25. <image class="img-size" :src="item.required ? '/static/image/must.png' : ''"></image>
  26. <view :class="item.vertical == 2 ? 'text-left' : 'text-justify'" class="text-title" style="width:100%;">
  27. {{item.title}}
  28. </view>
  29. </view>
  30. <!-- 内容 -->
  31. <view class="as-weight as-gravity-center-start">
  32. <!-- 文本内容 -->
  33. <view v-if="item.type == 1" class="text" :style="item.style">{{item[item.value]}}</view>
  34. <!-- 输入框 -->
  35. <view v-if="item.type == 2" style="width: 100%;">
  36. <input :disabled="item.disabled" v-model="item[item.value]" :type="item.inputType"
  37. :placeholder="item.hint ? item.hint : '请输入' + item.title" placeholder-class="text-hint"
  38. class="text as-gravity-center-start" :maxlength="item.maxlength" :style="item.style"
  39. style="min-height: 90rpx;word-break:break-all" />
  40. </view>
  41. <!-- 多项选择器 -->
  42. <view v-if="item.type == 3">
  43. <checkbox-group @change="checkboxChange($event,item)" :disabled="item.disabled">
  44. <view class="as-layout-horizontal" style="flex-wrap: wrap;margin: 10px 0;">
  45. <label style="display: flex;flex-direction: row;margin-right: 30rpx;"
  46. v-for="(itemData,index) in item.itemData " :key="index">
  47. <checkbox style="transform:scale(0.7)" :value="itemData.value" :checked="itemData.checked" />
  48. <view>{{itemData.value}}</view>
  49. </label>
  50. </view>
  51. </checkbox-group>
  52. </view>
  53. <!-- 普通选择器 当 range 是一个 Array<Object> 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
  54. :range-key="item[item.value]"-->
  55. <view v-if="item.type == 4" style="width: 100%;">
  56. <!-- range[范围] value[初始选择]-->
  57. <picker color="uni-picker-container" :disabled="item.disabled" :range-key="item.itemKey"
  58. :mode="item.mode ? item.mode : 'selector'" @change="bindPickerChange($event,item)"
  59. :range="item.itemData">
  60. <view class="text">
  61. {{item[item.value] ? (item.itemKey ? item[item.value][item.itemKey] : item[item.value]) :
  62. item.hint ? item.hint : '请选择' + item.title}}
  63. </view>
  64. </picker>
  65. </view>
  66. <!-- 富文本框 -->
  67. <view v-if="item.type == 5" style="width: 100%;">
  68. <view style="margin: 20rpx 30rpx 0 30rpx;border: 1px solid #999999;height: 200rpx;border-radius: 10px;">
  69. <textarea :disabled="item.disabled" class="text" v-model="item[item.value]"
  70. @input="textareaInput($event,item)" placeholder-class="text-hint" style="width: 100%;margin: 20rpx"
  71. :maxlength="item.maxlength" auto-height :placeholder="item.hint ? item.hint : '请输入' + item.title">
  72. </textarea>
  73. <view v-if="item.maxlength" class="as-gravity-center-end"
  74. style="display: flex;flex-direction: row;height: 20%;">
  75. <view class="text">{{!item.num ? '0' : item.num}}</view>
  76. <view class="text">/{{item.maxlength ? item.maxlength : 100}}</view>
  77. </view>
  78. </view>
  79. </view>
  80. <!-- 图片选择器 :imgWidth="imgWidth ? imgWidth : 0"-->
  81. <view v-if="item.type == 6" style="width: 100%;">
  82. <form-image @backImg="backImg($event,item)" style="width: 100%;" :retract="40"></form-image>
  83. </view>
  84. <!-- 单向选择器 -->
  85. <view v-if="item.type == 7" style="width: 100%;">
  86. <radio-group @change="radioChange($event,item)">
  87. <view class="as-layout-horizontal">
  88. <label style="display: flex;flex-direction: row;margin-right: 30rpx;"
  89. v-for="(itemData, index) in item.itemData" :key="index">
  90. <radio color="#1AAC1B" style="transform:scale(0.7)" :value="itemData.value"
  91. :checked="itemData.checked" />
  92. <view>{{itemData.name}}</view>
  93. </label>
  94. </view>
  95. </radio-group>
  96. </view>
  97. <!-- 证件照上传 -->
  98. <view v-if="item.type == 8" style="width: 100%;">
  99. <view style="margin: 0 40rpx;">
  100. <view class="text-error">
  101. {{item.hint && item.hint.split(',')[0] ? item.hint.split(',')[0] : ''}}
  102. </view>
  103. <view class="as-layout-horizontal as-gravity-center" style="height: 260rpx;margin-top: 20rpx;"
  104. :class="{dan:item.inputType === '999'}">
  105. <view class="as-layout-vertical as-gravity-center" style="width: 90%;height: 100%;">
  106. <image
  107. :src="item.placeholderImg ? item.placeholderImg.split(',')[0] : '../../static/image/license2.png'"
  108. style="width: 100%;height: 80%;" :style="item.style" @tap="ImageSelection(item,1)"></image>
  109. <view class="as-gravity-center text-hint" style="margin-top: 5rpx;">
  110. {{item.hint && item.hint.split(',')[1] ? item.hint.split(',')[1] : ''}}
  111. </view>
  112. </view>
  113. <view style="width: 40rpx;"></view>
  114. <view class="as-layout-vertical" style="width: 90%;height: 100%;" v-if="item.inputType != '999'">
  115. <image
  116. :src="item.placeholderImg && item.placeholderImg.split(',')[1] ? item.placeholderImg.split(',')[1] : '../../static/image/license2.png'"
  117. style="width: 100%;height: 80%;" @tap="ImageSelection(item,2)"></image>
  118. <view class="as-gravity-center text-hint" style="margin-top: 5rpx;">
  119. {{item.hint && item.hint.split(',')[2] ? item.hint.split(',')[2] : ''}}
  120. </view>
  121. </view>
  122. </view>
  123. </view>
  124. </view>
  125. <!-- 车牌号输入 -->
  126. <view v-if="item.type == 9" style="width: 100%">
  127. <view style="margin: 20rpx 30rpx 0 30rpx;">
  128. <car-num-ber-input @numberInputResult="numberInputResult($event,item)" :defaultStr='item[item.value]'>
  129. </car-num-ber-input>
  130. </view>
  131. </view>
  132. <!-- 省市区选择器 -->
  133. <view v-if="item.type == 10" style="width: 100%">
  134. <pick-regions :defaultRegion="defaultRegionCode" @getRegion="handleGetRegion($event,item)">
  135. <view class="text">
  136. {{handleTxt(item)}}
  137. </view>
  138. </pick-regions>
  139. </view>
  140. <!-- 短信验证码 -->
  141. <view v-if="item.type == 11" style="width: 100%;">
  142. <view class="as-layout-horizontal as-gravity-center">
  143. <input v-model="item[item.value]" type="number"
  144. :placeholder="item.hint ? item.hint : '请输入' + item.title" placeholder-class="text-hint"
  145. class="text as-gravity-center-start" :maxlength="item.maxlength" :style="item.style"
  146. style="min-height: 90rpx;word-break:break-all" />
  147. <button style="width: 160rpx;font-size: 24rpx;height: 60rpx" :disabled="item.disabled"
  148. @click="SMSsending($event,item)">{{item.hint ? item.hint : '获取验证码'}}</button>
  149. </view>
  150. </view>
  151. <!-- 隐私协议 -->
  152. <view v-if="item.type == 12" style="width: 100%;">
  153. <view class="as-layout-horizontal as-gravity-center">
  154. <checkbox value="cb" />请先阅读并同意
  155. <a style="color:#007AFF;text-decoration: underline;">《开户服务协议》</a>
  156. </view>
  157. </view>
  158. <!-- 车牌颜色 -->
  159. <view v-if="item.type == 13" style="width: 100%;">
  160. <licensePlateColor @numberplateResult="numberplateResult($event,item)" :defaultStr='item[item.value]'
  161. :vanType='vanType'>
  162. </licensePlateColor>
  163. </view>
  164. <!-- 上传图片,证件照等 -->
  165. <view v-if="item.type == 14" style="width: 100%;">
  166. <upload-car-img :dataList='item' @uploadImgHandle='uploadImgHandle($event,item)' />
  167. </view>
  168. <!-- <view v-if="item.type == 15" style="width: 100%;">
  169. <view class="input-box">
  170. <text class="sub-label">长</text>
  171. <input v-model="state.outlineL" @input="outlineInput" :disabled="state.isEnableChangeOCRCarInfo" />
  172. <text class="sub-text">X</text>
  173. <text class="sub-label">宽</text>
  174. <input v-model="state.outlineW" @input="outlineInput" :disabled="state.isEnableChangeOCRCarInfo" />
  175. <text class="sub-text">X</text>
  176. <text class="sub-label">高</text>
  177. <input v-model="state.outlineH" @input="outlineInput" :disabled="state.isEnableChangeOCRCarInfo" />
  178. <text class="sub-text">mm</text>
  179. </view>
  180. </view> -->
  181. <!-- 车辆用户类型选择 -->
  182. <view v-if="item.type == 101" style="width: 100%;">
  183. <CarUserType :dataList='item' @useCarUserType='useCarUserType($event,item)' />
  184. </view>
  185. <!-- 汽车尺寸处理 -->
  186. <view v-if="item.type == 102" style="width: 100%;">
  187. </view>
  188. <!-- 用户类型选择 -->
  189. <view v-if="item.type == 103" style="width: 100%;">
  190. <UserType :dataList='item' @useUserType='useUserType($event,item)' />
  191. </view>
  192. </view>
  193. </view>
  194. <!-- 最后一位不展示 且 设置为展示 -->
  195. <view v-show="item.underline" class="as-line-h" style="margin: 0rpx 20rpx 0rpx 20rpx;"></view>
  196. </view>
  197. </view>
  198. <view style="display: flex;flex-direction: row;">
  199. <button v-if="config.btnBack" class="back-bg as-gravity-center btn as-weight"
  200. @click="back">{{config.submitOneName ? config.submitOneName : '上一步'}}
  201. </button>
  202. <button class="btn btn-text as-gravity-center nav-bg as-weight"
  203. form-type="submit">{{config.submitName ? config.submitName : '提交'}}
  204. </button>
  205. </view>
  206. </form>
  207. </template>
  208. <script setup lang='ts'>
  209. import {
  210. TypeData
  211. } from './tools'
  212. import licensePlateColor from '@/components/LicensePlateColor.vue';
  213. import carNumBerInput from '@/components/car-number-input/car-number-input'
  214. import uploadCarImg from '@/components/upload-car-img/upload-car-img'
  215. import CarUserType from '@/components/CarUserType.vue'
  216. import UserType from '@/components/UserType.vue'
  217. import {
  218. pathToBase64
  219. } from '../../static/js/util/imageTool.js'
  220. import pickRegions from '@/components/pick-regions/pick-regions.vue'
  221. import {
  222. computed
  223. } from 'vue'
  224. const defaultRegionCode = '520115'
  225. const props = defineProps({
  226. vanType: {
  227. default: 'all'
  228. },
  229. formData: {
  230. type: Array as() => Array < TypeData > ,
  231. // type: Array as() => Array < any > ,
  232. default: () => []
  233. },
  234. config: {
  235. type: Object,
  236. default: function() {
  237. return {
  238. submitName: '提交', //提交按钮名称
  239. titleWidth: 240, //标题宽度
  240. isHeader: false, //头部信息展示
  241. }
  242. }
  243. }
  244. })
  245. const emit = defineEmits < {
  246. (e: 'submit', content: any): void,
  247. (e: 'bindPickerChange', event: any, item: TypeData): void,
  248. (e: 'handleGetRegion', event: any, item: TypeData): void,
  249. (e: 'uploadImg', content: any, item: TypeData, index: Number): void,
  250. (e: 'radioChange', event: any, item: TypeData): void,
  251. (e: 'sendText', event: any, item: TypeData): void,
  252. (e: 'uploadImgOcr', fileLise: any, item: any): void,
  253. } > ()
  254. //defineExpose 可宏来显式指定在 <script setup> 组件中要暴露出去的属性。
  255. //普通选择器
  256. function bindPickerChange(e: any, item: TypeData) {
  257. let select = e.target.value
  258. // #ifdef H5
  259. select = e.detail.value
  260. // #endif
  261. if (item.mode == 'date' || item.mode == 'time') {
  262. /* 日期选择器*/
  263. item[item.value] = select
  264. } else {
  265. /* 普通选择器*/
  266. item[item.value] = select || item.itemData[select]
  267. }
  268. emit('bindPickerChange', e, item)
  269. }
  270. // 车辆选择器处理
  271. function useCarUserType(e, item) {
  272. item[item.value] = e
  273. }
  274. function useUserType(e, item) {
  275. item[item.value] = e
  276. console.log(e);
  277. }
  278. function handleTxt(item) {
  279. if (!item.value) {
  280. return '请选择省市区'
  281. }
  282. const str = item[item.value] || '请选择省市区'
  283. return str
  284. }
  285. // 获取选择的地区
  286. function handleGetRegion(e: any, item: TypeData) {
  287. item[item.value] = e[0].name + e[1].name + e[2].name
  288. emit('handleGetRegion', e, item);
  289. }
  290. //多项选择器
  291. function checkboxChange(e: any, item: TypeData) {
  292. item[item.value] = e.detail.value /* 赋值*/
  293. }
  294. //单项选择器
  295. function radioChange(e: any, item: TypeData) {
  296. item[item.value] = e.detail.value /* 赋值*/
  297. emit('radioChange', e, item)
  298. }
  299. //富文本框输入内容
  300. function textareaInput(e: Event, item: TypeData) {
  301. item.num = item[item.value].length
  302. }
  303. //点击图片按钮
  304. function backImg(e: Event, item: TypeData) {
  305. item[item.value] = e
  306. }
  307. function back(e: Event) {
  308. }
  309. //车牌输入
  310. function numberInputResult(e: any, item: TypeData) {
  311. var str = e.replace(/\s*/g, "");
  312. item[item.value] = str
  313. }
  314. function numberplateResult(e: any, item) {
  315. item[item.value] = e.id
  316. console.log(e);
  317. }
  318. //短信验证码发送
  319. function SMSsending(e: any, item: TypeData) {
  320. let time = 60;
  321. emit('sendText', e, item)
  322. const fn = setInterval(function() {
  323. time--
  324. item.hint = time + 's'
  325. item.disabled = true
  326. if (time == 0) {
  327. clearInterval(fn)
  328. item.disabled = false
  329. item.hint = '获取验证码'
  330. }
  331. }, 1000)
  332. }
  333. //选择图片按钮
  334. function ImageSelection(item: TypeData, index: number) {
  335. uni.chooseImage({
  336. count: 1, //最多可以选择的文件个数
  337. sourceType: ['camera'], //album 从相册选视频,camera 使用相机拍摄,默认为:['album', 'camera']
  338. success(res: any) {
  339. if (res.tempFiles[0].size > 2000000) {
  340. uni.showToast({
  341. title: '图片大于2M,请重新上传',
  342. icon: 'none',
  343. duration: 1500
  344. })
  345. return;
  346. }
  347. // #ifdef H5
  348. pathToBase64(res.tempFilePaths[0]).then(data => {
  349. emit('uploadImg', data, item, index)
  350. })
  351. // #endif
  352. },
  353. fail(res: any) {
  354. if (!res.authSetting['scope.album']) {
  355. uni.showModal({
  356. title: '授权失败',
  357. content: '需要从您的相机或相册获取图片,请在设置界面打开相关权限',
  358. success: (res) => {
  359. if (res.confirm) {
  360. uni.openSetting()
  361. }
  362. }
  363. })
  364. }
  365. }
  366. })
  367. }
  368. function showToast(hint: string) {
  369. uni.showToast({
  370. icon: 'none',
  371. title: hint
  372. })
  373. }
  374. // type为14,上传图片
  375. function uploadImgHandle(fileList, item) {
  376. emit('uploadImgOcr', fileList, item)
  377. }
  378. //内容提交
  379. function formSubmit() {
  380. let content = {}
  381. // console.log('data值', props.formData)
  382. /* 整理数据对象返回内容 */
  383. for (var i = 0; i < props.formData.length; i++) {
  384. let data = props.formData[i]
  385. if (data.show) continue;
  386. // console.log('data', data)
  387. /* 时间另外判断 */
  388. if (data.type == 7 && data[data.value] === undefined) {
  389. data[data.value] = data.itemData.find(item => item.checked).value
  390. }
  391. // 如果等于100说明是标题,跳过
  392. if (data.type == 100) {
  393. continue
  394. }
  395. if (data.required && !data.show) {
  396. let reg = new RegExp(':', 'g') //g代表全部
  397. let newMsg = data.title.replace(reg, '');
  398. if (data.value.indexOf(',') != -1 && data.type == 8) {
  399. if (!data[data.value.split(',')[0]]) {
  400. showToast(data.hint + "不能为空")
  401. return
  402. } else if (!data[data.value.split(',')[1]]) {
  403. showToast(data.hint + "不能为空")
  404. return
  405. }
  406. } else if (data[data.value] === undefined || data[data.value] === '') {
  407. showToast(newMsg + "不能为空")
  408. return
  409. }
  410. }
  411. if (data.type == 2 && !data.show) {
  412. if (data.checkReg) {
  413. let regs = new RegExp(data.checkReg) //g代表全部
  414. if (!regs.test(data[data.value])) {
  415. showToast(data.checkPrompt || data.title + '校验失败')
  416. return
  417. }
  418. }
  419. if (data.minlength && data[data.value].length && +data.minlength > data[data.value].length) {
  420. showToast(data.title + '最小长度为' + data.minlength)
  421. return
  422. }
  423. }
  424. if (data.value.indexOf(',') != -1 && data.type == 8) {
  425. if (data[data.value.split(',')[0]]) {
  426. content[data.value.split(',')[0]] = data[data.value.split(',')[0]]
  427. }
  428. if (data[data.value.split(',')[1]]) {
  429. content[data.value.split(',')[1]] = data[data.value.split(',')[1]]
  430. }
  431. } else if (data.type == 10) {
  432. for (var s = 0; s < data.value.split(',').length; s++) {
  433. if (data[data.value]) {
  434. content[data.value.split(',')[s]] = data[data.value].split('-')[s]
  435. }
  436. }
  437. } else {
  438. if (data.type == 4 && data.name && data[data.value]) {
  439. content[data.value] = data[data.value][data.name]
  440. } else {
  441. content[data.value] = data[data.value]
  442. }
  443. }
  444. }
  445. emit('submit', content)
  446. }
  447. </script>
  448. <style lang='scss' scoped>
  449. .img-size {
  450. width: 30rpx;
  451. height: 30rpx;
  452. }
  453. .upload-car-bottom {
  454. font-size: 32rpx;
  455. text-align: left;
  456. margin: 15rpx 30rpx;
  457. }
  458. .header-info {
  459. padding: 40rpx 40rpx 10rpx;
  460. .title {
  461. font-size: 28rpx;
  462. color: #000;
  463. font-weight: bold;
  464. }
  465. .info {
  466. margin-top: 8rpx;
  467. font-size: 24rpx;
  468. color: #999;
  469. }
  470. }
  471. .btn {
  472. height: 80rpx;
  473. opacity: 1;
  474. border-radius: 100rpx;
  475. margin: 20rpx 20rpx 20rpx 20rpx;
  476. }
  477. .btn-text {
  478. color: #FFFFFF;
  479. font-size: 28rpx;
  480. }
  481. .nav-bg {
  482. /* background: linear-gradient(to right, #13E7C1, #43A1E0); */
  483. border: 1px solid #FFFFFF;
  484. background: #1AAC1B;
  485. }
  486. .back-bg {
  487. border: 1px solid #1AAC1B;
  488. color: #1AAC1B;
  489. background: #FFFFFF;
  490. }
  491. ::v-deep .uni-picker-container .uni-picker-action.uni-picker-action-confirm {
  492. background-color: blue;
  493. color: #1AAC1B;
  494. /* 添加其他样式属性 */
  495. }
  496. .dan {
  497. width: 50%;
  498. }
  499. </style>