Files
ks-app-employment-service/pages/login/wx-login.vue
2026-04-10 12:27:10 +08:00

652 lines
22 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>
<view class="wx-login-page">
<!-- 顶部导航栏 -->
<!-- <view class="nav-bar">
<view class="nav-back" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
</view>
<view class="nav-title">登录</view>
<view class="nav-placeholder"></view>
</view> -->
<!-- 页面内容 -->
<view class="page-content">
<!-- Logo和标题 -->
<view class="auth-header">
<image class="auth-logo" src="@/static/logo2-S.png" mode="aspectFit"></image>
<view class="auth-title">欢迎使用就业服务</view>
</view>
<!-- 角色选择 -->
<view class="role-select">
<view class="role-title">请选择您的角色</view>
<view class="role-options">
<view
class="role-item"
:class="{ active: userType === 1 }"
@click="selectRole(1)"
>
<view class="role-icon">
<uni-icons type="person" size="32" :color="userType === 1 ? '#256BFA' : '#999'"></uni-icons>
</view>
<view class="role-text">个人</view>
</view>
<view
class="role-item"
:class="{ active: userType === 0 }"
@click="selectRole(0)"
>
<view class="role-icon">
<uni-icons type="shop" size="32" :color="userType === 0 ? '#256BFA' : '#999'"></uni-icons>
</view>
<view class="role-text">单位</view>
</view>
</view>
</view>
<!-- 机构类型选择仅单位角色显示 -->
<view v-if="userType === 0" class="org-type-select">
<view class="org-type-title">请选择机构类型</view>
<view class="org-type-options">
<view
v-for="option in orgTypeOptions"
:key="option.value"
class="org-type-item"
:class="{ active: orgType === option.value }"
@click="selectOrgType(option.value)"
>
<view class="org-type-text">{{ option.label }}</view>
</view>
</view>
</view>
<!-- 授权说明 -->
<view class="auth-tips">
<view class="tip-item">
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
<text>保护您的个人信息安全</text>
</view>
<view class="tip-item">
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
<text>为您推荐更合适的岗位</text>
</view>
<view class="tip-item">
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
<text>享受完整的就业服务</text>
</view>
</view>
<!-- 授权按钮 -->
<view class="auth-actions">
<!-- 微信小程序使用 open-type="getPhoneNumber" -->
<!-- #ifdef MP-WEIXIN -->
<button
class="auth-btn primary"
open-type="getPhoneNumber"
@getphonenumber="onWxGetPhoneNumber"
>
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
<text>手机号快捷登录</text>
</button>
<!-- #endif -->
<!-- H5和App使用普通按钮 -->
<!-- #ifndef MP-WEIXIN -->
<button class="auth-btn primary" @click="wxLogin">
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
<text>手机号快捷登录</text>
</button>
<!-- #endif -->
<!-- 测试登录按钮仅开发环境 -->
<!-- #ifdef APP-PLUS || H5 -->
<button class="auth-btn secondary" @click="testLogin">
<text>测试账号登录</text>
</button>
<!-- #endif -->
</view>
<!-- 用户协议 -->
<view class="auth-agreement">
<view class="agreement-checkbox" @click="toggleAgreement">
<uni-icons
:type="agreedToAgreement ? 'checkbox-filled' : 'circle'"
size="20"
:color="agreedToAgreement ? '#256BFA' : '#999'"
></uni-icons>
<text class="agreement-text">我已阅读并同意</text>
</view>
<text class="link" @click="openAgreement('user')">隐私协议</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, inject, onMounted, computed } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import useUserStore from '@/stores/useUserStore';
import useDictStore from '@/stores/useDictStore';
const { $api } = inject('globalFunction');
const userStore = useUserStore();
const dictStore = useDictStore();
const userType = ref(null); // 用户角色1-求职者0-企业
const orgType = ref(null); // 机构类型
const orgTypeOptions = ref([]); // 机构类型选项
const agreedToAgreement = ref(false); // 是否同意用户协议
// 计算是否可提交
const canSubmit = computed(() => {
if (userType.value === null) return false;
if (userType.value === 0 && orgType.value === null) return false;
if (!agreedToAgreement.value) return false;
return true;
});
// 获取机构类型字典
const getOrgTypeDict = async () => {
try {
const options = await dictStore.getDictSelectOption('org_type');
orgTypeOptions.value = options;
} catch (error) {
console.error('获取机构类型字典失败:', error);
// 使用备用数据
orgTypeOptions.value = [
{ label: '有限责任公司', value: '1' },
{ label: '股份有限公司', value: '2' },
{ label: '个人独资企业', value: '3' },
{ label: '合伙企业', value: '4' },
{ label: '外商投资企业', value: '5' },
{ label: '其他', value: '6' }
];
}
};
// 选择角色
const selectRole = (type) => {
userType.value = type;
orgType.value = null; // 切换角色时重置机构类型选择
};
// 选择机构类型
const selectOrgType = (type) => {
orgType.value = type;
};
// 切换协议同意状态
const toggleAgreement = () => {
agreedToAgreement.value = !agreedToAgreement.value;
};
// 打开用户协议
const openAgreement = (type) => {
const urls = {
user: '/packageA/pages/agreement/user',
privacy: '/packageA/pages/agreement/privacy'
};
uni.navigateTo({
url: urls[type]
});
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 通用的验证函数
const validateForm = () => {
if (userType.value === null) {
uni.showToast({
title: '请先选择您的角色(个人或单位)',
icon: 'none',
duration: 2000
});
return false;
}
if (userType.value === 0 && orgType.value === null) {
uni.showToast({
title: '请选择机构类型',
icon: 'none',
duration: 2000
});
return false;
}
if (!agreedToAgreement.value) {
uni.showToast({
title: '请先阅读并同意隐私协议',
icon: 'none',
duration: 2000
});
return false;
}
return true;
};
// 微信小程序授权前的检查
const checkBeforeWxAuth = (e) => {
// 验证表单
if (!validateForm()) {
// 阻止微信授权流程
if (e && e.preventDefault) {
e.preventDefault();
}
return false;
} else {
console.log('Validation passed, allowing wx auth');
}
};
// 微信获取手机号
const onWxGetPhoneNumber = async (e) => {
const { encryptedData, iv } = e.detail;
// 使用通用验证函数
if (!validateForm()) {
return;
}
if (e.detail.errMsg === 'getPhoneNumber:ok') {
uni.login({
provider: 'weixin',
success: (loginRes) => {
const code = loginRes.code;
// 调用接口 /app/appWxphoneSmsCode
uni.showLoading({ title: '获取验证码中...' });
$api.createRequest('/app/appWxphoneSmsCode', {
code,
encryptedData,
iv,
userType: userType.value,
orgType: orgType.value
}, 'post').then((resData) => {
uni.hideLoading();
// 检查可能的手机号字段
const possiblePhoneFields = ['phone', 'mobile', 'phoneNumber', 'tel', 'mobilePhone'];
let phoneValue = '';
for (const field of possiblePhoneFields) {
if (resData[field]) {
phoneValue = resData[field];
console.log(`从接口返回中找到手机号字段 "${field}": ${phoneValue}`);
break;
}
}
if (resData.code === 200 || resData.success) {
// 跳转到短信验证页面,传递参数
const params = {
phone: phoneValue || '', // 接口返回的手机号
openid: resData.openid || '',
unionid: resData.unionid || '',
userType: userType.value,
orgType: orgType.value
};
uni.navigateTo({
url: '/pages/login/sms-verify?' + Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&')
});
} else {
$api.msg(resData.msg || '获取验证码失败');
}
}).catch((err) => {
uni.hideLoading();
$api.msg(err.msg || '获取验证码失败,请重试');
});
},
fail: (err) => {
uni.showToast({
title: '获取登录信息失败,请重试',
icon: 'none',
duration: 2000
});
}
});
} else if (e.detail.errMsg === 'getPhoneNumber:fail user deny') {
uni.showToast({
title: '您取消了授权',
icon: 'none',
duration: 2000
});
} else {
uni.showToast({
title: '获取手机号失败',
icon: 'none',
duration: 2000
});
}
};
// H5/App 微信登录(暂保持原有逻辑,后续可调整)
const wxLogin = () => {
// 使用通用验证函数
if (!validateForm()) {
console.log('Validation failed in wxLogin');
return;
}
// #ifdef H5
// H5端跳转到H5登录页面
uni.navigateTo({
url: '/pages/login/h5-login'
});
return;
// #endif
// #ifdef APP-PLUS
// App微信登录逻辑暂保持原有逻辑
uni.getProvider({
service: 'oauth',
success: (res) => {
if (~res.provider.indexOf('weixin')) {
uni.login({
provider: 'weixin',
success: (loginRes) => {
console.log('微信登录成功:', loginRes);
// 调用后端接口进行登录
$api.createRequest('/app/appLogin', {
code: loginRes.code,
userType: userType.value,
orgType: orgType.value
}, 'post').then((resData) => {
if (resData.token) {
userStore.loginSetToken(resData.token).then((resume) => {
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1;
uni.setStorageSync('userInfo', userInfo);
}
uni.showToast({
title: '登录成功',
icon: 'success',
duration: 2000
});
// 登录成功后返回上一页
uni.navigateBack();
});
}
});
},
fail: (err) => {
console.error('微信登录失败:', err);
uni.showToast({
title: '微信登录失败',
icon: 'none',
duration: 2000
});
}
});
}
}
});
// #endif
};
// 测试账号登录(仅开发环境)
const testLogin = () => {
// 使用通用验证函数
if (!validateForm()) {
return;
}
uni.showLoading({ title: '登录中...' });
const params = {
username: 'test',
password: 'test',
};
$api.createRequest('/app/login', params, 'post').then((resData) => {
uni.hideLoading();
userStore.loginSetToken(resData.token).then((resume) => {
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1;
uni.setStorageSync('userInfo', userInfo);
}
uni.showToast({
title: '测试登录成功',
icon: 'success',
duration: 2000
});
// 登录成功后返回上一页
uni.navigateBack();
}).catch(() => {
uni.showToast({
title: '获取用户信息失败',
icon: 'none',
duration: 2000
});
});
}).catch((err) => {
uni.hideLoading();
uni.showToast({
title: err.msg || '登录失败',
icon: 'none',
duration: 2000
});
});
};
// 页面加载时获取字典数据
onLoad(() => {
getOrgTypeDict();
});
</script>
<style lang="stylus" scoped>
.wx-login-page
min-height: 100vh
background: #FFFFFF
.nav-bar
display: flex
align-items: center
justify-content: space-between
height: 88rpx
padding: 0 32rpx
border-bottom: 1rpx solid #f5f5f5
.nav-back
width: 60rpx
height: 60rpx
display: flex
align-items: center
justify-content: center
.nav-title
font-size: 32rpx
font-weight: 600
color: #333333
.nav-placeholder
width: 60rpx
.page-content
padding: 40rpx 40rpx 60rpx
.auth-header
text-align: center
margin-bottom: 40rpx
.auth-logo
width: 120rpx
height: 120rpx
margin: 0 auto 24rpx
.auth-title
font-size: 36rpx
font-weight: 600
color: #333333
margin-bottom: 12rpx
.role-select
margin-bottom: 32rpx
.role-title
font-size: 28rpx
font-weight: 500
color: #333333
margin-bottom: 20rpx
text-align: center
.role-options
display: flex
justify-content: space-between
gap: 20rpx
.role-item
flex: 1
background: #F7F8FA
border: 2rpx solid #E5E5E5
border-radius: 16rpx
padding: 32rpx 20rpx
display: flex
flex-direction: column
align-items: center
position: relative
transition: all 0.3s ease
cursor: pointer
&.active
background: #F0F5FF
border-color: #256BFA
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.15)
.role-icon
margin-bottom: 16rpx
.role-text
font-size: 28rpx
color: #333333
font-weight: 500
.org-type-select
margin-bottom: 22rpx
.org-type-title
font-size: 28rpx
font-weight: 500
color: #333333
margin-bottom: 20rpx
text-align: center
.org-type-options
display: flex
flex-wrap: wrap
gap: 16rpx
.org-type-item
display:inline-block
background: #F7F8FA
border: 2rpx solid #E5E5E5
border-radius: 10rpx
padding: 10rpx 10rpx
transition: all 0.3s ease
cursor: pointer
text-align: center
&.active
background: #F0F5FF
border-color: #256BFA
box-shadow: 0 4rpx 12rpx rgba(37, 107, 250, 0.15)
.org-type-text
font-size: 22rpx
color: #333333
font-weight: 500
.auth-tips
background: #F7F8FA
border-radius: 16rpx
padding: 24rpx
margin-bottom: 40rpx
.tip-item
display: flex
align-items: center
margin-bottom: 16rpx
font-size: 26rpx
color: #666666
&:last-child
margin-bottom: 0
text
margin-left: 12rpx
.auth-actions
margin-bottom: 32rpx
.auth-btn
width: 100%
height: 88rpx
border-radius: 44rpx
display: flex
align-items: center
justify-content: center
font-size: 32rpx
font-weight: 500
border: none
margin-bottom: 20rpx
&:last-child
margin-bottom: 0
&.primary
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
color: #FFFFFF
box-shadow: 0 8rpx 20rpx rgba(37, 107, 250, 0.3)
&.secondary
background: #F7F8FA
color: #666666
&:disabled
opacity: 0.5
cursor: not-allowed
background: #CCCCCC !important
box-shadow: none !important
text
margin-left: 12rpx
.auth-agreement
display: flex
align-items: center
justify-content: center
font-size: 24rpx
color: #999999
line-height: 1.6
flex-wrap: wrap
gap: 8rpx
.agreement-checkbox
display: flex
align-items: center
cursor: pointer
.agreement-text
margin-left: 8rpx
color: #666666
.link
color: #256BFA
text-decoration: underline
// 按钮重置样式
button::after
border: none
</style>