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

527 lines
10 KiB

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