企业信息补全页面开发
This commit is contained in:
632
pages/complete-info/company-info.vue
Normal file
632
pages/complete-info/company-info.vue
Normal file
@@ -0,0 +1,632 @@
|
||||
<template>
|
||||
<AppLayout title="企业信息">
|
||||
<view class="company-info-container">
|
||||
<!-- 头部信息 -->
|
||||
<view class="header-info">
|
||||
<view class="progress-text">企业信息完善度</view>
|
||||
<view class="progress-value">{{ completionPercentage }}%</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单内容 -->
|
||||
<view class="form-content">
|
||||
<!-- 企业名称 -->
|
||||
<view class="form-item">
|
||||
<view class="label">企业名称</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model="formData.companyName"
|
||||
placeholder="请输入企业名称"
|
||||
@input="updateCompletion"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 统一社会信用代码 -->
|
||||
<view class="form-item">
|
||||
<view class="label">统一社会信用代码</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model="formData.socialCreditCode"
|
||||
placeholder="请输入统一社会信用代码"
|
||||
maxlength="18"
|
||||
@input="updateCompletion"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 企业注册地点 -->
|
||||
<view class="form-item clickable" @click="selectLocation">
|
||||
<view class="label">企业注册地点</view>
|
||||
<view class="input-content">
|
||||
<text class="input-text" :class="{ placeholder: !formData.registeredAddress }">
|
||||
{{ formData.registeredAddress || '请选择注册地点' }}
|
||||
</text>
|
||||
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业信息介绍 -->
|
||||
<view class="form-item clickable" @click="editCompanyIntro">
|
||||
<view class="label">企业信息介绍</view>
|
||||
<view class="input-content">
|
||||
<text class="input-text intro-text" :class="{ placeholder: !formData.companyIntro }">
|
||||
{{ formData.companyIntro || '请输入企业介绍' }}
|
||||
</text>
|
||||
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 企业法人姓名 -->
|
||||
<view class="form-item">
|
||||
<view class="label">企业法人姓名</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model="formData.legalPersonName"
|
||||
placeholder="请输入法人姓名"
|
||||
@input="updateCompletion"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 本地重点发展产业 -->
|
||||
<view class="form-item clickable" @click="selectIndustry">
|
||||
<view class="label">本地重点发展产业</view>
|
||||
<view class="input-content">
|
||||
<text class="input-text" :class="{ placeholder: !formData.industryType }">
|
||||
{{ formData.industryType || '请选择产业类型' }}
|
||||
</text>
|
||||
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 是否是本地企业 -->
|
||||
<view class="form-item clickable" @click="selectLocalCompany">
|
||||
<view class="label">是否是本地企业</view>
|
||||
<view class="input-content">
|
||||
<text class="input-text" :class="{ placeholder: formData.isLocalCompany === null }">
|
||||
{{ formData.isLocalCompany === null ? '请选择' : (formData.isLocalCompany ? '是' : '否') }}
|
||||
</text>
|
||||
<uni-icons type="arrowright" size="16" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系人信息列表 -->
|
||||
<view class="contact-section">
|
||||
<view class="section-title">联系人信息</view>
|
||||
|
||||
<!-- 每个联系人作为一个分组 -->
|
||||
<view
|
||||
class="contact-group"
|
||||
v-for="(contact, index) in formData.contacts"
|
||||
:key="index"
|
||||
>
|
||||
<view class="group-header">联系人{{ index + 1 }}</view>
|
||||
<view class="form-item">
|
||||
<view class="label">联系人姓名</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model="contact.name"
|
||||
placeholder="请输入联系人姓名"
|
||||
@input="updateCompletion"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="label">联系人电话</view>
|
||||
<input
|
||||
class="input-field"
|
||||
v-model="contact.phone"
|
||||
placeholder="请输入联系人电话"
|
||||
@input="updateCompletion"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加联系人按钮 -->
|
||||
<view class="add-contact-btn" @click="addContact" v-if="formData.contacts.length < 3">
|
||||
<uni-icons type="plus" size="20" color="#256BFA"></uni-icons>
|
||||
<text>添加联系人</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="cancel-btn" @click="cancel">取消</button>
|
||||
<button class="confirm-btn" @click="confirm">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 弹窗组件 -->
|
||||
<uni-popup ref="popup" type="center">
|
||||
<view class="popup-content">
|
||||
<view class="popup-title">{{ popupTitle }}</view>
|
||||
<input
|
||||
v-if="popupType === 'text'"
|
||||
class="popup-input"
|
||||
v-model="popupValue"
|
||||
:placeholder="popupPlaceholder"
|
||||
/>
|
||||
<textarea
|
||||
v-if="popupType === 'textarea'"
|
||||
class="popup-textarea"
|
||||
v-model="popupValue"
|
||||
:placeholder="popupPlaceholder"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<view class="popup-actions">
|
||||
<button class="popup-cancel" @click="closePopup">取消</button>
|
||||
<button class="popup-confirm" @click="confirmPopup">确定</button>
|
||||
</view>
|
||||
</view>
|
||||
</uni-popup>
|
||||
|
||||
<!-- 地址选择器 -->
|
||||
<area-cascade-picker ref="areaPicker"></area-cascade-picker>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, inject } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import AreaCascadePicker from '@/components/area-cascade-picker/area-cascade-picker.vue'
|
||||
|
||||
const { $api } = inject('globalFunction')
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
companyName: '',
|
||||
socialCreditCode: '',
|
||||
registeredAddress: '',
|
||||
registeredAddressName: '',
|
||||
longitude: null,
|
||||
latitude: null,
|
||||
companyIntro: '',
|
||||
legalPersonName: '',
|
||||
industryType: '', // 是否是本地重点发展产业
|
||||
isLocalCompany: null, // 是否是本地企业 (true/false/null)
|
||||
contacts: [
|
||||
{ name: '', phone: '' }
|
||||
]
|
||||
})
|
||||
|
||||
// 弹窗相关
|
||||
const popup = ref(null)
|
||||
const popupTitle = ref('')
|
||||
const popupType = ref('text') // text | textarea
|
||||
const popupValue = ref('')
|
||||
const popupPlaceholder = ref('')
|
||||
const currentEditField = ref('')
|
||||
|
||||
// 地址选择器引用
|
||||
const areaPicker = ref(null)
|
||||
|
||||
// 产业类型选项数据
|
||||
const industryOptions = [
|
||||
'人工智能',
|
||||
'生物医药',
|
||||
'新能源',
|
||||
'高端装备制造',
|
||||
'其他'
|
||||
]
|
||||
|
||||
// 完成度计算
|
||||
const completionPercentage = computed(() => {
|
||||
const fields = [
|
||||
formData.companyName,
|
||||
formData.socialCreditCode,
|
||||
formData.registeredAddress,
|
||||
formData.companyIntro,
|
||||
formData.legalPersonName,
|
||||
formData.industryType,
|
||||
formData.isLocalCompany !== null ? 'filled' : ''
|
||||
]
|
||||
|
||||
// 检查联系人信息
|
||||
const hasContact = formData.contacts.some(contact => contact.name && contact.phone)
|
||||
|
||||
const filledFields = fields.filter(field => field && field.trim()).length + (hasContact ? 1 : 0)
|
||||
const totalFields = fields.length + 1
|
||||
|
||||
return Math.round((filledFields / totalFields) * 100)
|
||||
})
|
||||
|
||||
// 更新完成度
|
||||
const updateCompletion = () => {
|
||||
// 完成度会自动通过computed更新
|
||||
}
|
||||
|
||||
// 选择注册地点
|
||||
const selectLocation = () => {
|
||||
// 打开五级联动地址选择器
|
||||
areaPicker.value?.open({
|
||||
title: '选择企业注册地点',
|
||||
maskClick: true,
|
||||
success: (addressData) => {
|
||||
// addressData 包含: address, province, city, district, street, community
|
||||
formData.registeredAddress = addressData.address
|
||||
formData.registeredAddressName = addressData.address
|
||||
|
||||
// 可以保存详细的地址信息
|
||||
formData.provinceCode = addressData.province?.code
|
||||
formData.provinceName = addressData.province?.name
|
||||
formData.cityCode = addressData.city?.code
|
||||
formData.cityName = addressData.city?.name
|
||||
formData.districtCode = addressData.district?.code
|
||||
formData.districtName = addressData.district?.name
|
||||
formData.streetCode = addressData.street?.code
|
||||
formData.streetName = addressData.street?.name
|
||||
formData.communityCode = addressData.community?.code
|
||||
formData.communityName = addressData.community?.name
|
||||
|
||||
updateCompletion()
|
||||
|
||||
$api.msg('地址选择成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理地图选择返回的地址
|
||||
const handleLocationSelected = (locationData) => {
|
||||
formData.registeredAddress = locationData.address
|
||||
formData.registeredAddressName = locationData.name
|
||||
formData.longitude = locationData.longitude
|
||||
formData.latitude = locationData.latitude
|
||||
updateCompletion()
|
||||
}
|
||||
|
||||
// 编辑企业介绍
|
||||
const editCompanyIntro = () => {
|
||||
currentEditField.value = 'companyIntro'
|
||||
popupTitle.value = '企业信息介绍'
|
||||
popupType.value = 'textarea'
|
||||
popupValue.value = formData.companyIntro
|
||||
popupPlaceholder.value = '请输入企业介绍'
|
||||
popup.value?.open()
|
||||
}
|
||||
|
||||
|
||||
// 选择产业类型
|
||||
const selectIndustry = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: industryOptions,
|
||||
success: (res) => {
|
||||
formData.industryType = industryOptions[res.tapIndex]
|
||||
updateCompletion()
|
||||
$api.msg('产业类型选择成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择是否是本地企业
|
||||
const selectLocalCompany = () => {
|
||||
uni.showActionSheet({
|
||||
itemList: ['是', '否'],
|
||||
success: (res) => {
|
||||
formData.isLocalCompany = res.tapIndex === 0
|
||||
updateCompletion()
|
||||
$api.msg('选择成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 添加联系人
|
||||
const addContact = () => {
|
||||
if (formData.contacts.length < 3) {
|
||||
formData.contacts.push({ name: '', phone: '' })
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const closePopup = () => {
|
||||
popup.value?.close()
|
||||
popupValue.value = ''
|
||||
}
|
||||
|
||||
// 确认弹窗
|
||||
const confirmPopup = () => {
|
||||
const field = currentEditField.value
|
||||
|
||||
if (field === 'companyIntro') {
|
||||
formData.companyIntro = popupValue.value
|
||||
}
|
||||
|
||||
updateCompletion()
|
||||
closePopup()
|
||||
}
|
||||
|
||||
// 取消
|
||||
const cancel = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 确认提交
|
||||
const confirm = () => {
|
||||
// 验证必填字段
|
||||
if (!formData.companyName.trim()) {
|
||||
$api.msg('请输入企业名称')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.socialCreditCode.trim()) {
|
||||
$api.msg('请输入统一社会信用代码')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.registeredAddress.trim()) {
|
||||
$api.msg('请选择注册地点')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.companyIntro.trim()) {
|
||||
$api.msg('请输入企业介绍')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.legalPersonName.trim()) {
|
||||
$api.msg('请输入法人姓名')
|
||||
return
|
||||
}
|
||||
|
||||
if (!formData.industryType.trim()) {
|
||||
$api.msg('请选择产业类型')
|
||||
return
|
||||
}
|
||||
|
||||
if (formData.isLocalCompany === null) {
|
||||
$api.msg('请选择是否是本地企业')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证至少有一个联系人
|
||||
const hasValidContact = formData.contacts.some(contact =>
|
||||
contact.name.trim() && contact.phone.trim()
|
||||
)
|
||||
|
||||
if (!hasValidContact) {
|
||||
$api.msg('请至少添加一个联系人信息')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证联系人电话格式
|
||||
const phoneRegex = /^1[3-9]\d{9}$/
|
||||
for (let contact of formData.contacts) {
|
||||
if (contact.name.trim() && contact.phone.trim()) {
|
||||
if (!phoneRegex.test(contact.phone)) {
|
||||
$api.msg('请输入正确的手机号码')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交数据
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
|
||||
// 这里调用后端接口保存企业信息
|
||||
const submitData = {
|
||||
...formData,
|
||||
contacts: formData.contacts.filter(contact => contact.name.trim() && contact.phone.trim())
|
||||
}
|
||||
|
||||
$api.createRequest('/app/company/complete-info', submitData, 'post')
|
||||
.then((resData) => {
|
||||
uni.hideLoading()
|
||||
$api.msg('企业信息保存成功')
|
||||
|
||||
// 跳转到首页或企业相关页面
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading()
|
||||
$api.msg(err.msg || '保存失败,请重试')
|
||||
})
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
// 可以在这里加载已有的企业信息
|
||||
console.log('企业信息补全页面加载')
|
||||
})
|
||||
|
||||
// 暴露方法给其他页面调用
|
||||
defineExpose({
|
||||
handleLocationSelected
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.company-info-container
|
||||
min-height: 100vh
|
||||
background: #f5f5f5
|
||||
padding-bottom: 120rpx
|
||||
|
||||
.header-info
|
||||
background: #fff
|
||||
padding: 40rpx 32rpx
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
margin-bottom: 20rpx
|
||||
|
||||
.progress-text
|
||||
font-size: 32rpx
|
||||
color: #000000
|
||||
font-weight: 500
|
||||
|
||||
.progress-value
|
||||
font-size: 32rpx
|
||||
color: #256BFA
|
||||
font-weight: 600
|
||||
|
||||
.form-content
|
||||
background: #fff
|
||||
margin-bottom: 20rpx
|
||||
|
||||
.form-item
|
||||
padding: 32rpx
|
||||
border-bottom: 1rpx solid #f0f0f0
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
|
||||
&:last-child
|
||||
border-bottom: none
|
||||
|
||||
&.clickable
|
||||
cursor: pointer
|
||||
|
||||
.label
|
||||
font-size: 28rpx
|
||||
color: #000000
|
||||
min-width: 200rpx
|
||||
|
||||
.input-field
|
||||
flex: 1
|
||||
font-size: 28rpx
|
||||
color: #333
|
||||
text-align: right
|
||||
|
||||
&::placeholder
|
||||
color: #999
|
||||
font-size: 26rpx
|
||||
|
||||
.input-content
|
||||
flex: 1
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
align-items: center
|
||||
|
||||
.input-text
|
||||
font-size: 28rpx
|
||||
color: #333
|
||||
flex: 1
|
||||
text-align: right
|
||||
|
||||
&.placeholder
|
||||
color: #999
|
||||
font-size: 26rpx
|
||||
|
||||
&.intro-text
|
||||
max-height: 80rpx
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.contact-section
|
||||
margin-top: 20rpx
|
||||
|
||||
.section-title
|
||||
padding: 32rpx
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
background: #fff
|
||||
border-bottom: 1rpx solid #f0f0f0
|
||||
|
||||
.contact-group
|
||||
background: #fff
|
||||
margin-bottom: 20rpx
|
||||
border-radius: 16rpx
|
||||
overflow: hidden
|
||||
|
||||
.group-header
|
||||
padding: 24rpx 32rpx
|
||||
font-size: 28rpx
|
||||
color: #000000
|
||||
background: #f8f9fa
|
||||
font-weight: 500
|
||||
border-bottom: 1rpx solid #e8e8e8
|
||||
|
||||
.form-item
|
||||
border-bottom: 1rpx solid #f0f0f0
|
||||
|
||||
&:last-child
|
||||
border-bottom: none
|
||||
|
||||
.add-contact-btn
|
||||
padding: 32rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
color: #256BFA
|
||||
font-size: 28rpx
|
||||
background: #fff
|
||||
border-top: 1rpx solid #f0f0f0
|
||||
|
||||
text
|
||||
margin-left: 12rpx
|
||||
|
||||
.bottom-actions
|
||||
position: fixed
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
background: #fff
|
||||
padding: 20rpx 32rpx
|
||||
display: flex
|
||||
gap: 20rpx
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1)
|
||||
|
||||
.cancel-btn, .confirm-btn
|
||||
flex: 1
|
||||
height: 80rpx
|
||||
border-radius: 40rpx
|
||||
font-size: 32rpx
|
||||
border: none
|
||||
|
||||
.cancel-btn
|
||||
background: #f5f5f5
|
||||
color: #666
|
||||
|
||||
.confirm-btn
|
||||
background: #256BFA
|
||||
color: #fff
|
||||
|
||||
// 弹窗样式
|
||||
.popup-content
|
||||
width: 600rpx
|
||||
background: #fff
|
||||
border-radius: 20rpx
|
||||
padding: 40rpx
|
||||
|
||||
.popup-title
|
||||
font-size: 36rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 30rpx
|
||||
text-align: center
|
||||
|
||||
.popup-input, .popup-textarea
|
||||
width: 100%
|
||||
padding: 20rpx
|
||||
border: 1rpx solid #e0e0e0
|
||||
border-radius: 10rpx
|
||||
font-size: 28rpx
|
||||
margin-bottom: 30rpx
|
||||
box-sizing: border-box
|
||||
text-align: center
|
||||
|
||||
.popup-textarea
|
||||
height: 200rpx
|
||||
|
||||
.popup-actions
|
||||
display: flex
|
||||
gap: 20rpx
|
||||
|
||||
.popup-cancel, .popup-confirm
|
||||
flex: 1
|
||||
height: 70rpx
|
||||
border-radius: 35rpx
|
||||
font-size: 28rpx
|
||||
border: none
|
||||
|
||||
.popup-cancel
|
||||
background: #f5f5f5
|
||||
color: #666
|
||||
|
||||
.popup-confirm
|
||||
background: #256BFA
|
||||
color: #fff
|
||||
|
||||
// 按钮重置样式
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
596
pages/complete-info/complete-info.vue
Normal file
596
pages/complete-info/complete-info.vue
Normal file
@@ -0,0 +1,596 @@
|
||||
<template>
|
||||
<AppLayout title="AI+就业服务程序">
|
||||
<view class="tab-container">
|
||||
<view class="uni-margin-wrap">
|
||||
<swiper
|
||||
class="swiper"
|
||||
:current="tabCurrent"
|
||||
:circular="false"
|
||||
:indicator-dots="false"
|
||||
:autoplay="false"
|
||||
:duration="500"
|
||||
>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="login-content">
|
||||
<image class="logo" src="@/static/logo.png"></image>
|
||||
<view class="logo-title">就业</view>
|
||||
</view>
|
||||
<view class="btns">
|
||||
<button class="wxlogin" @click="loginTest">内测登录</button>
|
||||
<view class="wxaddress">{{ config.appInfo.areaName }}公共就业和人才服务中心</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">1</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeExperience">
|
||||
<view class="input-titile">工作经验</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.experienceText"
|
||||
disabled
|
||||
placeholder="请选择您的工作经验"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-sex">
|
||||
<view class="sex-titile">求职区域</view>
|
||||
<view class="sext-ri">
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 0 }"
|
||||
@click="changeSex(0)"
|
||||
>
|
||||
男
|
||||
</view>
|
||||
<view
|
||||
class="sext-box"
|
||||
:class="{ 'sext-boxactive': fromValue.sex === 1 }"
|
||||
@click="changeSex(1)"
|
||||
>
|
||||
女
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeEducation">
|
||||
<view class="input-titile">学历</view>
|
||||
<input class="input-con" v-model="state.educationText" disabled placeholder="本科" />
|
||||
</view>
|
||||
<view class="content-input" :class="{ 'input-error': idCardError }">
|
||||
<view class="input-titile">身份证</view>
|
||||
<input
|
||||
class="input-con2"
|
||||
v-model="fromValue.idcard"
|
||||
maxlength="18"
|
||||
placeholder="请输入身份证号码"
|
||||
@input="validateIdCard"
|
||||
/>
|
||||
<view v-if="idCardError" class="error-message">{{ idCardError }}</view>
|
||||
<view v-if="fromValue.idcard && !idCardError" class="success-message">✓ 身份证格式正确</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="nextStep">下一步</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
<swiper-item @touchmove.stop="false">
|
||||
<view class="content-one">
|
||||
<view>
|
||||
<view class="content-title">
|
||||
<view class="title-lf">
|
||||
<view class="lf-h1">请您完善求职名片</view>
|
||||
<view class="lf-text">个人信息仅用于推送优质内容</view>
|
||||
</view>
|
||||
<view class="title-ri">
|
||||
<text style="color: #256bfa">2</text>
|
||||
<text>/2</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeArea">
|
||||
<view class="input-titile">求职区域</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.areaText"
|
||||
disabled
|
||||
placeholder="请选择您的求职区域"
|
||||
/>
|
||||
</view>
|
||||
<view class="content-input" @click="changeJobs">
|
||||
<view class="input-titile">求职岗位</view>
|
||||
<input
|
||||
class="input-con"
|
||||
disabled
|
||||
v-if="!state.jobsText.length"
|
||||
placeholder="请选择您的求职岗位"
|
||||
/>
|
||||
<view class="input-nx" @click="changeJobs" v-else>
|
||||
<view class="nx-item" v-for="item in state.jobsText">{{ item }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="content-input" @click="changeSalay">
|
||||
<view class="input-titile">期望薪资</view>
|
||||
<input
|
||||
class="input-con"
|
||||
v-model="state.salayText"
|
||||
disabled
|
||||
placeholder="请选择您的期望薪资"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="next-btn" @tap="complete">开启求职之旅</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
<SelectJobs ref="selectJobsModel"></SelectJobs>
|
||||
<SelectPopup ref="selectPopupRef"></SelectPopup>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import SelectJobs from '@/components/selectJobs/selectJobs.vue';
|
||||
import SelectPopup from '@/components/selectPopup/selectPopup.vue';
|
||||
import { reactive, inject, watch, ref, onMounted } from 'vue';
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import useDictStore from '@/stores/useDictStore';
|
||||
const { $api, navTo, config, IdCardValidator } = inject('globalFunction');
|
||||
const { loginSetToken, getUserResume } = useUserStore();
|
||||
const { getDictSelectOption, oneDictData } = useDictStore();
|
||||
|
||||
// #ifdef H5
|
||||
const injectedOpenSelectPopup = inject('openSelectPopup', null);
|
||||
// #endif
|
||||
|
||||
// status
|
||||
const selectJobsModel = ref();
|
||||
const selectPopupRef = ref();
|
||||
|
||||
// 创建本地的 openSelectPopup 函数
|
||||
const openSelectPopup = (config) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (selectPopupRef.value) {
|
||||
selectPopupRef.value.open(config);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
if (injectedOpenSelectPopup) {
|
||||
injectedOpenSelectPopup(config);
|
||||
}
|
||||
// #endif
|
||||
};
|
||||
const tabCurrent = ref(1);
|
||||
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
|
||||
const state = reactive({
|
||||
station: [],
|
||||
stationCateLog: 1,
|
||||
lfsalay: [2, 5, 10, 15, 20, 25, 30, 50],
|
||||
risalay: JSON.parse(JSON.stringify(salay)),
|
||||
areaText: '',
|
||||
educationText: '',
|
||||
experienceText: '',
|
||||
salayText: '',
|
||||
jobsText: [],
|
||||
});
|
||||
const fromValue = reactive({
|
||||
sex: 1,
|
||||
education: '4',
|
||||
salaryMin: 2000,
|
||||
salaryMax: 2000,
|
||||
area: 0,
|
||||
jobTitleId: '',
|
||||
experience: '1',
|
||||
idcard: '',
|
||||
});
|
||||
|
||||
// 身份证校验相关
|
||||
const idCardError = ref('');
|
||||
|
||||
onLoad((parmas) => {
|
||||
getTreeselect();
|
||||
});
|
||||
|
||||
onMounted(() => {});
|
||||
|
||||
function changeSex(sex) {
|
||||
fromValue.sex = sex;
|
||||
}
|
||||
|
||||
// 身份证实时校验
|
||||
function validateIdCard() {
|
||||
const idCard = fromValue.idcard.trim();
|
||||
|
||||
// 如果为空,清除错误信息
|
||||
if (!idCard) {
|
||||
idCardError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用身份证校验器进行校验
|
||||
const result = IdCardValidator.validate(idCard);
|
||||
|
||||
if (result.valid) {
|
||||
idCardError.value = '';
|
||||
} else {
|
||||
idCardError.value = result.message;
|
||||
}
|
||||
}
|
||||
function changeExperience() {
|
||||
openSelectPopup({
|
||||
title: '工作经验',
|
||||
maskClick: true,
|
||||
data: [oneDictData('experience')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.experience = value.value;
|
||||
state.experienceText = value.label;
|
||||
},
|
||||
change(_, [value]) {
|
||||
// this.setColunm(1, [123, 123]);
|
||||
console.log(this);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeEducation() {
|
||||
openSelectPopup({
|
||||
title: '学历',
|
||||
maskClick: true,
|
||||
data: [oneDictData('education')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.educationText = value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeArea() {
|
||||
openSelectPopup({
|
||||
title: '区域',
|
||||
maskClick: true,
|
||||
data: [oneDictData('area')],
|
||||
success: (_, [value]) => {
|
||||
fromValue.area = value.value;
|
||||
state.areaText = config.appInfo.areaName + '-' + value.label;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeSalay() {
|
||||
let leftIndex = 0;
|
||||
openSelectPopup({
|
||||
title: '薪资',
|
||||
maskClick: true,
|
||||
data: [state.lfsalay, state.risalay],
|
||||
unit: 'k',
|
||||
success: (_, [min, max]) => {
|
||||
fromValue.salaryMin = min.value * 1000;
|
||||
fromValue.salaryMax = max.value * 1000;
|
||||
state.salayText = `${fromValue.salaryMin}-${fromValue.salaryMax}`;
|
||||
},
|
||||
change(e) {
|
||||
const salayData = e.detail.value;
|
||||
if (leftIndex !== salayData[0]) {
|
||||
const copyri = JSON.parse(JSON.stringify(salay));
|
||||
const [lf, ri] = e.detail.value;
|
||||
const risalay = copyri.slice(lf, copyri.length);
|
||||
this.setColunm(1, risalay);
|
||||
leftIndex = salayData[0];
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function changeJobs() {
|
||||
selectJobsModel.value?.open({
|
||||
title: '添加岗位',
|
||||
success: (ids, labels) => {
|
||||
fromValue.jobTitleId = ids;
|
||||
state.jobsText = labels.split(',');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
// 校验身份证号码
|
||||
const idCard = fromValue.idcard.trim();
|
||||
if (!idCard) {
|
||||
$api.msg('请输入身份证号码');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = IdCardValidator.validate(idCard);
|
||||
if (!result.valid) {
|
||||
$api.msg(result.message);
|
||||
return;
|
||||
}
|
||||
|
||||
tabCurrent.value += 1;
|
||||
}
|
||||
|
||||
// 获取职位
|
||||
function getTreeselect() {
|
||||
$api.createRequest('/app/common/jobTitle/treeselect', {}, 'GET').then((resData) => {
|
||||
state.station = resData.data;
|
||||
});
|
||||
}
|
||||
|
||||
// 登录
|
||||
function loginTest() {
|
||||
// uni.share({
|
||||
// provider: 'weixin',
|
||||
// scene: 'WXSceneSession',
|
||||
// type: 2,
|
||||
// imageUrl: 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/uni@2x.png',
|
||||
// success: function (res) {
|
||||
// console.log('success:' + JSON.stringify(res));
|
||||
// },
|
||||
// fail: function (err) {
|
||||
// console.log('fail:' + JSON.stringify(err));
|
||||
// },
|
||||
// });
|
||||
const params = {
|
||||
username: 'test',
|
||||
password: 'test',
|
||||
};
|
||||
$api.createRequest('/app/login', params, 'post').then((resData) => {
|
||||
$api.msg('模拟帐号密码测试登录成功');
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
if (resume.data.jobTitleId) {
|
||||
// 设置推荐列表,每次退出登录都需要更新
|
||||
useUserStore().initSeesionId();
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
} else {
|
||||
nextStep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function complete() {
|
||||
const result = IdCardValidator.validate(fromValue.idcard);
|
||||
if (result.valid) {
|
||||
$api.createRequest('/app/user/resume', fromValue, 'post').then((resData) => {
|
||||
$api.msg('完成');
|
||||
// 获取用户信息并存储到store中
|
||||
getUserResume().then((userInfo) => {
|
||||
console.log('用户信息已存储到store:', userInfo);
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$api.msg('身份证校验失败');
|
||||
console.log('验证失败:', result.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.tab-container
|
||||
height: 100%
|
||||
width: 100%
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
flex-direction: row
|
||||
.uni-margin-wrap
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper
|
||||
width: 100%
|
||||
height: 100%
|
||||
.swiper-item
|
||||
display: block;
|
||||
width: 100%
|
||||
height: 100%
|
||||
.input-nx
|
||||
position: relative
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
padding-bottom: 30rpx
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
.nx-item
|
||||
padding: 20rpx 28rpx
|
||||
width: fit-content
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
border: 2rpx solid #E8EAEE;
|
||||
margin-right: 24rpx
|
||||
margin-top: 24rpx
|
||||
.nx-item::before
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 60rpx;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: translate(0, -50%) rotate(-45deg) ;
|
||||
.nx-item::after
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 61rpx;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: rotate(45deg)
|
||||
.container
|
||||
// background: linear-gradient(#4778EC, #002979);
|
||||
width: 100%;
|
||||
height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
|
||||
position: fixed;
|
||||
background: url('@/static/icon/background2.png') 0 0 no-repeat;
|
||||
background-size: 100% 728rpx;
|
||||
display: flex;
|
||||
flex-direction: column
|
||||
.container-hader
|
||||
height: 88rpx;
|
||||
text-align: center;
|
||||
line-height: 88rpx;
|
||||
color: #000000;
|
||||
font-weight: bold
|
||||
font-size: 32rpx
|
||||
|
||||
.login-content
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 40%;
|
||||
transform: translate(-50%, -50%);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
flex-wrap: nowrap;
|
||||
.logo
|
||||
width: 266rpx;
|
||||
height: 182rpx;
|
||||
.logo-title
|
||||
font-size: 88rpx;
|
||||
color: #22c984;
|
||||
width: 180rpx;
|
||||
.btns
|
||||
position: absolute;
|
||||
top: 70%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0)
|
||||
.wxlogin
|
||||
width: 562rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 70rpx;
|
||||
background-color: #13C57C;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 140rpx;
|
||||
font-size: 70rpx;
|
||||
.wxaddress
|
||||
color: #BBBBBB;
|
||||
margin-top: 70rpx;
|
||||
text-align: center;
|
||||
.content-one
|
||||
padding: 60rpx 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between
|
||||
height: calc(100% - 120rpx)
|
||||
.content-title
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: center
|
||||
margin-bottom: 70rpx
|
||||
.title-lf
|
||||
font-size: 44rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.lf-text
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6C7282;
|
||||
.title-ri
|
||||
font-size: 36rpx;
|
||||
color: #000000;
|
||||
font-weight: 600;
|
||||
.content-input
|
||||
margin-bottom: 52rpx
|
||||
.input-titile
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
color: #6A6A6A;
|
||||
.input-con2
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
.input-con
|
||||
font-weight: 400;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 80rpx;
|
||||
height: 80rpx;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
position: relative;
|
||||
.input-con::before
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: calc(50% - 2rpx);
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: translate(0, -50%) rotate(-45deg) ;
|
||||
.input-con::after
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 4rpx;
|
||||
height: 18rpx;
|
||||
border-radius: 2rpx
|
||||
background: #697279;
|
||||
transform: rotate(45deg)
|
||||
.error-message
|
||||
color: #ff4757;
|
||||
font-size: 24rpx;
|
||||
margin-top: 10rpx;
|
||||
line-height: 1.4;
|
||||
.success-message
|
||||
color: #2ed573;
|
||||
font-size: 24rpx;
|
||||
margin-top: 10rpx;
|
||||
line-height: 1.4;
|
||||
.input-error
|
||||
.input-con2
|
||||
border-bottom-color: #ff4757;
|
||||
.content-sex
|
||||
height: 110rpx;
|
||||
display: flex
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border-bottom: 2rpx solid #EBEBEB
|
||||
margin-bottom: 52rpx
|
||||
.sex-titile
|
||||
line-height: 80rpx;
|
||||
.sext-ri
|
||||
display: flex
|
||||
align-items: center;
|
||||
.sext-box
|
||||
height: 76rpx;
|
||||
width: 152rpx;
|
||||
text-align: center;
|
||||
line-height: 80rpx;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx
|
||||
border: 2rpx solid #E8EAEE;
|
||||
margin-left: 28rpx
|
||||
font-weight: 400;
|
||||
font-size: 28rpx;
|
||||
.sext-boxactive
|
||||
color: #256BFA
|
||||
background: rgba(37,107,250,0.1);
|
||||
border: 2rpx solid #256BFA;
|
||||
.next-btn
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
background: #256BFA;
|
||||
border-radius: 12rpx 12rpx 12rpx 12rpx;
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
line-height: 90rpx
|
||||
</style>
|
||||
|
820
pages/complete-info/components/map-location-picker.vue
Normal file
820
pages/complete-info/components/map-location-picker.vue
Normal file
@@ -0,0 +1,820 @@
|
||||
<template>
|
||||
<AppLayout title="选择地址" :showBack="true">
|
||||
<view class="map-container">
|
||||
<!-- 搜索框 -->
|
||||
<view class="search-box">
|
||||
<view class="search-input-wrapper">
|
||||
<uni-icons type="search" size="20" color="#999"></uni-icons>
|
||||
<input
|
||||
class="search-input"
|
||||
v-model="searchKeyword"
|
||||
placeholder="输入关键词搜索地址(支持模糊搜索)"
|
||||
@input="onSearchInput"
|
||||
@confirm="searchLocation"
|
||||
/>
|
||||
<uni-icons
|
||||
v-if="searchKeyword"
|
||||
type="clear"
|
||||
size="18"
|
||||
color="#999"
|
||||
@click="clearSearch"
|
||||
></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 搜索结果列表 -->
|
||||
<view class="search-results" v-if="showSearchResults">
|
||||
<scroll-view scroll-y class="results-scroll" v-if="searchResults.length > 0">
|
||||
<view
|
||||
class="result-item"
|
||||
v-for="(item, index) in searchResults"
|
||||
:key="index"
|
||||
@click="selectSearchResult(item)"
|
||||
>
|
||||
<view class="result-name">{{ item.name }}</view>
|
||||
<view class="result-address">{{ item.address }}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="empty-results" v-else-if="isSearching">
|
||||
<view class="loading-icon">
|
||||
<uni-icons type="loop" size="40" color="#999"></uni-icons>
|
||||
</view>
|
||||
<text>搜索中...</text>
|
||||
</view>
|
||||
<view class="empty-results" v-else>
|
||||
<uni-icons type="info" size="40" color="#999"></uni-icons>
|
||||
<text>未找到相关地址,请尝试其他关键词</text>
|
||||
<view class="search-tips">
|
||||
<text class="tip-title">搜索建议:</text>
|
||||
<text class="tip-item">• 输入具体地址名称</text>
|
||||
<text class="tip-item">• 输入地标建筑名称</text>
|
||||
<text class="tip-item">• 输入街道或区域名称</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 地图 -->
|
||||
<view class="map-wrapper" v-show="!showSearchResults">
|
||||
<!-- #ifdef H5 -->
|
||||
<view id="amap-container" class="amap-container"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifndef H5 -->
|
||||
<map
|
||||
id="map"
|
||||
class="map"
|
||||
:latitude="latitude"
|
||||
:longitude="longitude"
|
||||
:markers="markers"
|
||||
:show-location="true"
|
||||
@markertap="onMarkerTap"
|
||||
@regionchange="onRegionChange"
|
||||
@tap="onMapTap"
|
||||
>
|
||||
<cover-view class="map-center-marker">
|
||||
<cover-image src="/static/icon/Location.png" class="marker-icon"></cover-image>
|
||||
</cover-view>
|
||||
</map>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
|
||||
<!-- 当前位置信息 -->
|
||||
<view class="location-info" v-if="currentAddress && !showSearchResults">
|
||||
<view class="info-title">当前选择位置</view>
|
||||
<view class="info-name">{{ currentAddress.name }}</view>
|
||||
<view class="info-address">{{ currentAddress.address }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="bottom-actions">
|
||||
<button class="locate-btn" @click="getCurrentLocation" :disabled="isLocating">
|
||||
<uni-icons type="location-filled" size="20" color="#256BFA"></uni-icons>
|
||||
<text>{{ isLocating ? '定位中...' : '定位' }}</text>
|
||||
</button>
|
||||
<button class="confirm-btn" @click="confirmLocation">确认选择</button>
|
||||
</view>
|
||||
</view>
|
||||
</AppLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
const { $api } = inject('globalFunction')
|
||||
|
||||
// 搜索相关
|
||||
const searchKeyword = ref('')
|
||||
const searchResults = ref([])
|
||||
const showSearchResults = ref(false)
|
||||
const isSearching = ref(false)
|
||||
const isLocating = ref(false)
|
||||
let searchTimer = null
|
||||
|
||||
// 地图相关
|
||||
const latitude = ref(36.066938)
|
||||
const longitude = ref(120.382665)
|
||||
const markers = ref([])
|
||||
const currentAddress = ref(null)
|
||||
|
||||
// H5地图实例
|
||||
let map = null
|
||||
let AMap = null
|
||||
let geocoder = null
|
||||
let placeSearch = null
|
||||
|
||||
onLoad((options) => {
|
||||
// 可以接收初始位置参数
|
||||
if (options.latitude && options.longitude) {
|
||||
latitude.value = parseFloat(options.latitude)
|
||||
longitude.value = parseFloat(options.longitude)
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef H5
|
||||
initAmapH5()
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 先设置默认位置,避免地图显示空白
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: latitude.value,
|
||||
longitude: longitude.value,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
|
||||
// 延迟执行定位,避免页面加载时立即定位失败
|
||||
setTimeout(() => {
|
||||
getCurrentLocation()
|
||||
}, 1000)
|
||||
// #endif
|
||||
})
|
||||
|
||||
// H5端初始化高德地图
|
||||
const initAmapH5 = () => {
|
||||
// #ifdef H5
|
||||
if (window.AMap) {
|
||||
AMap = window.AMap
|
||||
initMap()
|
||||
} else {
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://webapi.amap.com/maps?v=2.0&key=9cfc9370bd8a941951da1cea0308e9e3&plugin=AMap.Geocoder,AMap.PlaceSearch'
|
||||
script.onload = () => {
|
||||
AMap = window.AMap
|
||||
initMap()
|
||||
}
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 初始化地图
|
||||
const initMap = () => {
|
||||
// #ifdef H5
|
||||
map = new AMap.Map('amap-container', {
|
||||
zoom: 15,
|
||||
center: [longitude.value, latitude.value],
|
||||
resizeEnable: true
|
||||
})
|
||||
|
||||
// 创建标记
|
||||
const marker = new AMap.Marker({
|
||||
position: [longitude.value, latitude.value],
|
||||
draggable: true
|
||||
})
|
||||
|
||||
marker.on('dragend', (e) => {
|
||||
const position = e.target.getPosition()
|
||||
longitude.value = position.lng
|
||||
latitude.value = position.lat
|
||||
reverseGeocode(position.lng, position.lat)
|
||||
})
|
||||
|
||||
map.add(marker)
|
||||
|
||||
// 初始化地理编码
|
||||
geocoder = new AMap.Geocoder({
|
||||
city: '全国'
|
||||
})
|
||||
|
||||
// 初始化地点搜索
|
||||
placeSearch = new AMap.PlaceSearch({
|
||||
city: '全国',
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
// 地图点击事件
|
||||
map.on('click', (e) => {
|
||||
const { lng, lat } = e.lnglat
|
||||
longitude.value = lng
|
||||
latitude.value = lat
|
||||
marker.setPosition([lng, lat])
|
||||
reverseGeocode(lng, lat)
|
||||
})
|
||||
|
||||
// 获取当前位置信息
|
||||
reverseGeocode(longitude.value, latitude.value)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 搜索输入
|
||||
const onSearchInput = () => {
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer)
|
||||
}
|
||||
|
||||
if (!searchKeyword.value.trim()) {
|
||||
showSearchResults.value = false
|
||||
searchResults.value = []
|
||||
isSearching.value = false
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
isSearching.value = true
|
||||
|
||||
searchTimer = setTimeout(() => {
|
||||
if (searchKeyword.value.trim()) {
|
||||
searchLocation()
|
||||
}
|
||||
}, 300) // 优化防抖时间从500ms改为300ms
|
||||
}
|
||||
|
||||
// 搜索地点
|
||||
const searchLocation = () => {
|
||||
if (!searchKeyword.value.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
isSearching.value = true
|
||||
|
||||
// #ifdef H5
|
||||
if (placeSearch) {
|
||||
placeSearch.search(searchKeyword.value, (status, result) => {
|
||||
isSearching.value = false
|
||||
if (status === 'complete' && result.poiList) {
|
||||
searchResults.value = result.poiList.pois.map(poi => ({
|
||||
name: poi.name,
|
||||
address: poi.address || poi.pname + poi.cityname + poi.adname,
|
||||
location: poi.location,
|
||||
lng: poi.location.lng,
|
||||
lat: poi.location.lat
|
||||
}))
|
||||
} else {
|
||||
searchResults.value = []
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 小程序端使用uni.request调用高德API
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/place/text',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
keywords: searchKeyword.value,
|
||||
city: '全国',
|
||||
offset: 20,
|
||||
citylimit: false, // 不限制城市,支持全国搜索
|
||||
extensions: 'all' // 返回详细信息
|
||||
},
|
||||
success: (res) => {
|
||||
isSearching.value = false
|
||||
console.log('搜索响应:', res.data) // 调试日志
|
||||
|
||||
if (res.data.status === '1' && res.data.pois && res.data.pois.length > 0) {
|
||||
searchResults.value = res.data.pois.map(poi => {
|
||||
const [lng, lat] = poi.location.split(',')
|
||||
return {
|
||||
name: poi.name,
|
||||
address: poi.address || `${poi.pname || ''}${poi.cityname || ''}${poi.adname || ''}`,
|
||||
lng: parseFloat(lng),
|
||||
lat: parseFloat(lat)
|
||||
}
|
||||
})
|
||||
console.log('搜索结果:', searchResults.value) // 调试日志
|
||||
} else {
|
||||
// 如果第一次搜索没有结果,尝试更宽泛的搜索
|
||||
if (searchKeyword.value.length > 2) {
|
||||
tryAlternativeSearch()
|
||||
} else {
|
||||
searchResults.value = []
|
||||
console.log('搜索无结果:', res.data) // 调试日志
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
isSearching.value = false
|
||||
searchResults.value = []
|
||||
console.error('搜索请求失败:', err) // 调试日志
|
||||
$api.msg('搜索失败,请检查网络连接')
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 备用搜索策略
|
||||
const tryAlternativeSearch = () => {
|
||||
// 尝试使用地理编码API搜索
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/geo',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
address: searchKeyword.value,
|
||||
city: '全国'
|
||||
},
|
||||
success: (res) => {
|
||||
isSearching.value = false
|
||||
console.log('备用搜索响应:', res.data) // 调试日志
|
||||
|
||||
if (res.data.status === '1' && res.data.geocodes && res.data.geocodes.length > 0) {
|
||||
searchResults.value = res.data.geocodes.map(geo => {
|
||||
const [lng, lat] = geo.location.split(',')
|
||||
return {
|
||||
name: geo.formatted_address,
|
||||
address: geo.formatted_address,
|
||||
lng: parseFloat(lng),
|
||||
lat: parseFloat(lat)
|
||||
}
|
||||
})
|
||||
console.log('备用搜索结果:', searchResults.value) // 调试日志
|
||||
} else {
|
||||
searchResults.value = []
|
||||
console.log('备用搜索也无结果:', res.data) // 调试日志
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
isSearching.value = false
|
||||
searchResults.value = []
|
||||
console.error('备用搜索失败:', err) // 调试日志
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择搜索结果
|
||||
const selectSearchResult = (item) => {
|
||||
longitude.value = item.lng
|
||||
latitude.value = item.lat
|
||||
currentAddress.value = {
|
||||
name: item.name,
|
||||
address: item.address,
|
||||
longitude: item.lng,
|
||||
latitude: item.lat
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (map) {
|
||||
map.setCenter([item.lng, item.lat])
|
||||
const marker = map.getAllOverlays('marker')[0]
|
||||
if (marker) {
|
||||
marker.setPosition([item.lng, item.lat])
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: item.lat,
|
||||
longitude: item.lng,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
// #endif
|
||||
|
||||
showSearchResults.value = false
|
||||
searchKeyword.value = ''
|
||||
}
|
||||
|
||||
// 清除搜索
|
||||
const clearSearch = () => {
|
||||
searchKeyword.value = ''
|
||||
searchResults.value = []
|
||||
showSearchResults.value = false
|
||||
isSearching.value = false
|
||||
if (searchTimer) {
|
||||
clearTimeout(searchTimer)
|
||||
}
|
||||
}
|
||||
|
||||
// 逆地理编码(根据坐标获取地址)
|
||||
const reverseGeocode = (lng, lat) => {
|
||||
// #ifdef H5
|
||||
if (geocoder) {
|
||||
geocoder.getAddress([lng, lat], (status, result) => {
|
||||
if (status === 'complete' && result.regeocode) {
|
||||
const addressComponent = result.regeocode.addressComponent
|
||||
const formattedAddress = result.regeocode.formattedAddress
|
||||
currentAddress.value = {
|
||||
name: addressComponent.building || addressComponent.township,
|
||||
address: formattedAddress,
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
data: {
|
||||
key: '9cfc9370bd8a941951da1cea0308e9e3',
|
||||
location: `${lng},${lat}`
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.data.status === '1' && res.data.regeocode) {
|
||||
const addressComponent = res.data.regeocode.addressComponent
|
||||
const formattedAddress = res.data.regeocode.formatted_address
|
||||
currentAddress.value = {
|
||||
name: addressComponent.building || addressComponent.township || '选择的位置',
|
||||
address: formattedAddress,
|
||||
longitude: lng,
|
||||
latitude: lat
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 获取当前定位
|
||||
const getCurrentLocation = () => {
|
||||
if (isLocating.value) return // 防止重复定位
|
||||
|
||||
isLocating.value = true
|
||||
uni.showLoading({ title: '定位中...' })
|
||||
|
||||
// 先检查定位权限
|
||||
uni.getSetting({
|
||||
success: (settingRes) => {
|
||||
if (settingRes.authSetting['scope.userLocation'] === false) {
|
||||
// 用户拒绝了定位权限,引导用户开启
|
||||
isLocating.value = false
|
||||
uni.hideLoading()
|
||||
uni.showModal({
|
||||
title: '定位权限',
|
||||
content: '需要获取您的位置信息来提供更好的服务,请在设置中开启定位权限',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting()
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 执行定位
|
||||
uni.getLocation({
|
||||
type: 'gcj02',
|
||||
altitude: false,
|
||||
success: (res) => {
|
||||
console.log('定位成功:', res) // 调试日志
|
||||
longitude.value = res.longitude
|
||||
latitude.value = res.latitude
|
||||
|
||||
// #ifdef H5
|
||||
if (map) {
|
||||
map.setCenter([res.longitude, res.latitude])
|
||||
const marker = map.getAllOverlays('marker')[0]
|
||||
if (marker) {
|
||||
marker.setPosition([res.longitude, res.latitude])
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
// 更新小程序端标记
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: res.latitude,
|
||||
longitude: res.longitude,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
// #endif
|
||||
|
||||
reverseGeocode(res.longitude, res.latitude)
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('定位失败:', err) // 调试日志
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
|
||||
// 根据错误类型给出不同提示
|
||||
let errorMsg = '定位失败'
|
||||
if (err.errMsg.includes('auth deny')) {
|
||||
errorMsg = '定位权限被拒绝,请在设置中开启'
|
||||
} else if (err.errMsg.includes('timeout')) {
|
||||
errorMsg = '定位超时,请重试'
|
||||
} else if (err.errMsg.includes('network')) {
|
||||
errorMsg = '网络异常,请检查网络连接'
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '定位失败',
|
||||
content: errorMsg + ',是否使用默认位置?',
|
||||
confirmText: '使用默认位置',
|
||||
cancelText: '重试',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
// 使用默认位置(北京)
|
||||
longitude.value = 116.397428
|
||||
latitude.value = 39.90923
|
||||
reverseGeocode(longitude.value, latitude.value)
|
||||
} else {
|
||||
// 重试定位
|
||||
setTimeout(() => {
|
||||
getCurrentLocation()
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: () => {
|
||||
uni.hideLoading()
|
||||
isLocating.value = false
|
||||
$api.msg('无法获取定位权限设置')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 地图区域变化(小程序端)
|
||||
const onRegionChange = (e) => {
|
||||
// #ifndef H5
|
||||
// 只有在用户手动拖动地图结束时才更新位置
|
||||
if (e.type === 'end' && e.causedBy === 'drag') {
|
||||
const mapContext = uni.createMapContext('map')
|
||||
mapContext.getCenterLocation({
|
||||
success: (res) => {
|
||||
longitude.value = res.longitude
|
||||
latitude.value = res.latitude
|
||||
reverseGeocode(res.longitude, res.latitude)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 地图点击事件(小程序端)
|
||||
const onMapTap = (e) => {
|
||||
// #ifndef H5
|
||||
const { latitude: lat, longitude: lng } = e.detail
|
||||
longitude.value = lng
|
||||
latitude.value = lat
|
||||
|
||||
// 更新标记
|
||||
markers.value = [{
|
||||
id: 1,
|
||||
latitude: lat,
|
||||
longitude: lng,
|
||||
iconPath: '/static/icon/Location.png',
|
||||
width: 30,
|
||||
height: 30
|
||||
}]
|
||||
|
||||
reverseGeocode(lng, lat)
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 确认选择
|
||||
const confirmLocation = () => {
|
||||
if (!currentAddress.value) {
|
||||
$api.msg('请选择地址')
|
||||
return
|
||||
}
|
||||
|
||||
// 返回上一页并传递数据
|
||||
const pages = getCurrentPages()
|
||||
const prevPage = pages[pages.length - 2]
|
||||
|
||||
if (prevPage) {
|
||||
prevPage.$vm.handleLocationSelected({
|
||||
address: currentAddress.value.address,
|
||||
name: currentAddress.value.name,
|
||||
longitude: currentAddress.value.longitude,
|
||||
latitude: currentAddress.value.latitude
|
||||
})
|
||||
}
|
||||
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
const onMarkerTap = (e) => {
|
||||
console.log('marker点击', e)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.map-container
|
||||
width: 100%
|
||||
height: 100vh
|
||||
position: relative
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.search-box
|
||||
position: absolute
|
||||
top: 20rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
z-index: 10
|
||||
|
||||
.search-input-wrapper
|
||||
background: #fff
|
||||
border-radius: 40rpx
|
||||
padding: 20rpx 30rpx
|
||||
display: flex
|
||||
align-items: center
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1)
|
||||
|
||||
.search-input
|
||||
flex: 1
|
||||
margin: 0 20rpx
|
||||
font-size: 28rpx
|
||||
|
||||
uni-icons
|
||||
flex-shrink: 0
|
||||
|
||||
.search-results
|
||||
position: absolute
|
||||
top: 100rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
bottom: 180rpx
|
||||
background: #fff
|
||||
border-radius: 20rpx
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1)
|
||||
z-index: 9
|
||||
overflow: hidden
|
||||
|
||||
.results-scroll
|
||||
height: 100%
|
||||
|
||||
.result-item
|
||||
padding: 30rpx
|
||||
border-bottom: 1rpx solid #f0f0f0
|
||||
|
||||
&:active
|
||||
background: #f5f5f5
|
||||
|
||||
.result-name
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.result-address
|
||||
font-size: 26rpx
|
||||
color: #999
|
||||
|
||||
.empty-results
|
||||
height: 100%
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
color: #999
|
||||
font-size: 28rpx
|
||||
|
||||
.loading-icon
|
||||
animation: rotate 1s linear infinite
|
||||
margin-bottom: 20rpx
|
||||
|
||||
uni-icons
|
||||
margin-bottom: 20rpx
|
||||
|
||||
text
|
||||
padding: 0 60rpx
|
||||
text-align: center
|
||||
line-height: 1.5
|
||||
|
||||
.search-tips
|
||||
margin-top: 40rpx
|
||||
padding: 0 40rpx
|
||||
|
||||
.tip-title
|
||||
font-size: 26rpx
|
||||
color: #666
|
||||
font-weight: 500
|
||||
margin-bottom: 20rpx
|
||||
display: block
|
||||
|
||||
.tip-item
|
||||
font-size: 24rpx
|
||||
color: #999
|
||||
line-height: 1.8
|
||||
display: block
|
||||
margin-bottom: 8rpx
|
||||
|
||||
@keyframes rotate
|
||||
from
|
||||
transform: rotate(0deg)
|
||||
to
|
||||
transform: rotate(360deg)
|
||||
|
||||
.map-wrapper
|
||||
flex: 1
|
||||
position: relative
|
||||
|
||||
.map, .amap-container
|
||||
width: 100%
|
||||
height: 100%
|
||||
|
||||
.map-center-marker
|
||||
position: absolute
|
||||
left: 50%
|
||||
top: 50%
|
||||
transform: translate(-50%, -100%)
|
||||
z-index: 5
|
||||
|
||||
.marker-icon
|
||||
width: 60rpx
|
||||
height: 80rpx
|
||||
|
||||
.location-info
|
||||
position: absolute
|
||||
bottom: 180rpx
|
||||
left: 32rpx
|
||||
right: 32rpx
|
||||
background: #fff
|
||||
border-radius: 20rpx
|
||||
padding: 30rpx
|
||||
box-shadow: 0 -4rpx 12rpx rgba(0,0,0,0.1)
|
||||
z-index: 10
|
||||
|
||||
.info-title
|
||||
font-size: 24rpx
|
||||
color: #999
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.info-name
|
||||
font-size: 32rpx
|
||||
color: #333
|
||||
font-weight: 500
|
||||
margin-bottom: 10rpx
|
||||
|
||||
.info-address
|
||||
font-size: 28rpx
|
||||
color: #666
|
||||
|
||||
.bottom-actions
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: 0
|
||||
right: 0
|
||||
background: #fff
|
||||
padding: 20rpx 32rpx
|
||||
display: flex
|
||||
gap: 20rpx
|
||||
box-shadow: 0 -2rpx 10rpx rgba(0,0,0,0.1)
|
||||
z-index: 11
|
||||
|
||||
.locate-btn
|
||||
width: 120rpx
|
||||
height: 80rpx
|
||||
background: #fff
|
||||
border: 2rpx solid #256BFA
|
||||
border-radius: 40rpx
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
justify-content: center
|
||||
font-size: 24rpx
|
||||
color: #256BFA
|
||||
|
||||
&:disabled
|
||||
opacity: 0.5
|
||||
color: #999
|
||||
border-color: #999
|
||||
|
||||
text
|
||||
margin-top: 4rpx
|
||||
|
||||
.confirm-btn
|
||||
flex: 1
|
||||
height: 80rpx
|
||||
background: #256BFA
|
||||
color: #fff
|
||||
border-radius: 40rpx
|
||||
font-size: 32rpx
|
||||
border: none
|
||||
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
||||
|
Reference in New Issue
Block a user