Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

form-builder-vue3.vue 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. <template>
  2. <!-- 表单 -->
  3. <form @submit="formSubmit">
  4. <view v-for="(item, index) in formData" :key="index">
  5. <view v-show="!item.show">
  6. <!-- 是否显示-->
  7. <view style="min-height: 90rpx" :class="
  8. item.vertical === 2 ? 'as-layout-vertical' : 'as-layout-horizontal'
  9. ">
  10. <!-- 标题 -->
  11. <view class="as-gravity-center-start" :style="'min-width:' + config.titleWidth + 'rpx'" style="
  12. margin: 0 20rpx;
  13. flex-direction: row;
  14. display: flex;
  15. flex-direction: row;
  16. ">
  17. <image class="img-size" :src="item.required ? `${$imgUrl}must.png` : ''" mode="aspectFill">
  18. </image>
  19. <view :class="item.vertical === 2 ? 'text-left' : 'text-justify'" class="text-title"
  20. style="width: 100%">
  21. {{ item.title }}
  22. </view>
  23. </view>
  24. <!-- 内容 -->
  25. <view class="as-weight as-gravity-center-start">
  26. <!-- 文本内容 -->
  27. <view v-if="item.type === 1" class="text" :style="item.style">{{
  28. item[item.value]
  29. }}</view>
  30. <!-- 输入框 -->
  31. <view v-if="item.type === 2" style="width: 100%">
  32. <input :disabled="item.disabled" v-model="item[item.value]" :type="item.inputType"
  33. :placeholder="item.hint ? item.hint : '请输入' + item.title" placeholder-class="text-hint"
  34. class="text as-gravity-center-start" :maxlength="item.maxlength" :style="item.style"
  35. style="min-height: 90rpx; word-break: break-all" />
  36. </view>
  37. <!-- 多项选择器 -->
  38. <view v-if="item.type === 3">
  39. <checkbox-group @change="checkboxChange($event, item)" :disabled="item.disabled">
  40. <view class="as-layout-horizontal">
  41. <label style="
  42. display: flex;
  43. flex-direction: row;
  44. margin-right: 30rpx;
  45. " v-for="(itemData, index) in item.itemData" :key="index">
  46. <checkbox style="transform: scale(0.7)" :value="itemData.value"
  47. :checked="itemData.checked" />
  48. <view>{{ itemData.name }}</view>
  49. </label>
  50. </view>
  51. </checkbox-group>
  52. </view>
  53. <!-- 普通选择器 当 range 是一个 Array<Object> 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容 :range-key="item[item.value]"-->
  54. <view v-if="item.type === 4" style="width: 100%">
  55. <!-- range[范围] value[初始选择]-->
  56. <picker :disabled="item.disabled" :range-key="item.itemKey"
  57. :mode="item.mode ? item.mode : 'selector'" @change="bindPickerChange($event, item)"
  58. :range="item.itemData">
  59. <view class="text">
  60. {{
  61. item[item.value]
  62. ? item.itemKey
  63. ? item[item.value][item.itemKey]
  64. : item[item.value]
  65. : item.hint
  66. ? item.hint
  67. : "请选择" + item.title
  68. }}
  69. </view>
  70. </picker>
  71. </view>
  72. <!-- 富文本框 -->
  73. <view v-if="item.type === 5" style="width: 100%">
  74. <view>
  75. <textarea :disabled="item.disabled" class="text" v-model="item[item.value]"
  76. @input="textareaInput($event, item)" placeholder-class="text-hint"
  77. style="width: 100%" :maxlength="item.maxlength" auto-height
  78. :placeholder="item.hint ? item.hint : '请输入' + item.title">
  79. </textarea>
  80. <view v-if="item.maxlength" class="as-gravity-center-end"
  81. style="display: flex; flex-direction: row; height: 20%">
  82. <view class="text">{{ !item.num ? "0" : item.num }}</view>
  83. <view class="text">/{{ item.maxlength ? item.maxlength : 100 }}</view>
  84. </view>
  85. </view>
  86. </view>
  87. <!-- 图片选择器 :imgWidth="imgWidth ? imgWidth : 0"-->
  88. <view v-if="item.type === 6" style="width: 100%">
  89. <form-image @backImg="backImg($event, item)" style="width: 100%" :retract="40"></form-image>
  90. </view>
  91. <!-- 单向选择器 -->
  92. <view v-if="item.type === 7" style="width: 100%">
  93. <radio-group @change="radioChange($event, item)">
  94. <view class="as-layout-horizontal">
  95. <label style="
  96. display: flex;
  97. flex-direction: row;
  98. margin-right: 30rpx;
  99. " v-for="(itemData, index) in item.itemData" :key="index">
  100. <radio style="transform: scale(0.7)" :value="itemData.value"
  101. :checked="itemData.checked" />
  102. <view>{{ itemData.name }}</view>
  103. </label>
  104. </view>
  105. </radio-group>
  106. </view>
  107. <!-- 证件照上传 -->
  108. <view v-if="item.type === 8" style="width: 100%">
  109. <view style="margin: 0 40rpx">
  110. <view class="text-error">{{ item.hint }}</view>
  111. <view class="as-layout-horizontal as-gravity-center"
  112. style="height: 250rpx; margin-top: 20rpx">
  113. <view class="as-layout-vertical" style="width: 90%; height: 100%">
  114. <image mode="aspectFill" :src="
  115. item.placeholderImg1
  116. ? item.placeholderImg1
  117. : `${$imgUrl}license2.png`
  118. " style="width: 100%; height: 80%" @tap="ImageSelection(item, 1)"></image>
  119. <view class="as-gravity-center text-hint" style="margin-top: 5rpx">
  120. {{ item.hint1 }}
  121. </view>
  122. </view>
  123. <view style="width: 40rpx"></view>
  124. <view class="as-layout-vertical" style="width: 90%; height: 100%">
  125. <image :src="
  126. item.placeholderImg2
  127. ? item.placeholderImg2
  128. : `${$imgUrl}license2.png`
  129. " style="width: 100%; height: 80%" @tap="ImageSelection(item, 2)" mode="aspectFill"></image>
  130. <view class="as-gravity-center text-hint" style="margin-top: 5rpx">
  131. {{ item.hint2 }}
  132. </view>
  133. </view>
  134. </view>
  135. </view>
  136. </view>
  137. <!-- 车牌号输入 -->
  138. <view v-if="item.type === 9" style="width: 100%">
  139. <view style="margin: 20rpx 30rpx 0 30rpx">
  140. <car-num-ber-input @numberInputResult="numberInputResult($event, item)">
  141. </car-num-ber-input>
  142. </view>
  143. </view>
  144. <!-- 车牌号输入 -->
  145. <view v-if="item.type === 10" style="width: 100%">
  146. <pick-regions :defaultRegion="defaultRegionCode" @getRegion="handleGetRegion($event, item)">
  147. <view class="text">
  148. {{
  149. item[item.value]
  150. ? item.itemKey
  151. ? item[item.value][item.itemKey]
  152. : item[item.value]
  153. : item.hint
  154. ? item.hint
  155. : "请选择" + item.title
  156. }}
  157. </view>
  158. </pick-regions>
  159. </view>
  160. </view>
  161. </view>
  162. <view v-show="index !== formData.length - 1 && !item.underline" class="as-line3"
  163. style="margin: 0rpx 20rpx 0rpx 20rpx"></view>
  164. </view>
  165. </view>
  166. <button class="btn btn-text as-gravity-center" form-type="submit">
  167. {{ config ? config.submitName : "提交" }}
  168. </button>
  169. </form>
  170. </template>
  171. <script setup lang="ts">
  172. import { TypeData } from "./tools";
  173. import { request } from "@/utils/network/request.js";
  174. import carNumBerInput from "@/components/car-number-input/car-number-input";
  175. import { pathToBase64 } from "@/utils/util/imageTool.js";
  176. import pickRegions from "@/login/pick-regions/pick-regions.vue";
  177. import { fileURL } from "@/datas/fileURL.js";
  178. const defaultRegionCode = "520115";
  179. const props = defineProps({
  180. formData: {
  181. type: Array as () => Array<TypeData>,
  182. default: () => [],
  183. },
  184. config: {
  185. type: Object,
  186. default: function () {
  187. return {
  188. submitName: "提交", //提交按钮名称
  189. titleWidth: 160, //标题宽度
  190. };
  191. },
  192. },
  193. });
  194. //defineEmits
  195. const emit = defineEmits<{
  196. (e : "submit", content : any) : void;
  197. (e : "uploadImg", content : any, item : TypeData, index : Number) : void;
  198. (e : "radioChange", event : any, item : TypeData) : void;
  199. }>();
  200. //defineExpose 可宏来显式指定在 <script setup> 组件中要暴露出去的属性。
  201. //普通选择器
  202. function bindPickerChange(e : any, item : TypeData) {
  203. let select = e.target.value;
  204. // #ifdef H5
  205. select = e.detail.value;
  206. // #endif
  207. if (item.mode === "date" || item.mode === "time") {
  208. /* 日期选择器*/
  209. item[item.value] = select;
  210. } else {
  211. /* 普通选择器*/
  212. item[item.value] = item.itemData[select];
  213. }
  214. }
  215. // 获取选择的地区
  216. function handleGetRegion(e : any, item : TypeData) {
  217. console.log("输出内容", e);
  218. item[item.value] = e[0].name + "-" + e[1].name + "-" + e[2].name;
  219. }
  220. //多项选择器
  221. function checkboxChange(e : any, item : TypeData) {
  222. item[item.value] = e.detail.value; /* 赋值*/
  223. }
  224. //单项选择器
  225. function radioChange(e : any, item : TypeData) {
  226. item[item.value] = e.detail.value; /* 赋值*/
  227. emit("radioChange", e, item);
  228. }
  229. //富文本框输入内容
  230. function textareaInput(e : Event, item : TypeData) {
  231. item.num = item[item.value].length;
  232. }
  233. //点击图片按钮
  234. function backImg(e : Event, item : TypeData) {
  235. item[item.value] = e;
  236. }
  237. //车牌输入
  238. function numberInputResult(e : Event, item : TypeData) {
  239. console.log("输出内容", e);
  240. item[item.value] = e;
  241. }
  242. //选择图片按钮
  243. function ImageSelection(item : TypeData, index : number) {
  244. uni.chooseImage({
  245. count: 1, //最多可以选择的文件个数
  246. sourceType: ["camera"], //album 从相册选视频,camera 使用相机拍摄,默认为:['album', 'camera']
  247. success(res : any) {
  248. if (res.tempFiles[0].size > 2000000) {
  249. uni.showToast({
  250. title: "图片大于2M,请重新上传",
  251. icon: "none",
  252. duration: 1500,
  253. });
  254. return;
  255. }
  256. // #ifdef MP-WEIXIN
  257. uni.getFileSystemManager().readFile({
  258. filePath: res.tempFiles[0].tempFilePath, //要读取的文件的路径
  259. encoding: "base64", //编码格式
  260. success: (res) => {
  261. let imgBase64 = res.data;
  262. let reqData = {
  263. imageBase: imgBase64,
  264. accountNum: "qtzl_xcx",
  265. secretKey: "4DE47302-EDC7-41D4-888F-59A79DF4EA46",
  266. };
  267. //上传给服务器
  268. uni.showLoading({
  269. title: "正在加载中...",
  270. mask: true,
  271. });
  272. //文件上传
  273. request("", {
  274. baseUrl: "https://etcfile.etcjz.cn/v1/file/uploadImageBaseFile",
  275. data: reqData,
  276. }).then((content) => {
  277. uni.hideLoading();
  278. if (content.rc != "00") {
  279. uni.showModal({
  280. title: "请求错误",
  281. content: "图片上传失败",
  282. showCancel: false,
  283. confirmText: "取消",
  284. });
  285. }
  286. //处理后的 Data
  287. let data = {
  288. source: 2, //来源
  289. imageBase64: imgBase64,
  290. imageType: index,
  291. imageUrl: content.data.fileUrl,
  292. };
  293. emit("uploadImg", data, item, index);
  294. });
  295. },
  296. fail: (err) => {
  297. console.log("错误提示", err);
  298. },
  299. });
  300. // #endif
  301. // #ifdef H5
  302. pathToBase64(res.tempFilePaths[0]).then((data) => {
  303. let base64 = data.replaceAll("data:image/jpeg;base64,", "");
  304. base64 = base64.replaceAll("data:image/png;base64,", "");
  305. base64 = base64.replaceAll("data:image/jpg;base64,", "");
  306. let reqData = {
  307. imageBase: base64,
  308. accountNum: "qtzl_xcx",
  309. secretKey: "4DE47302-EDC7-41D4-888F-59A79DF4EA46",
  310. };
  311. //上传给服务器
  312. uni.showLoading({
  313. title: "文件上传中...",
  314. mask: true,
  315. });
  316. //文件上传
  317. request("", {
  318. baseUrl: "https://etcfile.etcjz.cn/v1/file/uploadImageBaseFile",
  319. data: reqData,
  320. })
  321. .then((content) => {
  322. console.log("上传结果", content);
  323. uni.hideLoading();
  324. if (content.rc != "00" && content.rc != 0) {
  325. uni.showModal({
  326. title: "请求错误",
  327. content: "图片上传失败",
  328. showCancel: false,
  329. confirmText: "取消",
  330. });
  331. }
  332. //展示缩略图
  333. if (index % 2 != 0) {
  334. //奇数第一张 偶数第二张
  335. item.placeholderImg1 = content.data.fileUrl;
  336. item[item.value.split(",")[0]] = content.data.fileUrl;
  337. } else {
  338. item.placeholderImg2 = content.data.fileUrl;
  339. item[item.value.split(",")[1]] = content.data.fileUrl;
  340. }
  341. //只展示缩略图
  342. if (!item.inputType) {
  343. emit("uploadImg", data, item, index);
  344. } else {
  345. //if(item.type === 1)
  346. //证件OCR识别
  347. uni.showLoading({
  348. title: "证件识别中...",
  349. mask: true,
  350. });
  351. let reqData2 = {
  352. imageDriveBase: "",
  353. imageBase64: base64,
  354. imageType: index % 2 != 0 ? 1 : 2, //1代表身份证正面,2代表反面
  355. imageUrl: content.data.fileUrl,
  356. };
  357. //默认身份证
  358. let code = "IF010012019070410001";
  359. if (item.inputType == "2") {
  360. //2为行驶证
  361. code = "IF010012019070410002";
  362. reqData2.imageBase64 = "";
  363. reqData2.imageDriveBase = base64;
  364. }
  365. request(code, {
  366. data: reqData2,
  367. }).then((ocr) => {
  368. uni.hideLoading();
  369. if (ocr.rc !== "00") {
  370. uni.showModal({
  371. title: "请求错误",
  372. content: "证件识别失败",
  373. showCancel: false,
  374. confirmText: "取消",
  375. });
  376. }
  377. let data = ocr.rd;
  378. //[身份证正面]姓名(name) 住址(address) 出生日期(birthday) 身份证号(idno) 民族(nation)
  379. //[身份证反面]证件机关(agency) 有效起始日期(begindate) 有效结束日期(enddate)
  380. emit("uploadImg", data, item, index);
  381. });
  382. }
  383. })
  384. .catch((e) => {
  385. console.log("输出内容", e);
  386. });
  387. });
  388. // #endif
  389. },
  390. fail(res : any) {
  391. if (!res.authSetting["scope.album"]) {
  392. uni.showModal({
  393. title: "授权失败",
  394. content: "需要从您的相机或相册获取图片,请在设置界面打开相关权限",
  395. success: (res) => {
  396. if (res.confirm) {
  397. uni.openSetting();
  398. }
  399. },
  400. });
  401. }
  402. },
  403. });
  404. }
  405. function showToast(hint : string) {
  406. uni.showToast({
  407. icon: "none",
  408. title: hint,
  409. });
  410. }
  411. //内容提交
  412. function formSubmit() {
  413. let content = {};
  414. /* 整理数据对象返回内容 */
  415. for (var i = 0; i < props.formData.length; i++) {
  416. let data = props.formData[i];
  417. /* 时间另外判断 */
  418. if (data.required) {
  419. let reg = new RegExp(":", "g"); //g代表全部
  420. let newMsg = data.title.replace(reg, "");
  421. if (data.value.indexOf(",") != -1 && data.type === 8) {
  422. if (!data[data.value.split(",")[0]]) {
  423. showToast(data.hint1 + "不能为空");
  424. return;
  425. } else if (!data[data.value.split(",")[1]]) {
  426. showToast(data.hint2 + "不能为空");
  427. return;
  428. }
  429. } else if (!data[data.value]) {
  430. showToast(newMsg + "不能为空");
  431. return;
  432. }
  433. }
  434. if (data.value.indexOf(",") != -1 && data.type === 8) {
  435. if (data[data.value.split(",")[0]]) {
  436. content[data.value.split(",")[0]] = data[data.value.split(",")[0]];
  437. }
  438. if (data[data.value.split(",")[1]]) {
  439. content[data.value.split(",")[1]] = data[data.value.split(",")[1]];
  440. }
  441. } else if (data.type === 10) {
  442. console.log("输出内容========", data);
  443. for (var i = 0; i < data.value.split(",").length; i++) {
  444. content[data.value.split(",")[i]] = data[data.value].split("-")[i];
  445. }
  446. } else {
  447. if (data.type === 4 && data.name) {
  448. content[data.value] = data[data.value][data.name];
  449. } else {
  450. content[data.value] = data[data.value];
  451. }
  452. }
  453. }
  454. emit("submit", content);
  455. }
  456. </script>
  457. <style lang="scss" scoped>
  458. .img-size {
  459. width: 30rpx;
  460. height: 30rpx;
  461. }
  462. .btn {
  463. height: 80rpx;
  464. background: #307afe;
  465. opacity: 1;
  466. border-radius: 100rpx;
  467. margin: 20rpx 20rpx 20rpx 20rpx;
  468. }
  469. .btn-text {
  470. color: #ffffff;
  471. font-size: 28rpx;
  472. }
  473. </style>