Files
delivery-uniapp/pages/registered/registerRiders.vue

691 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<s-layout title="注册骑手" class="set-userinfo-wrap">
<view class="page">
<up-form ref="riderForm" :model="form" :rules="rules" labelPosition="left" labelWidth="90">
<view class="section-title">基础信息</view>
<up-form-item label="真实姓名" prop="realName" :required="true">
<up-input v-model="form.realName" placeholder="请输入您的姓名" />
</up-form-item>
<up-form-item label="身份证号" prop="idNo" :required="true">
<up-input v-model="form.idNo" placeholder="数字开头18位号码" maxlength="18" />
</up-form-item>
<up-form-item label="生效日期" prop="birthDate" :required="true">
<up-datetime-picker hasInput v-model="form.birthDate" mode="date" placeholder="请选择身份证生效日期" />
</up-form-item>
<up-form-item label="失效日期" prop="expiryDate" :required="true">
<view class="expiry-row">
<up-radio-group v-model="form.expiryMode" direction="horizontal">
<up-radio name="long" label="长期有效"></up-radio>
<up-radio name="date" label="选择失效日期"></up-radio>
</up-radio-group>
<up-datetime-picker v-if="form.expiryMode === 'date'" hasInput
v-model="form.expiryDate" mode="date" placeholder="选择失效日期" />
</view>
</up-form-item>
<up-form-item label="性别" prop="gender" :required="true">
<up-radio-group v-model="form.gender" direction="horizontal">
<up-radio name="male" label="男"></up-radio>
<up-radio name="female" label="女"></up-radio>
</up-radio-group>
</up-form-item>
<up-form-item label="紧急联系人姓名" prop="emergencyName" :required="true">
<up-input v-model="form.emergencyName" placeholder="请输入" />
</up-form-item>
<up-form-item label="紧急联系人手机" prop="emergencyPhone" :required="true">
<up-input v-model="form.emergencyPhone" placeholder="请输入" type="tel" maxlength="11" />
</up-form-item>
<up-form-item label="上传身份证正反面" prop="idImages">
<view class="upload-row">
<view class="upload-box">
<up-upload :max-count="1" :show-file-list="false" @change="onUploadFront">
<view class="upload-placeholder" v-if="!frontImage">
<up-icon name="+" />
</view>
<up-image v-else :src="frontImage" mode="aspectFill" class="thumb" />
</up-upload>
<text class="hint">国徽面</text>
</view>
<view class="upload-box">
<up-upload :max-count="1" :show-file-list="false" @change="onUploadBack">
<view class="upload-placeholder" v-if="!backImage">
<up-icon name="+" />
</view>
<up-image v-else :src="backImage" mode="aspectFill" class="thumb" />
</up-upload>
<text class="hint">人像面</text>
</view>
</view>
</up-form-item>
<view class="section-title">接单选择</view>
<up-form-item label="职业身份" prop="occupation" :required="true">
<up-radio-group v-model="form.occupation" direction="horizontal">
<up-radio name="student" label="在校学生"></up-radio>
<up-radio name="worker" label="社会人员/职工"></up-radio>
</up-radio-group>
</up-form-item>
<!-- 学生视图兼职意愿可兼职时段健康证 -->
<template v-if="form.occupation === 'student'">
<up-form-item label="兼职意愿" prop="partTimeIntent" :required="true">
<up-picker hasInput :columns="partTimeOptions" v-model="partTimeLabel" @confirm="onPartTimeConfirm"></up-picker>
</up-form-item>
<up-form-item label="请选择可兼职时段" prop="partTimePeriods">
<view class="choose-row" @click="timeShow1 = true">
<text class="muted">{{ partTimePeriodsLabel }}</text>
<text class="status" :class="{empty: form.partTimePeriods.length===0}">{{ form.partTimePeriods.length===0 ? '待完善 >' : '已完善' }}</text>
</view>
</up-form-item>
<up-popup :show="timeShow1" mode="bottom" @close="timeShow1 = false">
<view class="popup-content">
<view class="popup-header">
<text>选择可兼职时段</text>
</view>
<view class="popup-body">
<!-- 网格每行为一天三段时段按钮可切换选中 -->
<view class="time-grid">
<view class="time-row" v-for="(day, dayIndex) in days" :key="dayIndex">
<view class="day-label">{{ day }}</view>
<view class="slots">
<view
class="slot-btn"
:class="{selected: isSlotSelected(dayIndex, 0)}"
@click="toggleSlot(dayIndex, 0)"
key="m"
>08:00~13:00</view>
<view
class="slot-btn"
:class="{selected: isSlotSelected(dayIndex, 1)}"
@click="toggleSlot(dayIndex, 1)"
key="a"
>13:00~17:30</view>
<view
class="slot-btn"
:class="{selected: isSlotSelected(dayIndex, 2)}"
@click="toggleSlot(dayIndex, 2)"
key="e"
>17:00~22:30</view>
</view>
</view>
</view>
</view>
<view class="popup-footer">
<up-button plain @click="onCancelTime">取消</up-button>
<up-button type="primary" @click="onSaveTime">保存</up-button>
</view>
</view>
</up-popup>
</template>
<!-- 社会人员/职工视图类别意向城市健康证 -->
<template v-else>
<up-form-item label="类别" prop="category" :required="true">
<up-picker hasInput :columns="categoryOptions" @confirm="onCategoryConfirm">
<template #trigger>
<up-input readonly v-model="categoryLabel" placeholder="请选择类别" />
</template>
</up-picker>
</up-form-item>
<up-form-item label="意向城市" prop="city" :required="true">
<up-input readonly v-model="cityLabel" placeholder="请选择" @tap="regionShow = true" />
</up-form-item>
</template>
<!-- 健康证学生/社会人员皆可上传 -->
<up-form-item label="健康证" prop="healthCert">
<view class="health-row" @click="openHealthPopup">
<view class="health-left">上传健康证</view>
<view class="health-right">
<up-icon name="arrow-right" color="#6c6c6c" size="21"></up-icon>
</view>
</view>
</up-form-item>
<view class="submit-row">
<up-button type="primary" @click="onSubmit">保存并下一步完善工资结算信息</up-button>
</view>
</up-form>
</view>
<!-- 健康证信息弹框 -->
<up-popup :show="popupShow" mode="bottom" @close="onCancelHealth" :round="16" safeAreaInsetBottom>
<view class="health-popup">
<view class="health-popup-header">
<text class="title">上传健康证</text>
</view>
<view class="health-popup-body">
<view class="field-row">
<text class="label">编号</text>
<up-input v-model="healthNumber" placeholder="请输入健康证编号" class="field-input" />
</view>
<view class="field-row">
<text class="label">类别</text>
<up-input v-model="healthCategory" placeholder="请输入健康证类别" class="field-input" />
</view>
<view class="field-row date-row">
<text class="label">有效日期</text>
<up-datetime-picker hasInput v-model="healthValidStart" mode="date" placeholder="年/月/日" class="date-input" />
<text class="dash"> </text>
<up-datetime-picker hasInput v-model="healthValidEnd" mode="date" placeholder="年/月/日" class="date-input" />
</view>
<view class="upload-list">
<text class="upload-title">上传健康证</text>
<view class="upload-row-inner">
<view v-for="(img, idx) in healthImages" :key="idx" class="upload-item">
<up-image :src="img" class="upload-thumb" mode="aspectFill" />
<view class="remove-btn" @click.stop="removeHealthImage(idx)">×</view>
</view>
<up-upload :max-count="1" :show-file-list="false" @change="onUploadHealth">
<view class="upload-add" v-if="healthImages.length < 4">
<up-icon name="+" />
</view>
</up-upload>
</view>
</view>
</view>
<view class="health-popup-footer">
<up-button plain @click="onCancelHealth">取消</up-button>
<up-button type="primary" class="save-btn" @click="onSaveHealth">保存</up-button>
</view>
</view>
</up-popup>
<su-region-picker :show="regionShow" @confirm="onRegionConfirm" @cancel="regionShow = false" />
</s-layout>
</template>
<script setup>
import { reactive, ref, computed, watch, onBeforeMount } from 'vue'
import AreaApi from '@/sheep/api/system/area';
// import SuRegionPicker from 'sheep/ui/su-region-picker/su-region-picker.vue'
const form = reactive({
realName: '',
idNo: '',
birthDate: '',
expiryMode: 'long',
expiryDate: '',
gender: 'male',
emergencyName: '',
emergencyPhone: '',
idImages: [],
// 接单选择相关字段
occupation: 'student',
partTimeIntent: '',
partTimePeriods: [],
healthCert: '',
// 健康证详情字段(弹窗保存到这里)
healthCertNumber: '',
healthCertCategory: '',
healthCertValidStart: '',
healthCertValidEnd: '',
category: '',
city: '',
})
const timeShow1 = ref(false)
const partTimeOptions = reactive([
['长期至少1学期', '非长期(临时/偶尔兼职)']
])
const partTimeLabel = ref([])
const categoryOptions = [
['全职', '兼职']
]
const categoryLabel = ref([])
const cityOptions = [
{ text: '请选择城市', value: '' },
{ text: '北京市', value: 'beijing' },
{ text: '上海市', value: 'shanghai' },
]
const cityLabel = ref('')
const regionShow = ref(false)
// 可选时段网格数据与选择状态
const days = ['周日','周一','周二','周三','周四','周五','周六']
const timeSlots = ['08:00~13:00','13:00~17:30','17:00~22:30']
// 使用二维布尔数组表示选择状态selectedGrid[dayIndex][slotIndex] = true/false
const selectedGrid = reactive(Array.from({ length: days.length }, () => [false, false, false]))
// 弹窗临时副本,打开时拷贝 selectedGrid 到 tempSelected用于取消恢复
const tempSelected = reactive(Array.from({ length: days.length }, () => [false, false, false]))
const popupShow = ref(false)
function isSlotSelected(dayIndex, slotIndex) {
return tempSelected[dayIndex][slotIndex]
}
function toggleSlot(dayIndex, slotIndex) {
tempSelected[dayIndex][slotIndex] = !tempSelected[dayIndex][slotIndex]
}
function onCancelTime() {
// 恢复原选中状态并关闭弹窗
for (let i = 0; i < days.length; i++) {
for (let j = 0; j < timeSlots.length; j++) {
tempSelected[i][j] = selectedGrid[i][j]
}
}
timeShow1.value = false
}
function onSaveTime() {
// 将 tempSelected 同步到 selectedGrid 和 form.partTimePeriods保存为可读文本
const selections = []
for (let i = 0; i < days.length; i++) {
for (let j = 0; j < timeSlots.length; j++) {
selectedGrid[i][j] = tempSelected[i][j]
if (tempSelected[i][j]) {
selections.push(`${days[i]} ${timeSlots[j]}`)
}
}
}
form.partTimePeriods = selections
timeShow1.value = false
}
// 打开弹窗时将当前选择拷贝到 tempSelected
watch(timeShow1, (val) => {
if (val) {
for (let i = 0; i < days.length; i++) {
for (let j = 0; j < timeSlots.length; j++) {
tempSelected[i][j] = selectedGrid[i][j]
}
}
}
})
const rules = {
realName: [{ required: true, message: '请输入真实姓名' }],
idNo: [
{ required: true, message: '请输入身份证号' },
{ pattern: /^[0-9A-Za-z]{15,18}$/, message: '请输入正确的身份证号' },
],
birthDate: [{ required: true, message: '请选择生效日期' }],
expiryDate: [
{
validator(value) {
if (form.expiryMode === 'date' && !value) {
return false
}
return true
},
message: '请选择失效日期',
},
],
gender: [{ required: true, message: '请选择性别' }],
emergencyName: [{ required: true, message: '请输入紧急联系人姓名' }],
emergencyPhone: [
{ required: true, message: '请输入紧急联系人手机' },
{ pattern: /^1\d{10}$/, message: '请输入正确的手机号码' },
],
}
const riderForm = ref(null)
const frontImage = ref('')
const backImage = ref('')
const healthCert = ref('')
// 健康证弹窗临时状态
const healthImages = reactive([])
const healthNumber = ref('')
const healthCategory = ref('')
const healthValidStart = ref('')
const healthValidEnd = ref('')
const partTimePeriodsLabel = computed(() => {
return form.partTimePeriods.length ? form.partTimePeriods.join('、') : ''
})
function onUploadFront(event) {
const file = Array.isArray(event) ? event[0] : (event.detail || event)
const url = file?.url || file?.path || file?.thumb || ''
frontImage.value = url
form.idImages = [url, form.idImages[1] || '']
}
function onUploadBack(event) {
const file = Array.isArray(event) ? event[0] : (event.detail || event)
const url = file?.url || file?.path || file?.thumb || ''
backImage.value = url
form.idImages = [form.idImages[0] || '', url]
}
function onUploadHealth(event) {
const file = Array.isArray(event) ? event[0] : (event.detail || event)
const url = file?.url || file?.path || file?.thumb || ''
if (url) {
// 限制最多 4 张预览图
if (healthImages.length < 4) {
healthImages.push(url)
}
healthCert.value = url
// 保持兼容form.healthCert 存首张图片(若需要可改为数组)
form.healthCert = url
}
}
function onPartTimeConfirm(selected) {
const first = Array.isArray(selected) ? selected[0] : selected
if (first && (first.value || first.text)) {
form.partTimeIntent = first.value || first.text
partTimeLabel.value = first.text || first.value
}
}
function onCategoryConfirm(selected) {
const first = Array.isArray(selected) ? selected[0] : selected
if (first && (first.value || first.text)) {
form.category = first.value || first.text
categoryLabel.value = first.text || first.value
}
}
function onCityConfirm(selected) {
const first = Array.isArray(selected) ? selected[0] : selected
if (first && (first.value || first.text)) {
form.city = first.value || first.text
cityLabel.value = first.text || first.value
}
}
function onRegionConfirm(result) {
// result: { province_name, province_id, city_name, city_id, district_name, district_id }
form.city = result
cityLabel.value = `${result.province_name || ''} ${result.city_name || ''} ${result.district_name || ''}`.trim()
regionShow.value = false
}
function openHealthPopup() {
// 从表单恢复到弹窗临时状态
healthImages.splice(0, healthImages.length)
if (form.healthCert) {
healthImages.push(form.healthCert)
}
healthNumber.value = form.healthCertNumber || ''
healthCategory.value = form.healthCertCategory || ''
healthValidStart.value = form.healthCertValidStart || ''
healthValidEnd.value = form.healthCertValidEnd || ''
popupShow.value = true
}
function removeHealthImage(index) {
if (index >= 0 && index < healthImages.length) {
healthImages.splice(index, 1)
}
}
function onCancelHealth() {
// 直接关闭弹窗,放弃临时更改
popupShow.value = false
}
function onSaveHealth() {
// 保存弹窗数据回表单
form.healthCert = healthImages.length ? healthImages[0] : ''
healthCert.value = form.healthCert
form.healthCertNumber = healthNumber.value
form.healthCertCategory = healthCategory.value
form.healthCertValidStart = healthValidStart.value
form.healthCertValidEnd = healthValidEnd.value
popupShow.value = false
}
async function onSubmit() {
try {
await riderForm.value.validate()
console.log('提交表单', JSON.parse(JSON.stringify(form)))
// 将已填写的注册信息临时存储,供结算页继续使用
try {
uni.setStorageSync('riderFormData', JSON.parse(JSON.stringify(form)))
} catch (err) {
console.warn('存储注册信息失败', err)
}
// 跳转到工资结算账户信息页面
uni.navigateTo({ url: '/pages/registered/accountInfo' })
} catch (e) {
console.warn('表单校验未通过', e)
}
}
onBeforeMount(() => {
if (!!uni.getStorageSync('areaData')) {
return;
}
// 提前加载省市区数据
AreaApi.getAreaTree().then((res) => {
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
});
</script>
<style lang="scss" scoped>
.page {
padding: 38rpx;
background: #fff;
}
.section-title {
padding: 10px 0;
color: #666;
font-weight: 600;
}
.expiry-row {
display: flex;
flex-direction: column;
gap: 8px;
}
.upload-row {
display: flex;
gap: 12px;
}
.upload-box {
display: flex;
flex-direction: column;
align-items: center;
}
.upload-placeholder {
width: 88px;
height: 60px;
border: 1px dashed #ddd;
display: flex;
align-items: center;
justify-content: center;
}
.thumb {
width: 88px;
height: 60px;
border-radius: 4px;
}
.hint {
margin-top: 6px;
color: #999;
font-size: 12px;
}
.submit-row {
margin-top: 20px;
display: flex;
justify-content: center;
}
.time-grid {
padding: 10px 0;
}
.time-row {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.day-label {
width: 90rpx;
color: #333;
}
.slots {
display: flex;
gap: 10px;
flex: 1;
}
.slot-btn {
padding: 8px 12px;
border-radius: 6px;
background: #f5f5f5;
color: #333;
font-size: 24rpx;
}
.slot-btn.selected {
background: #09aaff;
color: #fff;
}
.popup-footer {
display: flex;
gap: 10px;
padding: 12px;
justify-content: space-between;
}
.popup-content {
padding: 50rpx 20rpx 20rpx;
}
.popup-body {
padding: 10rpx 15rpx 20rpx;
}
/* 健康证相关样式 */
.health-row {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding-right: 10rpx;
}
.health-left {
color: #333;
}
.health-right {
display: flex;
align-items: center;
gap: 10rpx;
}
.health-thumbs {
display: flex;
gap: 10rpx;
align-items: center;
}
.health-thumb-wrap {
width: 88px;
height: 88px;
}
.health-thumb {
width: 88px;
height: 88px;
border-radius: 6px;
}
.health-add {
width: 88px;
height: 88px;
border: 1px dashed #ddd;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: #999;
}
.health-popup {
padding: 30rpx 30rpx 30rpx;
background: #fff;
}
.health-popup-header .title {
font-weight: 700;
font-size: 32rpx;
text-align: center;
margin-bottom: 10px;
}
.health-popup-body {
padding: 10rpx 0 20rpx;
}
.field-row {
display: flex;
align-items: center;
gap: 38rpx;
margin-bottom: 12px;
}
.label {
width: 90rpx;
color: #666;
}
.field-input {
flex: 1;
}
.date-row .date-input {
width: 35%;
}
.dash {
width: 10rpx;
text-align: center;
color: #999;
}
.upload-list {
margin-top: 10px;
}
.upload-title {
color: #333;
margin-bottom: 8px;
display: block;
}
.upload-row-inner {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.upload-item {
position: relative;
width: 88px;
height: 88px;
}
.upload-thumb {
width: 88px;
height: 88px;
border-radius: 6px;
}
.remove-btn {
position: absolute;
top: -6px;
right: -6px;
background: rgba(0,0,0,0.6);
color: #fff;
width: 24px;
height: 24px;
border-radius: 12px;
text-align: center;
line-height: 24px;
font-size: 18px;
}
.upload-add {
width: 88px;
height: 88px;
border: 1px dashed #ddd;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
color: #999;
}
.health-popup-footer {
display: flex;
justify-content: space-between;
padding-top: 16px;
gap: 12px;
}
.save-btn {
width: 240rpx;
align-self: center;
}
</style>