liujie
2026-09-09 39f6fca28e3f78abe48f08e474ec011abb990b36
mingdaojingjiang_guide_app/pages/register-profile/index.vue
@@ -5,30 +5,40 @@
    <scroll-view scroll-y class="form-scroll">
      <view class="form-wrap">
        <view class="field-block">
          <view class="field-head">姓名</view>
          <input v-model="form.name" class="plain-input" placeholder="请输入姓名" />
          <view class="field-head" :class="{ 'field-head-error': errors.name }">姓名</view>
          <input
            v-model="form.name"
            class="plain-input"
            :class="{ 'field-invalid': errors.name }"
            placeholder="请输入姓名"
            @input="clearFieldError('name')"
          />
          <text v-if="errors.name" class="field-error">{{ errors.name }}</text>
        </view>
        <view class="field-block">
          <view class="field-head">身份证号</view>
          <view class="field-head" :class="{ 'field-head-error': errors.idNo }">身份证号</view>
          <input
            v-model="form.idNo"
            class="plain-input"
            :class="{ 'field-invalid': errors.idNo }"
            type="idcard"
            maxlength="18"
            placeholder="请输入身份证号"
            @input="handleIdCardInput"
          />
          <text v-if="errors.idNo" class="field-error">{{ errors.idNo }}</text>
        </view>
        <view class="field-block">
          <view class="field-head">性别</view>
          <view class="field-head" :class="{ 'field-head-error': errors.gender }">性别</view>
          <picker :range="genderOptions" @change="setGender">
            <view class="select-row">
            <view class="select-row" :class="{ 'field-invalid': errors.gender }">
              <text :class="{ placeholder: !form.gender }">{{ form.gender || '请选择' }}</text>
              <image class="select-icon" src="/static/icons/chevron-down.svg" mode="aspectFit" />
            </view>
          </picker>
          <text v-if="errors.gender" class="field-error">{{ errors.gender }}</text>
        </view>
        <view class="field-block">
@@ -52,40 +62,45 @@
        </view>
        <view class="field-block">
          <view class="field-head">讲解队/个人</view>
          <view class="field-head" :class="{ 'field-head-error': errors.guideType }">讲解队/个人</view>
          <picker :range="teamOptions" range-key="name" @change="setTeam">
            <view class="select-row">
            <view class="select-row" :class="{ 'field-invalid': errors.guideType }">
              <text :class="{ placeholder: !form.teamName }">{{ form.teamName || '请选择' }}</text>
              <image class="select-icon" src="/static/icons/chevron-down.svg" mode="aspectFit" />
            </view>
          </picker>
          <text v-if="errors.guideType" class="field-error">{{ errors.guideType }}</text>
        </view>
        <view class="upload-section">
          <view class="field-head">上传身份证</view>
          <view class="field-head" :class="{ 'field-head-error': errors.idFront || errors.idBack }">上传身份证</view>
          <view class="upload-grid two">
            <view class="upload-item" @tap="chooseImage('idFront')">
            <view class="upload-item" :class="{ 'field-invalid': errors.idFront }" @tap="chooseImage('idFront')">
              <image v-if="form.idFront" class="upload-preview" :src="form.idFront" mode="aspectFill" />
              <image v-else class="plus-icon" src="/static/icons/plus.svg" mode="aspectFit" />
            </view>
            <view class="upload-item" @tap="chooseImage('idBack')">
            <view class="upload-item" :class="{ 'field-invalid': errors.idBack }" @tap="chooseImage('idBack')">
              <image v-if="form.idBack" class="upload-preview" :src="form.idBack" mode="aspectFill" />
              <image v-else class="plus-icon" src="/static/icons/plus.svg" mode="aspectFit" />
            </view>
          </view>
          <view class="upload-labels two">
            <text>正面</text>
            <text>反面</text>
            <text :class="{ 'upload-label-error': errors.idFront }">正面</text>
            <text :class="{ 'upload-label-error': errors.idBack }">反面</text>
          </view>
          <view class="upload-errors">
            <text v-if="errors.idFront" class="field-error">{{ errors.idFront }}</text>
            <text v-if="errors.idBack" class="field-error">{{ errors.idBack }}</text>
          </view>
        </view>
        <view class="upload-section certificate-section">
          <view class="certificate-head">
            <text>上传导游证</text>
            <text :class="{ 'field-head-error': errors.guideLicense }">上传导游证</text>
            <text>上传导游资格证(非必填)</text>
          </view>
          <view class="upload-grid two">
            <view class="upload-item" @tap="chooseImage('guideLicense')">
            <view class="upload-item" :class="{ 'field-invalid': errors.guideLicense }" @tap="chooseImage('guideLicense')">
              <image v-if="form.guideLicense" class="upload-preview" :src="form.guideLicense" mode="aspectFill" />
              <image v-else class="plus-icon" src="/static/icons/plus.svg" mode="aspectFit" />
            </view>
@@ -94,6 +109,7 @@
              <image v-else class="plus-icon" src="/static/icons/plus.svg" mode="aspectFit" />
            </view>
          </view>
          <text v-if="errors.guideLicense" class="field-error">{{ errors.guideLicense }}</text>
        </view>
        <button class="submit-button" :disabled="submitting" @tap="submit">
