| | |
| | | <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"> |
| | |
| | | </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> |
| | |
| | | <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"> |
| | |
| | | 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年以上'], |
| | |
| | | .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() { |
| | |
| | | }, |
| | | setGender(event) { |
| | | this.form.gender = this.genderOptions[event.detail.value] |
| | | this.clearFieldError('gender') |
| | | }, |
| | | setAge(event) { |
| | | this.form.age = this.ageOptions[event.detail.value] |
| | |
| | | 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) |
| | | } |
| | | }) |
| | | }, |
| | |
| | | .toUpperCase() |
| | | .replace(/[^0-9X]/g, '') |
| | | .slice(0, 18) |
| | | this.clearFieldError('idNo') |
| | | }, |
| | | isValidIdCard(value) { |
| | | const idCardNo = value.trim().toUpperCase() |
| | |
| | | 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 |
| | |
| | | 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; |
| | | } |
| | |
| | | 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 { |
| | |
| | | text-align: center; |
| | | } |
| | | |
| | | .upload-errors { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8rpx; |
| | | } |
| | | |
| | | .certificate-head { |
| | | margin-bottom: 22rpx; |
| | | color: #1f3b5b; |