猫妈狗爸伴宠师小程序前端代码
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.

524 lines
10 KiB

3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
3 months ago
  1. <template>
  2. <view class="containers po-r">
  3. <image src="" mode="" class="mainBg"></image>
  4. <view class="po-a content">
  5. <stepProgress :step="1"></stepProgress>
  6. <view class="bg-fff mt22 form ">
  7. <view class="title fw700 size-30 flex-rowl">
  8. 基本信息
  9. </view>
  10. <dForm ref="formRef" :list="state.list" labelWidth="220rpx" :isFooter="false" @input="onFormInput"></dForm>
  11. </view>
  12. <view class="license__view" v-if="form.isHave">
  13. <view class="license">
  14. <up-checkbox-group
  15. v-model="licenseData.selected"
  16. shape="circle"
  17. activeColor="#FFBF60"
  18. labelColor="#000000"
  19. labelSize="26rpx"
  20. >
  21. <view class="license-options">
  22. <up-checkbox
  23. v-for="item in licenseOptions"
  24. :key="`license-${item.id}`"
  25. :label="item.title"
  26. :name="item.id"
  27. >
  28. </up-checkbox>
  29. </view>
  30. </up-checkbox-group>
  31. <view class="tips">
  32. {{ configList.pet_news.paramValueText }}
  33. </view>
  34. <up-upload
  35. :fileList="licenseData.fileList"
  36. @afterRead="afterRead"
  37. @delete="deletePic"
  38. multiple
  39. >
  40. <image src="../static/list/icon-upload.png" style="width: 144rpx;height: 144rpx;"></image>
  41. </up-upload>
  42. </view>
  43. </view>
  44. <view class="bg-fff mt22 form bt120">
  45. <view class="title fw700 size-30 flex-rowl">
  46. 个人宠物类型
  47. </view>
  48. <view class="flex-between wrap mt32">
  49. <view class="type" v-for="item in petTypeOptions" :key="`petType-${item.id}`" @click="onSelectPetType(item.id)">
  50. <image :src="item.imageNo" mode="widthFix"></image>
  51. <template v-if="petType.includes(item.id)">
  52. <image class="active" :src="item.image" mode="widthFix"></image>
  53. <view class="active-icon">
  54. <up-icon name="checkmark-circle" color="#FFBF60" size="32rpx"></up-icon>
  55. </view>
  56. </template>
  57. </view>
  58. </view>
  59. </view>
  60. <view class="footer-btn" @click="toNext">
  61. <view class="btn">
  62. 下一步
  63. </view>
  64. </view>
  65. </view>
  66. <configPopup ref="configPopupRef" />
  67. </view>
  68. </template>
  69. <script setup>
  70. import { ref, reactive, computed } from "vue";
  71. import { useStore } from 'vuex'
  72. import { onLoad } from '@dcloudio/uni-app'
  73. import { ossUpload } from '@/utils/oss-upload/oss/index.js'
  74. import { getLicenseList } from '@/api/examination'
  75. import { insertUser, udpateUser, getUserOne } from '@/api/userTeacher'
  76. import dForm from "@/components/dForm/index.vue"
  77. import stepProgress from '../components/stepProgress.vue';
  78. import configPopup from '@/components/configPopup.vue'
  79. const configPopupRef = ref(null)
  80. const store = useStore()
  81. const configList = computed(() => {
  82. return store.getters.configList
  83. })
  84. const userId = computed(() => {
  85. return store.state.user.userInfo.userId
  86. })
  87. const id = ref(null)
  88. const state = reactive({
  89. list: [{
  90. type: "input",
  91. label: "姓名",
  92. key: "name",
  93. placeholder: "请输入您的真实姓名",
  94. },
  95. {
  96. type: "input",
  97. label: "身份证号",
  98. key: "idCard",
  99. placeholder: "请输入您的真实身份证号",
  100. },
  101. {
  102. type: "radio",
  103. label: "性别",
  104. key: "sex",
  105. options: [{
  106. name: "男",
  107. value: 0,
  108. },
  109. {
  110. name: "女",
  111. value: 1,
  112. }
  113. ]
  114. },
  115. {
  116. type: "input",
  117. label: "年龄",
  118. key: "age",
  119. placeholder: "请输入您的年龄",
  120. },
  121. {
  122. type: "input",
  123. label: "手机号",
  124. key: "phone",
  125. placeholder: "请输入您的手机号",
  126. },
  127. {
  128. type: "input",
  129. label: "养宠经验",
  130. key: "experience",
  131. placeholder: "请输入您的养宠年限",
  132. unit: "年"
  133. },
  134. {
  135. type: "radio",
  136. label: "是否有专业执照",
  137. key: "isHave",
  138. placeholder: "请选择",
  139. options: [{
  140. name: "是",
  141. value: 1,
  142. }, {
  143. name: "没有",
  144. value: 0,
  145. }]
  146. },
  147. ]
  148. })
  149. const formRef = ref()
  150. const form = ref({})
  151. const onFormInput = (e) => {
  152. form.value = e
  153. }
  154. const fetchUserInfo = async () => {
  155. try {
  156. const { userTelephone } = store.state.user.userInfo
  157. if(!userId.value) {
  158. return
  159. }
  160. const data = await getUserOne(userId.value)
  161. if (data) {
  162. const {
  163. id: _id,
  164. status,
  165. name,
  166. idCard,
  167. sex,
  168. age,
  169. phone,
  170. experience,
  171. isHave,
  172. license,
  173. images,
  174. petType: _petType,
  175. } = data
  176. if ([1,2].includes(status)) { // status: 0-审核中 1-通过 2-不通过
  177. uni.navigateTo({
  178. url: `/otherPages/authentication/examination/trainCompleted/index?status=${status}`
  179. })
  180. return
  181. }
  182. id.value = _id
  183. formRef.value.setData({
  184. name,
  185. idCard,
  186. sex,
  187. age,
  188. phone: phone || userTelephone,
  189. experience,
  190. isHave,
  191. })
  192. licenseData.selected = license?.split?.(';').map(str => parseInt(str)) || []
  193. licenseData.fileList = images?.split?.(';').map(url => ({ url })) || []
  194. petType.value = _petType.split(',').map(n => parseInt(n))
  195. } else {
  196. formRef.value.setData({
  197. phone: userTelephone,
  198. })
  199. }
  200. store.dispatch('fetchPetTypeOptions')
  201. fetchLicenseOptions()
  202. } catch (err) {
  203. console.log('--err', err)
  204. }
  205. }
  206. const licenseData = reactive({
  207. selected: [],
  208. fileList: []
  209. })
  210. const licenseOptions = ref([])
  211. const fetchLicenseOptions = async () => {
  212. try {
  213. licenseOptions.value = await getLicenseList()
  214. } catch (err) {
  215. }
  216. }
  217. const afterRead = (event) => {
  218. event.file.forEach(n => {
  219. ossUpload(n.url)
  220. .then(url => {
  221. licenseData.fileList.push({
  222. url
  223. })
  224. })
  225. })
  226. };
  227. const deletePic = (event) => {
  228. licenseData.fileList.splice(event.index, 1);
  229. };
  230. const petType = ref([])
  231. const petTypeOptions = computed(() => {
  232. return store.getters.petTypeOptions
  233. })
  234. const onSelectPetType = (type) => {
  235. petType.value = petType.value.includes(type) ? petType.value.filter(n => n !== type) : [...petType.value, type]
  236. }
  237. const toNext = async () => {
  238. try {
  239. const {
  240. name,
  241. idCard,
  242. sex,
  243. age,
  244. phone,
  245. experience,
  246. isHave,
  247. } = form.value
  248. const data = {
  249. userId: userId.value,
  250. name,
  251. idCard,
  252. sex,
  253. age,
  254. phone,
  255. experience,
  256. isHave,
  257. petType: petType.value.join(','),
  258. }
  259. if (isHave) {
  260. const { selected, fileList } = licenseData
  261. data.license = selected.join(';')
  262. data.images = fileList.map(item => item.url).join(';')
  263. }
  264. // 效验字段必填
  265. const requiredFields = {
  266. name: '姓名',
  267. idCard: '身份证号',
  268. age: '年龄',
  269. phone: '手机号',
  270. experience: '养宠经验'
  271. }
  272. for (const [field, label] of Object.entries(requiredFields)) {
  273. if (!data[field]) {
  274. uni.showToast({
  275. title: `请填写${label}`,
  276. icon: 'none'
  277. })
  278. return
  279. }
  280. }
  281. // 性别需要单独判断,因为0是有效值(男性)
  282. if (data.sex != 0 && data.sex != 1) {
  283. uni.showToast({
  284. title: '请选择性别',
  285. icon: 'none'
  286. })
  287. return
  288. }
  289. // 验证手机号格式
  290. if (!/^1[3-9]\d{9}$/.test(data.phone)) {
  291. uni.showToast({
  292. title: '请输入正确的手机号',
  293. icon: 'none'
  294. })
  295. return
  296. }
  297. // 验证身份证号格式
  298. if (!/^\d{17}[\dXx]$/.test(data.idCard)) {
  299. uni.showToast({
  300. title: '请输入正确的身份证号',
  301. icon: 'none'
  302. })
  303. return
  304. }
  305. // 验证年龄范围
  306. if (isNaN(data.age) || data.age < 18 || data.age > 70) {
  307. uni.showToast({
  308. title: '请输入有效的年龄(18-70岁)',
  309. icon: 'none'
  310. })
  311. return
  312. }
  313. if (petType.value.length === 0) {
  314. uni.showToast({
  315. title: '请选择宠物类型',
  316. icon: 'none'
  317. })
  318. return
  319. }
  320. if (data.isHave === 1) {
  321. if (licenseData.selected.length === 0) {
  322. uni.showToast({
  323. title: '请选择专业执照类型',
  324. icon: 'none'
  325. })
  326. return
  327. }
  328. if (licenseData.fileList.length === 0) {
  329. uni.showToast({
  330. title: '请上传执照图片',
  331. icon: 'none'
  332. })
  333. return
  334. }
  335. }
  336. if (id.value) {
  337. data.id = id.value
  338. await udpateUser(data)
  339. } else {
  340. await insertUser(data)
  341. }
  342. uni.navigateTo({
  343. url: `/otherPages/authentication/examination/start?petType=${petType.value.join(',')}`
  344. })
  345. } catch (err) {
  346. uni.navigateTo({
  347. url: `/otherPages/authentication/examination/start?petType=${petType.value.join(',')}`
  348. })
  349. }
  350. }
  351. onLoad(() => {
  352. fetchUserInfo()
  353. store.dispatch('fetchPetTypeOptions')
  354. })
  355. </script>
  356. <style lang="scss" scoped>
  357. .bt120 {
  358. margin-bottom: 120rpx;
  359. width: 716rpx;
  360. box-sizing: border-box;
  361. }
  362. .footer-btn {
  363. width: 100vw;
  364. height: 144rpx;
  365. background-color: #fff;
  366. display: flex;
  367. justify-content: center;
  368. position: fixed;
  369. bottom: 0;
  370. left: 0;
  371. align-items: center;
  372. .btn {
  373. font-size: 30rpx;
  374. color: #fff;
  375. display: flex;
  376. justify-content: center;
  377. align-items: center;
  378. width: 574rpx;
  379. height: 94rpx;
  380. border-radius: 94rpx;
  381. background-color: #FFBF60;
  382. }
  383. }
  384. .type {
  385. width: 190rpx;
  386. margin-bottom: 74rpx;
  387. position: relative;
  388. image {
  389. width: 100%;
  390. }
  391. .active {
  392. position: absolute;
  393. top: 0;
  394. left: 0;
  395. &-icon {
  396. position: absolute;
  397. top: 14rpx;
  398. right: 18rpx;
  399. }
  400. }
  401. }
  402. .form {
  403. padding: 40rpx 32rpx;
  404. box-sizing: border-box;
  405. width: 716rpx;
  406. }
  407. .title {
  408. &::before {
  409. content: "";
  410. display: block;
  411. width: 9rpx;
  412. height: 33rpx;
  413. background-color: #FFBF60;
  414. margin-right: 7rpx;
  415. }
  416. }
  417. .mb6 {
  418. margin-bottom: 6rpx;
  419. }
  420. .containers {
  421. .mainBg {
  422. width: 100vw;
  423. height: 442rpx;
  424. background-image: linear-gradient(to bottom, #FFBF60, #f5f5f5);
  425. }
  426. .content {
  427. top: 0;
  428. left: 0;
  429. padding: 16rpx;
  430. }
  431. }
  432. .license__view {
  433. width: 716rpx;
  434. padding-bottom: 40rpx;
  435. box-sizing: border-box;
  436. background-color: #FFFFFF;
  437. .license {
  438. width: 100%;
  439. padding: 13rpx 16rpx;
  440. box-sizing: border-box;
  441. background-color: #FFFCF1;
  442. &-options {
  443. display: grid;
  444. grid-template-columns: repeat(2, 1fr);
  445. }
  446. }
  447. .tips {
  448. margin: 22rpx 0 24rpx 0;
  449. color: #FFBF60;
  450. font-size: 22rpx;
  451. width: 100%;
  452. word-break: break-all;
  453. }
  454. }
  455. </style>