formPage.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <template>
  2. <view class="formBox">
  3. <view :class="['input_list', { 'scan_page': scanPage }]">
  4. <block v-for="(item, index) in localFormConfigList" :key="index">
  5. <!-- **********************switch开关 -->
  6. <view
  7. :class="['input_item', 'input_item_fir', !scanPage && 'border_bottom', item.className]"
  8. v-if="item.type == 'switch' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  9. >
  10. <view class="input_tit">{{ item.label }}</view>
  11. <switch
  12. class="public_switch"
  13. :checked="item.defValue == 'true' && isInclude(currentType, item.store.type)"
  14. @change="switchChange($event, item.vModel)"
  15. />
  16. </view>
  17. <!-- ******************************input框 -->
  18. <view
  19. :class="['input_item', !scanPage && 'border_bottom', item.className]"
  20. v-if="item.type == 'text' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  21. >
  22. <view class="input_tit">{{ item.label }}</view>
  23. <view class="input_con">
  24. <input
  25. class="input"
  26. @blur="setVal($event, item.vModel)"
  27. :value="item.defValue"
  28. placeholder-class="placeholder"
  29. :placeholder="item.placeholder"
  30. />
  31. <!-- 获取验证码 -->
  32. <block v-if="item.vModel == 'provingCode'">
  33. <view
  34. :class="['code_con_txt', disabledBtn ? '' : 'colorCustom']"
  35. @click="getCode"
  36. v-if="seconds <= 0"
  37. >获取验证码</view>
  38. <view class="code_con_txt colorCustom" v-else>{{ seconds }}秒后重新获取</view>
  39. </block>
  40. <!-- 获取手机号 -->
  41. <block v-if="(item.vModel == 'certNum' || item.vModel == 'guardianCertNum') && showPhotoImg">
  42. <view class="code_con_txt" @click="photoBtnClick">
  43. <image class="photo_btn" :src="iconUrl.photograph"></image>
  44. </view>
  45. </block>
  46. </view>
  47. </view>
  48. <!-- ****************************单选框 -->
  49. <view
  50. :class="['input_item', !scanPage && 'border_bottom', item.className]"
  51. v-if="item.type == 'radio' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  52. >
  53. <view class="input_tit">{{ item.label }}</view>
  54. <view class="input_con">
  55. <view class="radio_list">
  56. <view
  57. :class="['radio_item', item.defValue == optIndex ? 'backgroundCustom' : '']"
  58. v-for="(cell, optIndex) in item.options"
  59. :key="optIndex"
  60. @click="radioClick(item.vModel, optIndex)"
  61. >{{ cell.label }}</view>
  62. </view>
  63. </view>
  64. </view>
  65. <!-- ********************************日期框 -->
  66. <view
  67. :class="['input_item', !scanPage && 'border_bottom', item.className]"
  68. v-if="item.type == 'datePicker' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  69. >
  70. <view class="input_tit">{{ item.label }}</view>
  71. <picker
  72. mode="date"
  73. @change="setVal($event, item.vModel)"
  74. :end="today"
  75. :value="item.defValue"
  76. >
  77. <view class="input_con">
  78. <input class="input" disabled :value="item.defValue" />
  79. <image class="public_right_img" :src="iconUrl.icon_right"></image>
  80. </view>
  81. </picker>
  82. </view>
  83. <!-- ****************************下拉框 -->
  84. <view
  85. :class="['input_item', !scanPage && 'border_bottom', item.className]"
  86. v-if="item.type == 'select' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  87. >
  88. <view class="input_tit">{{ item.label }}</view>
  89. <picker
  90. @change="pickerChange($event, item.vModel)"
  91. :value="item.defValue"
  92. :range="item.options"
  93. range-key="label"
  94. :disabled="item.isDisabled"
  95. >
  96. <view class="input_con">
  97. <input
  98. :class="['input', item.isDisabled && 'inputDisabled']"
  99. disabled
  100. :value="item.options[item.defValue] ? item.options[item.defValue].label : ''"
  101. />
  102. <image class="public_right_img" :src="iconUrl.icon_right"></image>
  103. </view>
  104. </picker>
  105. </view>
  106. <!-- ****************************省市区 -->
  107. <view
  108. :class="['input_item', !scanPage && 'border_bottom', item.className]"
  109. v-if="item.type == 'region' && item.isShow == '1' && isInclude(currentType, item.store.type)"
  110. >
  111. <view class="input_tit">{{ item.label }}</view>
  112. <picker
  113. @change="regionChange($event, item.vModel)"
  114. mode="region"
  115. :value="item.defValue"
  116. >
  117. <view class="input_con">
  118. <view class="input">{{ item.defValue || '请选择' + item.label }}</view>
  119. <image class="public_right_img" :src="iconUrl.icon_right"></image>
  120. </view>
  121. </picker>
  122. </view>
  123. <!-- ***************************** 提示 -->
  124. <view
  125. class="tip"
  126. v-if="item.type == 'tip' && isInclude(currentType, item.store.type)"
  127. >{{ item.label }}</view>
  128. </block>
  129. </view>
  130. <view class="public_btn backgroundCustom_F08" @click="toSelect">{{ btnName }}</view>
  131. <view
  132. class="public_btn addHealthCard colorCustom"
  133. @click="toRelation"
  134. v-if="showRelation"
  135. >已有健康卡,一键快速关联</view>
  136. <canvas
  137. canvas-id="pressCanvas"
  138. class="press-canvas"
  139. :style="{ width: windowWidth + 'px', height: windowWidth + 'px' }"
  140. ></canvas>
  141. </view>
  142. </template>
  143. <script lang="ts" setup>
  144. import { ref, reactive, watch, onMounted } from 'vue';
  145. import { uploadZxFileUrl, idCardVerification, sendVerificationCode_V3 } from '@/pagesPersonal/service';
  146. import { common,imgCompress } from '@/utils';
  147. import icon from '@/utils/icon';
  148. import store from '@/store';
  149. const props = defineProps({
  150. /**form表单列表 */
  151. formConfigList: {
  152. type: Array,
  153. default: () => []
  154. },
  155. /**当前方式 default 默认(身份识别) children新生儿(监护人身份证) */
  156. currentType: {
  157. type: String,
  158. default: ''
  159. },
  160. /**
  161. * 按钮名称
  162. */
  163. btnName: {
  164. type: String,
  165. default: '下一步'
  166. },
  167. /**
  168. * 是否在健康卡身份证识别页面
  169. * 此页面需要修改form样式
  170. */
  171. scanPage: {
  172. type: Boolean,
  173. default: false
  174. },
  175. /**
  176. * 证件号码input后面是否展示照相机图片
  177. */
  178. showPhotoImg: {
  179. type: Boolean,
  180. default: true
  181. },
  182. /**
  183. * 是否展示 关联健康卡按钮
  184. */
  185. showRelation: {
  186. type: Boolean,
  187. default: false
  188. }
  189. });
  190. const emit = defineEmits(['toSelect']);
  191. const iconUrl = ref(icon)
  192. const mobile = ref(''); //获取验证码时用到的手机号
  193. const pcId = ref(''); //验证码pcId,
  194. const seconds = ref(0); //获取验证码剩余时间
  195. const disabledBtn = ref(true); //获取验证码按钮是否禁用
  196. const windowWidth = ref(0);
  197. const today = ref('');
  198. const localFormConfigList = ref<any[]>([]);
  199. const currentType = ref('');
  200. let timer: any = null;
  201. onMounted(() => {
  202. const sysInfo = uni.getSystemInfoSync();
  203. windowWidth.value = sysInfo.windowWidth;
  204. });
  205. watch(
  206. () => props.formConfigList,
  207. (newVal) => {
  208. if (!common.isEmpty(newVal)) {
  209. let cType = props.currentType;
  210. let fList = JSON.parse(JSON.stringify(newVal)); // Deep copy to avoid mutating prop
  211. fList.map((item: any) => {
  212. item.store = common.isJSON(item.store) ? JSON.parse(item.store) : item.store;
  213. item.options = common.isJSON(item.options) ? JSON.parse(item.options) : item.options;
  214. /**日期初始化 */
  215. if (item.type == 'datePicker') {
  216. item.defValue = common.newDay();
  217. }
  218. });
  219. if (!cType) {
  220. if (fList.filter((item: any) => item.vModel == 'children' && item.defValue == 'true').length > 0) {
  221. /**如果新生儿已开启 */
  222. cType = 'children';
  223. } else {
  224. cType = 'default';
  225. }
  226. }
  227. fList.sort((a: any, b: any) => {
  228. if (a.store.index && b.store.index) {
  229. return Number(a.store.index) - Number(b.store.index);
  230. } else {
  231. return 1;
  232. }
  233. });
  234. fList = setFormConfigList(fList);
  235. today.value = common.newDay();
  236. localFormConfigList.value = fList;
  237. currentType.value = cType;
  238. }
  239. },
  240. { immediate: true, deep: true }
  241. );
  242. // WXS helper replacement
  243. const isInclude = (str: string, strs: string | string[]) => {
  244. if (strs && strs.indexOf(str) != -1) {
  245. return true;
  246. } else {
  247. return false;
  248. }
  249. };
  250. /**
  251. * 读取身份证按钮点击
  252. */
  253. const photoBtnClick = async () => {
  254. let resp = await chooseImage();
  255. if (!common.isEmpty(resp)) {
  256. let data = await upLoad(resp);
  257. if (!common.isEmpty(data)) {
  258. let queryData = {
  259. filePath: data.url.replace(/\\/g, '/'),
  260. type: 1 //1身份证 2户口本
  261. };
  262. let { resData } = await idCardVerification(queryData);
  263. if (resData.RespCode == 10000 && resData.Data) {
  264. let idCardInfo = resData.Data;
  265. let fList = localFormConfigList.value;
  266. let cType = currentType.value;
  267. fList.map((item) => {
  268. if (cType == 'children') {
  269. /**如果是新生儿 给监护人证件信息赋值 */
  270. if (item.vModel == 'guardianName') {
  271. item.defValue = idCardInfo[0].Names;
  272. } else if (item.vModel == 'guardianCertNum') {
  273. item.defValue = idCardInfo[0].IdCards;
  274. }
  275. } else {
  276. /**否则给当前就诊人证件信息赋值 */
  277. if (item.vModel == 'memberName') {
  278. item.defValue = idCardInfo[0].Names;
  279. } else if (item.vModel == 'certNum') {
  280. item.defValue = idCardInfo[0].IdCards;
  281. }
  282. }
  283. });
  284. localFormConfigList.value = fList;
  285. }
  286. }
  287. }
  288. };
  289. /**选取照片 */
  290. const chooseImage = () => {
  291. return new Promise((resolve) => {
  292. uni.chooseImage({
  293. count: 1,
  294. sourceType: ['album', 'camera'],
  295. /**album:从相册选图 camera:使用相机 */
  296. success: (res) => {
  297. resolve(res);
  298. }
  299. });
  300. });
  301. };
  302. /**
  303. * 上传图片
  304. */
  305. const upLoad = (resp: any) => {
  306. uni.showLoading({
  307. mask: true
  308. });
  309. return new Promise<any>((resolve) => {
  310. imgCompress.getLessLimitSizeImage(
  311. 'pressCanvas',
  312. resp.tempFilePaths[0],
  313. 1,
  314. windowWidth.value,
  315. (path: string) => {
  316. uni.uploadFile({
  317. url: uploadZxFileUrl,
  318. filePath: path,
  319. name: 'newsFile',
  320. formData: {
  321. user: 'test'
  322. },
  323. header: {
  324. token: store.state.token
  325. },
  326. success(res) {
  327. uni.hideLoading();
  328. let data = JSON.parse(res.data);
  329. if (data.RespCode == '10000') {
  330. resolve(data);
  331. } else {
  332. common.showModal('图片上传失败');
  333. resolve(false);
  334. }
  335. }
  336. });
  337. }
  338. );
  339. });
  340. };
  341. /**
  342. * 点击获取验证码
  343. */
  344. const getCode = async () => {
  345. if (disabledBtn.value) {
  346. return;
  347. }
  348. let queryData = {
  349. mobile: mobile.value
  350. };
  351. let { resData } = await sendVerificationCode_V3(queryData);
  352. if (resData.RespCode == 10000 && resData.Data) {
  353. pcId.value = resData.Data[0].pcId;
  354. common.showModal('发送成功!');
  355. startCountDown();
  356. }
  357. };
  358. const startCountDown = () => {
  359. if (timer) clearInterval(timer);
  360. seconds.value = 60;
  361. disabledBtn.value = true;
  362. timer = setInterval(() => {
  363. seconds.value--;
  364. if (seconds.value <= 0) {
  365. clearInterval(timer);
  366. seconds.value = 0;
  367. disabledBtn.value = false;
  368. }
  369. }, 1000);
  370. };
  371. /**
  372. * 滑块开关
  373. */
  374. const switchChange = async (e: any, key: string) => {
  375. let value = e.detail.value;
  376. let cType = currentType.value;
  377. let fList = localFormConfigList.value;
  378. fList.map((item) => {
  379. if (item.vModel == key) {
  380. item.defValue = value ? 'true' : 'false';
  381. }
  382. });
  383. if (value && key == 'children') {
  384. cType = key;
  385. } else if (key == 'children') {
  386. /**关闭新生儿 */
  387. cType = 'default';
  388. }
  389. /**开启新生儿时 显示性别和出生日期 关闭时隐藏*/
  390. fList.map((cell) => {
  391. if (cell.vModel == 'sex' || cell.vModel == 'birthDate') {
  392. if (key == 'children') {
  393. /**判断当前证件类型是否为身份证 如果是身份证 不显示性别和出生日期 */
  394. let certType = fList.filter((item) => item.vModel == 'certType')[0];
  395. cell.isShow = value
  396. ? '1'
  397. : certType.options[certType.defValue].value == '01'
  398. ? '0'
  399. : '1';
  400. }
  401. }
  402. });
  403. fList = setFormConfigList(fList);
  404. currentType.value = cType;
  405. localFormConfigList.value = fList;
  406. };
  407. /**
  408. * 选择证件类型
  409. */
  410. const pickerChange = (e: any, key: string) => {
  411. let value = e.detail.value;
  412. let fList = localFormConfigList.value;
  413. let cType = currentType.value;
  414. fList.map((item) => {
  415. if (item.vModel == key) {
  416. item.defValue = value;
  417. if (item.vModel == 'certType' && cType != 'children') {
  418. fList.map((cell) => {
  419. /**如果选择的不是身份证 显示性别和出生日期(新生儿页面不隐藏) */
  420. if (cell.vModel == 'sex' || cell.vModel == 'birthDate') {
  421. cell.isShow = item.options[value].value != '01' ? '1' : '0';
  422. }
  423. /**如果选择的不是身份证 禁用验证方式选择 */
  424. if (cell.vModel == 'checkType') {
  425. cell.isDisabled = item.options[value].value != '01' ? true : false;
  426. cell.defValue = 0;
  427. }
  428. /**如果选择的不是身份证 显示手机号和验证码*/
  429. if (cell.vModel == 'userMobile' || cell.vModel == 'provingCode') {
  430. cell.isShow = 1;
  431. }
  432. });
  433. }
  434. /**选择验证方式 */
  435. if (item.vModel == 'checkType') {
  436. fList.map((cell) => {
  437. /**如果选择的不是手机号验证 隐藏手机号和验证码 1手机号验证 2人脸*/
  438. if (cell.vModel == 'userMobile' || cell.vModel == 'provingCode') {
  439. cell.isShow = item.options[value].value == '1' ? '1' : '0';
  440. }
  441. });
  442. }
  443. }
  444. });
  445. localFormConfigList.value = fList;
  446. };
  447. /**
  448. * 省市区选择器
  449. */
  450. const regionChange = (e: any, key: string) => {
  451. let value = e.detail.value;
  452. let fList = localFormConfigList.value;
  453. fList.map((item) => {
  454. if (item.vModel == key) {
  455. item.defValue = value;
  456. }
  457. });
  458. localFormConfigList.value = fList;
  459. };
  460. /**
  461. * 输入框赋值
  462. */
  463. const setVal = (e: any, key: string) => {
  464. let fList = localFormConfigList.value;
  465. let value = e.detail.value;
  466. fList.map((item) => {
  467. if (item.vModel == key) {
  468. item.defValue = value;
  469. /**输入手机号时 判断发送验证码按钮显隐 */
  470. if (item.vModel == 'userMobile') {
  471. let reg = new RegExp(item.regularExpressions);
  472. let disabled = true;
  473. if (reg.test(item.defValue)) {
  474. disabled = false;
  475. }
  476. disabledBtn.value = disabled;
  477. mobile.value = item.defValue;
  478. }
  479. /**输入身份证号时 转换大写*/
  480. if (item.vModel == 'certNum' || item.vModel == 'guardianCertNum') {
  481. item.defValue = item.defValue.toUpperCase();
  482. }
  483. }
  484. });
  485. localFormConfigList.value = fList;
  486. };
  487. /**
  488. * 单选框点击
  489. */
  490. const radioClick = (key: string, index: number) => {
  491. let fList = localFormConfigList.value;
  492. fList.map((item) => {
  493. if (item.vModel == key) {
  494. item.defValue = index;
  495. }
  496. });
  497. localFormConfigList.value = fList;
  498. };
  499. /**
  500. * 点击下一步
  501. */
  502. const toSelect = async () => {
  503. await common.sleep();
  504. emit('toSelect', {
  505. formConfigList: localFormConfigList.value,
  506. currentType: currentType.value,
  507. pcId: pcId.value
  508. });
  509. };
  510. /**
  511. * 点击关联健康卡
  512. */
  513. const toRelation = () => {
  514. /**点击关联健康卡 给父组件一个relation为true判断 */
  515. emit('toSelect', {
  516. relation: true
  517. });
  518. };
  519. // 设置圆角
  520. const setFormConfigList = (fList: any[]) => {
  521. fList.forEach((item, index) => {
  522. item.className = '';
  523. if (item.type == 'tip' && item.isShow == '1') {
  524. for (let i = 1; i < index; i++) {
  525. if (fList[index - i].isShow == '1') {
  526. fList[index - i].className = 'toBoderRadio';
  527. break;
  528. }
  529. }
  530. }
  531. });
  532. return fList;
  533. };
  534. </script>
  535. <style lang="scss" scoped>
  536. .formBox {
  537. padding: 0 30upx;
  538. }
  539. .input_list {
  540. display: inline-block;
  541. width: 100%;
  542. overflow: hidden;
  543. border-radius: 24upx;
  544. }
  545. .input_item {
  546. height: 110upx;
  547. display: flex;
  548. align-items: center;
  549. position: relative;
  550. margin-bottom: -4upx;
  551. background: #fff;
  552. padding: 0 30upx;
  553. }
  554. .public_right_img {
  555. right: 30upx;
  556. }
  557. .input_con {
  558. width: 460upx;
  559. display: flex;
  560. align-items: center;
  561. justify-content: space-between;
  562. }
  563. .input_tit {
  564. font-size: 30upx;
  565. color: rgba(85, 85, 85, 1);
  566. width: 230upx;
  567. flex-shrink: 0;
  568. }
  569. .radio_list {
  570. width: 100%;
  571. display: flex;
  572. align-items: center;
  573. }
  574. .radio_item {
  575. padding: 6upx 48upx;
  576. font-family: PingFangSC-Medium, PingFang SC;
  577. font-weight: 500;
  578. color: #fff;
  579. background-color: #ccc;
  580. border-radius: 64upx;
  581. line-height: 44upx;
  582. margin-right: 24upx;
  583. }
  584. .input {
  585. padding: 20upx 0;
  586. }
  587. .public_btn {
  588. margin: 60upx 30upx;
  589. }
  590. .code_con .input {
  591. width: 300upx;
  592. }
  593. .code_con_txt {
  594. font-size: 28upx;
  595. white-space: nowrap;
  596. padding: 20upx 0 20upx 20upx;
  597. color: #999;
  598. }
  599. .input_item_fir {
  600. justify-content: space-between;
  601. }
  602. .tip {
  603. font-size: 30upx;
  604. font-family: Source Han Sans CN;
  605. font-weight: 500;
  606. color: rgba(1, 1, 1, 1);
  607. padding: 20upx 0;
  608. background-color: #f1f1f6;
  609. position: relative;
  610. }
  611. .tip + .input_item {
  612. border-radius: 24upx 24upx 0 0;
  613. }
  614. .tip:before,
  615. .tip:after {
  616. content: '';
  617. display: block;
  618. width: 30upx;
  619. height: 100%;
  620. background-color: #f1f1f6;
  621. top: 0;
  622. position: absolute;
  623. }
  624. .tip:before {
  625. left: -30upx;
  626. }
  627. .tip:after {
  628. right: -30upx;
  629. }
  630. .btnBox {
  631. width: 150upx;
  632. }
  633. .btnBox button {
  634. display: flex;
  635. align-items: center;
  636. justify-content: center;
  637. background-color: transparent;
  638. }
  639. .disabledBtn {
  640. opacity: 0.5;
  641. }
  642. .photo_btn {
  643. width: 40upx;
  644. height: 36upx;
  645. }
  646. .press-canvas {
  647. position: absolute;
  648. top: -2000upx;
  649. background-color: gray;
  650. }
  651. .inputDisabled {
  652. color: #999;
  653. }
  654. .scan_page .input_item {
  655. height: 80upx;
  656. line-height: 24upx;
  657. }
  658. .addHealthCard {
  659. border: 1px solid;
  660. font-family: PingFangSC-Regular, PingFang SC;
  661. font-weight: 400;
  662. margin-top: -10upx;
  663. }
  664. .toBoderRadio {
  665. border-radius: 0 0 30upx 30upx;
  666. }
  667. </style>