Parcourir la source

完成修改认证页面

steelwei il y a 1 mois
Parent
commit
0148617373
4 fichiers modifiés avec 653 ajouts et 6 suppressions
  1. 13 0
      api/fulfiller.js
  2. 6 0
      pages.json
  3. 584 0
      pages/mine/settings/auth/edit.vue
  4. 50 6
      pages/mine/settings/auth/index.vue

+ 13 - 0
api/fulfiller.js

@@ -222,3 +222,16 @@ export function deleteAccount() {
     method: 'DELETE'
   })
 }
+
+/**
+ * 更新认证信息
+ * @param {Object} data - 认证数据
+ * @author steelwei
+ */
+export function updateAuthInfo(data) {
+  return request({
+    url: '/fulfiller/fulfiller/my/auth',
+    method: 'POST',
+    data
+  })
+}

+ 6 - 0
pages.json

@@ -143,6 +143,12 @@
 				"navigationStyle": "custom"
 			}
 		},
+		{
+			"path": "pages/mine/settings/auth/edit",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
 		{
 			"path": "pages/mine/settings/bank/index",
 			"style": {

+ 584 - 0
pages/mine/settings/auth/edit.vue

@@ -0,0 +1,584 @@
+<template>
+  <view class="edit-auth-container">
+    <!-- 自定义头部 -->
+    <view class="custom-header">
+      <view class="header-left" @click="navBack">
+        <image class="back-icon" src="/static/icons/chevron_right_dark.svg" style="transform: rotate(180deg);"></image>
+      </view>
+      <text class="header-title">修改认证信息</text>
+      <view class="header-right"></view>
+    </view>
+    <view class="header-placeholder"></view>
+
+    <!-- 顶部警告提示 -->
+    <view class="warning-tip">
+      <text class="warning-icon">⚠</text>
+      <text class="warning-text">若修改认证信息,将在审核通过后生效</text>
+    </view>
+
+    <!-- 身份认证 -->
+    <view class="section-card">
+      <view class="section-title">身份认证</view>
+      <text class="section-subtitle">点击图片修改</text>
+      
+      <view class="id-card-row">
+        <!-- 身份证正面 -->
+        <view class="id-card-upload" @click="chooseImage('front')">
+          <image v-if="idCardFront" :src="idCardFront" class="id-card-img" mode="aspectFill"></image>
+          <view v-else class="id-card-placeholder">
+            <text class="placeholder-text">ID Front</text>
+          </view>
+          <view class="delete-btn" v-if="idCardFront" @click.stop="deleteImage('front')">×</view>
+          <view class="corner-tag">人像面</view>
+        </view>
+
+        <!-- 身份证反面 -->
+        <view class="id-card-upload" @click="chooseImage('back')">
+          <image v-if="idCardBack" :src="idCardBack" class="id-card-img" mode="aspectFill"></image>
+          <view v-else class="id-card-placeholder">
+            <text class="placeholder-text">ID Back</text>
+          </view>
+          <view class="delete-btn" v-if="idCardBack" @click.stop="deleteImage('back')">×</view>
+          <view class="corner-tag">国徽面</view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 服务类型 -->
+    <view class="section-card">
+      <view class="section-title">服务类型</view>
+      <text class="section-subtitle">可多选</text>
+      
+      <view class="service-list">
+        <view 
+          class="service-item" 
+          v-for="(service, index) in serviceOptions" 
+          :key="index"
+          @click="toggleService(service)"
+        >
+          <text class="service-name">{{ service }}</text>
+          <view class="check-icon" :class="{ active: selectedServices.includes(service) }">
+            <text v-if="selectedServices.includes(service)">✓</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 资质证书 -->
+    <view class="section-card">
+      <view class="section-title">资质证书</view>
+      <text class="section-subtitle">请上传对应服务的资质</text>
+      
+      <!-- 动态渲染每个服务类型的资质上传 -->
+      <view v-for="(service, index) in selectedServices" :key="index" class="qual-section">
+        <text class="qual-title">{{ service }}资质</text>
+        
+        <view class="qual-upload-row">
+          <!-- 已上传的资质图片 -->
+          <view 
+            class="qual-item" 
+            v-for="(img, imgIndex) in qualifications[service]" 
+            :key="imgIndex"
+            @click="previewImage(service, imgIndex)"
+          >
+            <image :src="img" class="qual-img" mode="aspectFill"></image>
+            <view class="delete-btn" @click.stop="deleteQualImage(service, imgIndex)">×</view>
+          </view>
+          
+          <!-- 上传按钮 -->
+          <view class="qual-upload-btn" @click="chooseQualImage(service)">
+            <text class="plus-icon">+</text>
+          </view>
+        </view>
+      </view>
+
+      <text v-if="selectedServices.length === 0" class="empty-hint">请先选择服务类型</text>
+    </view>
+
+    <!-- 底部提交按钮 -->
+    <view class="bottom-btn-area">
+      <button class="submit-btn" @click="submitAuth">提交审核</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getAuthInfo, uploadFile, updateAuthInfo } from '@/api/fulfiller'
+
+export default {
+  data() {
+    return {
+      idCardFront: '',
+      idCardBack: '',
+      idCardFrontOssId: '',
+      idCardBackOssId: '',
+      serviceOptions: ['宠物接送', '上门喂遛', '上门洗护'],
+      selectedServices: [],
+      qualifications: {},
+      qualOssIds: {}
+    }
+  },
+  onLoad() {
+    this.loadAuthInfo()
+  },
+  methods: {
+    async loadAuthInfo() {
+      try {
+        uni.showLoading({ title: '加载中...' })
+        const res = await getAuthInfo()
+        if (res.code === 200 && res.data) {
+          this.idCardFront = res.data.idCardFrontUrl || ''
+          this.idCardBack = res.data.idCardBackUrl || ''
+          this.idCardFrontOssId = res.data.idCardFront || ''
+          this.idCardBackOssId = res.data.idCardBack || ''
+          this.selectedServices = res.data.serviceTypeList || []
+          
+          this.selectedServices.forEach(service => {
+            this.qualifications[service] = []
+            this.qualOssIds[service] = []
+          })
+          
+          if (res.data.qualImageUrls && res.data.qualImageUrls.length > 0) {
+            const firstService = this.selectedServices[0]
+            if (firstService) {
+              this.qualifications[firstService] = res.data.qualImageUrls
+            }
+          }
+        }
+        uni.hideLoading()
+      } catch (e) {
+        uni.hideLoading()
+        console.error('加载认证信息失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }
+    },
+    navBack() {
+      uni.navigateBack({ delta: 1 })
+    },
+    chooseImage(side) {
+      uni.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: async (res) => {
+          const tempPath = res.tempFilePaths[0]
+          if (side === 'front') {
+            this.idCardFront = tempPath
+          } else {
+            this.idCardBack = tempPath
+          }
+          
+          try {
+            uni.showLoading({ title: '上传中...' })
+            const uploadRes = await uploadFile(tempPath)
+            if (side === 'front') {
+              this.idCardFrontOssId = uploadRes.data.ossId
+            } else {
+              this.idCardBackOssId = uploadRes.data.ossId
+            }
+            uni.hideLoading()
+            uni.showToast({ title: '上传成功', icon: 'success' })
+          } catch (err) {
+            uni.hideLoading()
+            console.error('上传身份证图片失败:', err)
+            uni.showToast({ title: '上传失败', icon: 'none' })
+          }
+        }
+      })
+    },
+    deleteImage(side) {
+      if (side === 'front') {
+        this.idCardFront = ''
+        this.idCardFrontOssId = ''
+      } else {
+        this.idCardBack = ''
+        this.idCardBackOssId = ''
+      }
+    },
+    toggleService(service) {
+      const index = this.selectedServices.indexOf(service)
+      if (index > -1) {
+        this.selectedServices.splice(index, 1)
+        delete this.qualifications[service]
+        delete this.qualOssIds[service]
+      } else {
+        this.selectedServices.push(service)
+        this.qualifications[service] = []
+        this.qualOssIds[service] = []
+      }
+      this.$forceUpdate()
+    },
+    chooseQualImage(service) {
+      uni.chooseImage({
+        count: 9,
+        sizeType: ['compressed'],
+        sourceType: ['album', 'camera'],
+        success: async (res) => {
+          if (!this.qualifications[service]) {
+            this.qualifications[service] = []
+            this.qualOssIds[service] = []
+          }
+          
+          for (const tempPath of res.tempFilePaths) {
+            this.qualifications[service].push(tempPath)
+            this.$forceUpdate()
+            
+            try {
+              uni.showLoading({ title: '上传中...' })
+              const uploadRes = await uploadFile(tempPath)
+              this.qualOssIds[service].push(uploadRes.data.ossId)
+              uni.hideLoading()
+            } catch (err) {
+              uni.hideLoading()
+              console.error('上传资质图片失败:', err)
+            }
+          }
+        }
+      })
+    },
+    deleteQualImage(service, index) {
+      this.qualifications[service].splice(index, 1)
+      if (this.qualOssIds[service]) {
+        this.qualOssIds[service].splice(index, 1)
+      }
+      this.$forceUpdate()
+    },
+    previewImage(service, index) {
+      uni.previewImage({
+        urls: this.qualifications[service],
+        current: index
+      })
+    },
+    async submitAuth() {
+      if (!this.idCardFront || !this.idCardBack) {
+        uni.showToast({ title: '请上传身份证正反面', icon: 'none' })
+        return
+      }
+      
+      if (this.selectedServices.length === 0) {
+        uni.showToast({ title: '请选择服务类型', icon: 'none' })
+        return
+      }
+      
+      for (const service of this.selectedServices) {
+        if (!this.qualifications[service] || this.qualifications[service].length === 0) {
+          uni.showToast({ title: `请上传${service}资质`, icon: 'none' })
+          return
+        }
+      }
+      
+      uni.showModal({
+        title: '提示',
+        content: '修改认证信息需要重新审核,审核期间无法接单,确定要继续吗?',
+        success: (res) => {
+          if (res.confirm) {
+            this.doSubmit()
+          }
+        }
+      })
+    },
+    async doSubmit() {
+      const allQualOssIds = []
+      Object.values(this.qualOssIds).forEach(ids => {
+        allQualOssIds.push(...ids)
+      })
+      
+      const submitData = {
+        idCardFront: this.idCardFrontOssId,
+        idCardBack: this.idCardBackOssId,
+        serviceTypes: JSON.stringify(this.selectedServices),
+        qualifications: JSON.stringify(allQualOssIds)
+      }
+      
+      try {
+        uni.showLoading({ title: '提交中...' })
+        await updateAuthInfo(submitData)
+        uni.hideLoading()
+        uni.showToast({ title: '提交成功,等待审核', icon: 'success', duration: 1500 })
+        setTimeout(() => {
+          uni.navigateBack({ delta: 1 })
+        }, 1500)
+      } catch (err) {
+        uni.hideLoading()
+        console.error('提交失败:', err)
+        uni.showToast({ title: '提交失败', icon: 'none' })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+page {
+  background-color: #F8F8F8;
+}
+
+.custom-header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 88rpx;
+  padding-top: var(--status-bar-height);
+  background-color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding-left: 30rpx;
+  padding-right: 30rpx;
+  box-sizing: content-box;
+  z-index: 100;
+}
+
+.header-placeholder {
+  height: calc(88rpx + var(--status-bar-height));
+}
+
+.back-icon {
+  width: 40rpx;
+  height: 40rpx;
+}
+
+.header-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.header-right {
+  width: 40rpx;
+}
+
+.edit-auth-container {
+  padding: 20rpx 30rpx;
+  padding-bottom: 180rpx;
+}
+
+.warning-tip {
+  background: linear-gradient(90deg, #FFF3E0 0%, #FFE0B2 100%);
+  border-radius: 16rpx;
+  padding: 24rpx 30rpx;
+  display: flex;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.warning-icon {
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.warning-text {
+  font-size: 26rpx;
+  color: #E65100;
+  flex: 1;
+}
+
+.section-card {
+  background-color: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 10rpx;
+}
+
+.section-subtitle {
+  font-size: 24rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 30rpx;
+}
+
+.id-card-row {
+  display: flex;
+  justify-content: space-between;
+}
+
+.id-card-upload {
+  width: 48%;
+  height: 200rpx;
+  border-radius: 16rpx;
+  position: relative;
+  overflow: hidden;
+  background-color: #E8F5E9;
+}
+
+.id-card-img {
+  width: 100%;
+  height: 100%;
+}
+
+.id-card-placeholder {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.placeholder-text {
+  font-size: 36rpx;
+  color: #4CAF50;
+  font-weight: bold;
+}
+
+.delete-btn {
+  position: absolute;
+  top: 10rpx;
+  right: 10rpx;
+  width: 40rpx;
+  height: 40rpx;
+  background-color: rgba(255, 87, 34, 0.9);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+}
+
+.corner-tag {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.5);
+  color: #fff;
+  font-size: 20rpx;
+  padding: 4rpx 10rpx;
+  border-top-left-radius: 8rpx;
+}
+
+.service-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.service-item {
+  background-color: #FFF8E1;
+  border-radius: 16rpx;
+  padding: 24rpx 30rpx;
+  margin-bottom: 20rpx;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.service-item:last-child {
+  margin-bottom: 0;
+}
+
+.service-name {
+  font-size: 28rpx;
+  color: #333;
+}
+
+.check-icon {
+  width: 40rpx;
+  height: 40rpx;
+  border: 2rpx solid #FF9800;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  font-size: 24rpx;
+}
+
+.check-icon.active {
+  background-color: #FF9800;
+}
+
+.qual-section {
+  margin-top: 30rpx;
+}
+
+.qual-section:first-child {
+  margin-top: 0;
+}
+
+.qual-title {
+  font-size: 26rpx;
+  color: #666;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.qual-upload-row {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.qual-item {
+  width: 150rpx;
+  height: 150rpx;
+  border-radius: 12rpx;
+  margin-right: 20rpx;
+  margin-bottom: 20rpx;
+  position: relative;
+  overflow: hidden;
+}
+
+.qual-img {
+  width: 100%;
+  height: 100%;
+}
+
+.qual-upload-btn {
+  width: 150rpx;
+  height: 150rpx;
+  border-radius: 12rpx;
+  border: 2rpx dashed #ccc;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: #F5F5F5;
+}
+
+.plus-icon {
+  font-size: 60rpx;
+  color: #999;
+}
+
+.empty-hint {
+  font-size: 26rpx;
+  color: #999;
+  text-align: center;
+  display: block;
+  padding: 40rpx 0;
+}
+
+.bottom-btn-area {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 20rpx 40rpx;
+  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+  background-color: #fff;
+  box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.05);
+  box-sizing: border-box;
+  z-index: 99;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 88rpx;
+  background-color: #FF5722;
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 44rpx;
+  border: none;
+  line-height: 88rpx;
+  text-align: center;
+  margin: 0 auto;
+  padding: 0;
+}
+</style>

+ 50 - 6
pages/mine/settings/auth/index.vue

@@ -15,7 +15,9 @@
             <view class="section-header">
                 <view class="orange-bar"></view>
                 <text class="section-title">身份认证</text>
-                <view class="tag-green">已认证</view>
+                <view class="tag-orange" v-if="authInfo.pendingAudit">认证中</view>
+                <view class="tag-green" v-else-if="authInfo.authId">已认证</view>
+                <view class="tag-gray" v-else>未认证</view>
             </view>
             <view class="info-row">
                 <text class="label">真实姓名</text>
@@ -75,8 +77,10 @@
 
         <!-- 修改按钮 -->
         <view class="bottom-btn-area">
-            <button class="action-btn" @click="editAuth">修改认证信息</button>
-            <text class="tips">修改认证信息需要重新审核,审核期间无法接单</text>
+            <button class="action-btn disabled" v-if="authInfo.pendingAudit" disabled>认证审核中...</button>
+            <button class="action-btn" v-else @click="editAuth">修改认证信息</button>
+            <text class="tips" v-if="authInfo.pendingAudit">认证信息正在审核中,请耐心等待</text>
+            <text class="tips" v-else>修改认证信息需要重新审核,审核期间无法接单</text>
         </view>
     </view>
 </template>
@@ -93,7 +97,9 @@ export default {
                 idCardFront: '',
                 idCardBack: '',
                 serviceTypes: [],
-                authQual: '',
+                authId: false,
+                authQual: false,
+                pendingAudit: false,
                 qualImages: []
             }
         }
@@ -117,7 +123,9 @@ export default {
                         idCardFront: res.data.idCardFrontUrl || '',
                         idCardBack: res.data.idCardBackUrl || '',
                         serviceTypes: res.data.serviceTypeList || [],
-                        authQual: res.data.authQual,
+                        authId: res.data.authId || false,
+                        authQual: res.data.authQual || false,
+                        pendingAudit: res.data.pendingAudit || false,
                         qualImages: res.data.qualImageUrls || []
                     }
                 }
@@ -131,7 +139,17 @@ export default {
             return idCard.substring(0, 4) + '**********' + idCard.substring(idCard.length - 4)
         },
         editAuth() {
-            uni.showToast({ title: '跳转修改认证页', icon: 'none' });
+            uni.showModal({
+                title: '提示',
+                content: '修改认证信息需要重新审核,审核期间无法接单,确定要继续吗?',
+                success: (res) => {
+                    if (res.confirm) {
+                        uni.navigateTo({
+                            url: '/pages/mine/settings/auth/edit'
+                        })
+                    }
+                }
+            })
         }
     }
 }
@@ -209,6 +227,27 @@ page {
     padding: 4rpx 12rpx;
     border-radius: 8rpx;
 }
+.tag-orange {
+    font-size: 24rpx;
+    color: #FF9800;
+    background-color: #FFF3E0;
+    padding: 4rpx 12rpx;
+    border-radius: 8rpx;
+}
+.tag-red {
+    font-size: 24rpx;
+    color: #F44336;
+    background-color: #FFEBEE;
+    padding: 4rpx 12rpx;
+    border-radius: 8rpx;
+}
+.tag-gray {
+    font-size: 24rpx;
+    color: #999;
+    background-color: #F5F5F5;
+    padding: 4rpx 12rpx;
+    border-radius: 8rpx;
+}
 
 .info-row {
     display: flex;
@@ -327,6 +366,11 @@ page {
     height: 88rpx;
     line-height: 88rpx;
 }
+.action-btn.disabled {
+    background-color: #F5F5F5;
+    color: #999;
+    border: 1px solid #E0E0E0;
+}
 .tips {
     display: block;
     font-size: 24rpx;