@@ -132,6 +148,15 @@
      phone: '',
      successDialogVisible: false,
      submitting: false,
      errors: {
        name: '',
        gender: '',
        guideType: '',
        idNo: '',
        idFront: '',
        idBack: '',
        guideLicense: ''
      },
      genderOptions: ['男', '女'],
      ageOptions: ['18-25岁', '26-35岁', '36-45岁', '46岁以上'],
      workYearOptions: ['1年以内', '1-3年', '3-5年', '5年以上'],
@@ -169,6 +194,35 @@
      .catch((error) => uni.showToast({ title: error.message, icon: 'none' }))
  },
  methods: {
    clearFieldError(field) {
      if (this.errors[field]) {
        this.errors[field] = ''
      }
    },
    resetFieldErrors() {
      Object.keys(this.errors).forEach((field) => {
        this.errors[field] = ''
      })
    },
    validateRequiredFields() {
      this.resetFieldErrors()
      const requiredFields = [
        ['name', !this.form.name.trim(), '请输入姓名'],
        ['gender', !this.form.gender, '请选择性别'],
        ['guideType', !this.form.guideType, '请选择讲解队或个人'],
        ['idNo', !this.form.idNo.trim(), '请输入身份证号'],
        ['idFront', !this.form.idFront, '请上传身份证正面'],
        ['idBack', !this.form.idBack, '请上传身份证反面'],
        ['guideLicense', !this.form.guideLicense, '请上传导游证']
      ]
      let firstError = ''
      requiredFields.forEach(([field, invalid, message]) => {
        if (!invalid) return
        this.errors[field] = message
        if (!firstError) firstError = message
      })
      return firstError
    },
    goBack() {
      uni.navigateBack({
        fail() {
@@ -178,6 +232,7 @@
    },
    setGender(event) {
      this.form.gender = this.genderOptions[event.detail.value]
      this.clearFieldError('gender')
    },
    setAge(event) {
      this.form.age = this.ageOptions[event.detail.value]
@@ -191,12 +246,14 @@
      this.form.guideType = team.guideType
      this.form.teamId = team.id || null
      this.form.teamName = team.name
      this.clearFieldError('guideType')
    },
    chooseImage(field) {
      uni.chooseImage({
        count: 1,
        success: (res) => {
          this.form[field] = res.tempFilePaths[0]
          this.clearFieldError(field)
        }
      })
    },
@@ -205,6 +262,7 @@
        .toUpperCase()
        .replace(/[^0-9X]/g, '')
        .slice(0, 18)
      this.clearFieldError('idNo')
    },
    isValidIdCard(value) {
      const idCardNo = value.trim().toUpperCase()
@@ -229,29 +287,15 @@
      return idCardNo[17] === checkCodes[sum % 11]
    },
    submit() {
      if (!this.form.name) {
        uni.showToast({ title: '请输入姓名', icon: 'none' })
        return
      }
      if (!this.form.gender) {
        uni.showToast({ title: '请选择性别', icon: 'none' })
        return
      }
      if (!this.form.guideType) {
        uni.showToast({ title: '请选择讲解队或个人', icon: 'none' })
        return
      }
      if (!this.form.idNo) {
        uni.showToast({ title: '请输入身份证号', icon: 'none' })
      const requiredError = this.validateRequiredFields()
      if (requiredError) {
        uni.showToast({ title: requiredError, icon: 'none' })
        return
      }
      this.form.idNo = this.form.idNo.trim().toUpperCase()
      if (!this.isValidIdCard(this.form.idNo)) {
        this.errors.idNo = '身份证号格式不正确,请检查末位校验码'
        uni.showToast({ title: '身份证号格式不正确,请检查末位校验码', icon: 'none' })
        return
      }
      if (!this.form.idFront || !this.form.idBack || !this.form.guideLicense) {
        uni.showToast({ title: '请上传必填证件', icon: 'none' })
        return
      }
      if (this.submitting) return
@@ -314,6 +358,23 @@
  font-size: 22rpx;
}
.field-head-error,
.upload-label-error {
  color: #e95b55;
}
.field-invalid {
  border-color: #f0746d;
  background: #fff9f8;
}
.field-error {
  display: block;
  margin-top: 9rpx;
  color: #e95b55;
  font-size: 23rpx;
}
.select-row {
  justify-content: space-between;
}
@@ -345,9 +406,15 @@
  justify-content: center;
  width: 94rpx;
  height: 94rpx;
  box-sizing: border-box;
  border-radius: 12rpx;
  background: #e6e6e6;
  overflow: hidden;
}
.upload-item.field-invalid {
  border: 2rpx solid #f0746d;
  background: #fff9f8;
}
.plus-icon {
@@ -367,6 +434,12 @@
  text-align: center;
}
.upload-errors {
  display: flex;
  flex-direction: column;
  gap: 8rpx;
}
.certificate-head {
  margin-bottom: 22rpx;
  color: #1f3b5b;