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 20KB

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