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

520 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. const data = await getUserOne(userId.value)
  158. if (data) {
  159. const {
  160. id: _id,
  161. status,
  162. name,
  163. idCard,
  164. sex,
  165. age,
  166. phone,
  167. experience,
  168. isHave,
  169. license,
  170. images,
  171. petType: _petType,
  172. } = data
  173. if ([1,2].includes(status)) { // status: 0-审核中 1-通过 2-不通过
  174. uni.navigateTo({
  175. url: `/otherPages/authentication/examination/trainCompleted/index?status=${status}`
  176. })
  177. return
  178. }
  179. id.value = _id
  180. formRef.value.setData({
  181. name,
  182. idCard,
  183. sex,
  184. age,
  185. phone: phone || userTelephone,
  186. experience,
  187. isHave,
  188. })
  189. licenseData.selected = license?.split?.(';').map(str => parseInt(str)) || []
  190. licenseData.fileList = images?.split?.(';').map(url => ({ url })) || []
  191. petType.value = _petType.split(',').map(n => parseInt(n))
  192. } else {
  193. formRef.value.setData({
  194. phone: userTelephone,
  195. })
  196. }
  197. store.dispatch('fetchPetTypeOptions')
  198. fetchLicenseOptions()
  199. } catch (err) {
  200. console.log('--err', err)
  201. }
  202. }
  203. const licenseData = reactive({
  204. selected: [],
  205. fileList: []
  206. })
  207. const licenseOptions = ref([])
  208. const fetchLicenseOptions = async () => {
  209. try {
  210. licenseOptions.value = await getLicenseList()
  211. } catch (err) {
  212. }
  213. }
  214. const afterRead = (event) => {
  215. event.file.forEach(n => {
  216. ossUpload(n.url)
  217. .then(url => {
  218. licenseData.fileList.push({
  219. url
  220. })
  221. })
  222. })
  223. };
  224. const deletePic = (event) => {
  225. licenseData.fileList.splice(event.index, 1);
  226. };
  227. const petType = ref([])
  228. const petTypeOptions = computed(() => {
  229. return store.getters.petTypeOptions
  230. })
  231. const onSelectPetType = (type) => {
  232. petType.value = petType.value.includes(type) ? petType.value.filter(n => n !== type) : [...petType.value, type]
  233. }
  234. const toNext = async () => {
  235. try {
  236. const {
  237. name,
  238. idCard,
  239. sex,
  240. age,
  241. phone,
  242. experience,
  243. isHave,
  244. } = form.value
  245. const data = {
  246. userId: userId.value,
  247. name,
  248. idCard,
  249. sex,
  250. age,
  251. phone,
  252. experience,
  253. isHave,
  254. petType: petType.value.join(','),
  255. }
  256. if (isHave) {
  257. const { selected, fileList } = licenseData
  258. data.license = selected.join(';')
  259. data.images = fileList.map(item => item.url).join(';')
  260. }
  261. // 效验字段必填
  262. const requiredFields = {
  263. name: '姓名',
  264. idCard: '身份证号',
  265. age: '年龄',
  266. phone: '手机号',
  267. experience: '养宠经验'
  268. }
  269. for (const [field, label] of Object.entries(requiredFields)) {
  270. if (!data[field]) {
  271. uni.showToast({
  272. title: `请填写${label}`,
  273. icon: 'none'
  274. })
  275. return
  276. }
  277. }
  278. // 性别需要单独判断,因为0是有效值(男性)
  279. if (data.sex != 0 && data.sex != 1) {
  280. uni.showToast({
  281. title: '请选择性别',
  282. icon: 'none'
  283. })
  284. return
  285. }
  286. // 验证手机号格式
  287. if (!/^1[3-9]\d{9}$/.test(data.phone)) {
  288. uni.showToast({
  289. title: '请输入正确的手机号',
  290. icon: 'none'
  291. })
  292. return
  293. }
  294. // 验证身份证号格式
  295. if (!/^\d{17}[\dXx]$/.test(data.idCard)) {
  296. uni.showToast({
  297. title: '请输入正确的身份证号',
  298. icon: 'none'
  299. })
  300. return
  301. }
  302. // 验证年龄范围
  303. if (isNaN(data.age) || data.age < 18 || data.age > 70) {
  304. uni.showToast({
  305. title: '请输入有效的年龄(18-70岁)',
  306. icon: 'none'
  307. })
  308. return
  309. }
  310. if (petType.value.length === 0) {
  311. uni.showToast({
  312. title: '请选择宠物类型',
  313. icon: 'none'
  314. })
  315. return
  316. }
  317. if (data.isHave === 1) {
  318. if (licenseData.selected.length === 0) {
  319. uni.showToast({
  320. title: '请选择专业执照类型',
  321. icon: 'none'
  322. })
  323. return
  324. }
  325. if (licenseData.fileList.length === 0) {
  326. uni.showToast({
  327. title: '请上传执照图片',
  328. icon: 'none'
  329. })
  330. return
  331. }
  332. }
  333. if (id.value) {
  334. data.id = id.value
  335. await udpateUser(data)
  336. } else {
  337. await insertUser(data)
  338. }
  339. uni.navigateTo({
  340. url: `/otherPages/authentication/examination/start?petType=${petType.value.join(',')}`
  341. })
  342. } catch (err) {
  343. uni.navigateTo({
  344. url: `/otherPages/authentication/examination/start?petType=${petType.value.join(',')}`
  345. })
  346. }
  347. }
  348. onLoad(() => {
  349. fetchUserInfo()
  350. store.dispatch('fetchPetTypeOptions')
  351. })
  352. </script>
  353. <style lang="scss" scoped>
  354. .bt120 {
  355. margin-bottom: 120rpx;
  356. width: 716rpx;
  357. box-sizing: border-box;
  358. }
  359. .footer-btn {
  360. width: 100vw;
  361. height: 144rpx;
  362. background-color: #fff;
  363. display: flex;
  364. justify-content: center;
  365. position: fixed;
  366. bottom: 0;
  367. left: 0;
  368. align-items: center;
  369. .btn {
  370. font-size: 30rpx;
  371. color: #fff;
  372. display: flex;
  373. justify-content: center;
  374. align-items: center;
  375. width: 574rpx;
  376. height: 94rpx;
  377. border-radius: 94rpx;
  378. background-color: #FFBF60;
  379. }
  380. }
  381. .type {
  382. width: 190rpx;
  383. margin-bottom: 74rpx;
  384. position: relative;
  385. image {
  386. width: 100%;
  387. }
  388. .active {
  389. position: absolute;
  390. top: 0;
  391. left: 0;
  392. &-icon {
  393. position: absolute;
  394. top: 14rpx;
  395. right: 18rpx;
  396. }
  397. }
  398. }
  399. .form {
  400. padding: 40rpx 32rpx;
  401. box-sizing: border-box;
  402. width: 716rpx;
  403. }
  404. .title {
  405. &::before {
  406. content: "";
  407. display: block;
  408. width: 9rpx;
  409. height: 33rpx;
  410. background-color: #FFBF60;
  411. margin-right: 7rpx;
  412. }
  413. }
  414. .mb6 {
  415. margin-bottom: 6rpx;
  416. }
  417. .containers {
  418. .mainBg {
  419. width: 100vw;
  420. height: 442rpx;
  421. background-image: linear-gradient(to bottom, #FFBF60, #f5f5f5);
  422. }
  423. .content {
  424. top: 0;
  425. left: 0;
  426. padding: 16rpx;
  427. }
  428. }
  429. .license__view {
  430. width: 716rpx;
  431. padding-bottom: 40rpx;
  432. box-sizing: border-box;
  433. background-color: #FFFFFF;
  434. .license {
  435. width: 100%;
  436. padding: 13rpx 16rpx;
  437. box-sizing: border-box;
  438. background-color: #FFFCF1;
  439. &-options {
  440. display: grid;
  441. grid-template-columns: repeat(2, 1fr);
  442. }
  443. }
  444. .tips {
  445. margin: 22rpx 0 24rpx 0;
  446. color: #FFBF60;
  447. font-size: 22rpx;
  448. width: 100%;
  449. word-break: break-all;
  450. }
  451. }
  452. </style>