Files
ks-app-employment-service/components/wxAuthLogin/WxAuthLogin.vue

644 lines
22 KiB
Vue
Raw Normal View History

2025-10-20 11:43:44 +08:00
<template>
<uni-popup ref="popup" type="center" :mask-click="false">
<view class="auth-modal">
<view class="modal-content">
<!-- 关闭按钮 -->
<view class="close-btn" @click="close">
<uni-icons type="closeempty" size="24" color="#999"></uni-icons>
</view>
<!-- Logo和标题 -->
<view class="auth-header">
2025-11-14 18:10:59 +08:00
<image class="auth-logo" src="@/static/logo2-S.png" mode="aspectFit"></image>
2025-10-20 11:43:44 +08:00
<view class="auth-title">欢迎使用就业服务</view>
</view>
2025-10-21 22:58:47 +08:00
<!-- 角色选择 -->
<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>
2026-01-19 19:09:14 +08:00
<view class="role-text">个人</view>
2025-10-21 22:58:47 +08:00
</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>
2026-01-19 19:09:14 +08:00
<view class="role-text">单位</view>
2025-10-21 22:58:47 +08:00
</view>
</view>
</view>
2026-01-27 13:58:46 +08:00
<!-- 机构类型选择仅单位角色显示 -->
<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>
2025-10-20 11:43:44 +08:00
<!-- 授权说明 -->
<view class="auth-tips">
<view class="tip-item">
2025-10-20 16:15:29 +08:00
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
2025-10-20 11:43:44 +08:00
<text>保护您的个人信息安全</text>
</view>
<view class="tip-item">
2025-10-20 16:15:29 +08:00
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
2025-10-20 11:43:44 +08:00
<text>为您推荐更合适的岗位</text>
</view>
<view class="tip-item">
2025-10-20 16:15:29 +08:00
<uni-icons type="checkmarkempty" size="16" color="#256BFA"></uni-icons>
2025-10-20 11:43:44 +08:00
<text>享受完整的就业服务</text>
</view>
</view>
<!-- 授权按钮 -->
<view class="auth-actions">
<!-- 微信小程序使用 open-type="getPhoneNumber" -->
<!-- #ifdef MP-WEIXIN -->
<button
class="auth-btn primary"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
>
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
2025-12-24 20:36:03 +08:00
<text>手机号快捷登录</text>
2025-10-20 11:43:44 +08:00
</button>
<!-- #endif -->
<!-- H5和App使用普通按钮 -->
<!-- #ifndef MP-WEIXIN -->
<button class="auth-btn primary" @click="wxLogin">
<uni-icons type="phone" size="20" color="#FFFFFF"></uni-icons>
2025-12-24 20:36:03 +08:00
<text>手机号快捷登录</text>
2025-10-20 11:43:44 +08:00
</button>
<!-- #endif -->
<!-- 测试登录按钮仅开发环境 -->
<!-- #ifdef APP-PLUS || H5 -->
<button class="auth-btn secondary" @click="testLogin">
<text>测试账号登录</text>
</button>
<!-- #endif -->
</view>
<!-- 用户协议 -->
2026-02-04 13:17:01 +08:00
<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>
<!-- <text></text>
<text class="link" @click="openAgreement('privacy')">隐私政策</text> -->
</view>
2025-10-20 11:43:44 +08:00
</view>
</view>
</uni-popup>
</template>
<script setup>
2026-01-27 13:58:46 +08:00
import { ref, inject, onMounted } from 'vue';
2025-10-20 11:43:44 +08:00
import useUserStore from '@/stores/useUserStore';
2026-01-27 13:58:46 +08:00
import useDictStore from '@/stores/useDictStore';
2025-10-23 17:16:16 +08:00
import { tabbarManager } from '@/utils/tabbarManager';
2025-10-20 11:43:44 +08:00
const { $api } = inject('globalFunction');
const { loginSetToken } = useUserStore();
2026-01-27 13:58:46 +08:00
const dictStore = useDictStore();
2025-10-20 11:43:44 +08:00
const popup = ref(null);
2025-10-21 22:58:47 +08:00
const userType = ref(null); // 用户角色1-求职者0-企业
2026-01-27 13:58:46 +08:00
const orgType = ref(null); // 机构类型
const orgTypeOptions = ref([]); // 机构类型选项
2026-02-04 13:17:01 +08:00
const agreedToAgreement = ref(false); // 是否同意用户协议
2025-10-20 11:43:44 +08:00
const emit = defineEmits(['success', 'cancel']);
2026-01-27 13:58:46 +08:00
// 获取机构类型字典
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' }
];
}
};
// 组件挂载时获取字典数据
onMounted(() => {
getOrgTypeDict();
});
2025-10-20 11:43:44 +08:00
// 打开弹窗
const open = () => {
popup.value?.open();
2025-10-21 22:58:47 +08:00
userType.value = null; // 重置角色选择
2026-01-27 13:58:46 +08:00
orgType.value = null; // 重置机构类型选择
2026-02-04 13:17:01 +08:00
// 检查是否已同意协议
const agreed = uni.getStorageSync('agreedToUserAgreement');
agreedToAgreement.value = !!agreed;
2025-10-20 11:43:44 +08:00
};
// 关闭弹窗
const close = () => {
popup.value?.close();
emit('cancel');
};
2025-10-21 22:58:47 +08:00
// 选择角色
const selectRole = (type) => {
userType.value = type;
2026-01-27 13:58:46 +08:00
orgType.value = null; // 切换角色时重置机构类型选择
};
// 选择机构类型
const selectOrgType = (type) => {
orgType.value = type;
2025-10-21 22:58:47 +08:00
};
// 验证角色是否已选择
const validateRole = () => {
if (userType.value === null) {
$api.msg('请先选择您的角色');
return false;
}
2026-01-27 13:58:46 +08:00
// 验证机构类型是否已选择(仅单位角色)
if (userType.value === 0 && orgType.value === null) {
$api.msg('请选择机构类型');
return false;
}
2026-02-04 13:17:01 +08:00
// 验证是否同意用户协议
if (!agreedToAgreement.value) {
$api.msg('请先阅读并同意用户协议');
return false;
}
2025-10-21 22:58:47 +08:00
return true;
};
2025-10-20 11:43:44 +08:00
const getPhoneNumber = (e) => {
console.log('获取手机号:', e);
2025-12-08 21:20:33 +08:00
console.log('userType.value', userType.value)
2026-02-04 13:17:01 +08:00
// 验证角色、机构类型和用户协议
if (!validateRole()) {
return;
}
2025-10-21 22:58:47 +08:00
2025-10-20 11:43:44 +08:00
if (e.detail.errMsg === 'getPhoneNumber:ok') {
2025-10-21 22:58:47 +08:00
uni.login({
provider: 'weixin',
success: (loginRes) => {
console.log('微信登录code获取成功', loginRes.code);
const { encryptedData, iv } = e.detail;
const code = loginRes.code; // 使用wx.login返回的code
// 调用后端接口进行登录
uni.showLoading({ title: '登录中...' });
$api.createRequest('/app/appLogin', {
code,
encryptedData,
iv,
2026-01-27 13:58:46 +08:00
userType: userType.value,
orgType: orgType.value
2025-10-21 22:58:47 +08:00
}, 'post').then((resData) => {
uni.hideLoading();
2025-10-23 17:16:16 +08:00
console.log(resData, 'resume.idCard');
2025-10-21 22:58:47 +08:00
if (resData.token) {
// 登录成功存储token
loginSetToken(resData.token).then((resume) => {
2025-10-23 17:16:16 +08:00
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
2025-10-21 22:58:47 +08:00
$api.msg('登录成功');
2025-10-23 17:16:16 +08:00
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
2025-10-21 22:58:47 +08:00
close();
emit('success');
// 根据用户类型跳转到不同的信息补全页面
2025-10-23 17:16:16 +08:00
if (!resume.jobTitleId) {
console.log(resume, 'resume.idCard');
if (userType.value === 1 && !resData.idCard) {
2025-10-21 22:58:47 +08:00
// 求职者跳转到个人信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/complete-info?step=1'
2025-10-21 22:58:47 +08:00
});
2025-10-23 17:16:16 +08:00
} else if (userType.value === 0 && !resData.idCard) {
2025-10-21 22:58:47 +08:00
// 招聘者跳转到企业信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/company-info'
2025-10-21 22:58:47 +08:00
});
}
}
}).catch(() => {
$api.msg('获取用户信息失败');
});
} else {
$api.msg('登录失败,请重试');
}
}).catch((err) => {
uni.hideLoading();
$api.msg(err.msg || '登录失败,请重试');
});
},
fail: (err) => {
console.error('获取微信登录code失败', err);
$api.msg('获取登录信息失败,请重试');
}
});
2025-10-20 11:43:44 +08:00
} else if (e.detail.errMsg === 'getPhoneNumber:fail user deny') {
$api.msg('您取消了授权');
} else {
$api.msg('获取手机号失败');
}
};
// H5/App 微信登录
const wxLogin = () => {
2025-10-21 22:58:47 +08:00
// 验证角色是否已选择
if (!validateRole()) {
return;
}
2025-10-20 11:43:44 +08:00
// #ifdef H5
2025-11-14 18:10:59 +08:00
// H5端跳转到H5登录页面
close();
uni.navigateTo({
url: '/pages/login/h5-login'
2025-10-20 11:43:44 +08:00
});
2025-11-14 18:10:59 +08:00
return;
2025-10-20 11:43:44 +08:00
// #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', {
2025-10-21 22:58:47 +08:00
code: loginRes.code,
2026-01-27 13:58:46 +08:00
userType: userType.value,
orgType: orgType.value
2025-10-20 11:43:44 +08:00
}, 'post').then((resData) => {
if (resData.token) {
loginSetToken(resData.token).then((resume) => {
2025-10-23 17:16:16 +08:00
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
2025-10-20 11:43:44 +08:00
$api.msg('登录成功');
2025-10-23 17:16:16 +08:00
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
2025-10-20 11:43:44 +08:00
close();
emit('success');
if (!resume.data.jobTitleId) {
2025-10-21 22:58:47 +08:00
if (userType.value === 1) {
// 求职者跳转到个人信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/complete-info?step=1'
2025-10-21 22:58:47 +08:00
});
} else if (userType.value === 0) {
// 招聘者跳转到企业信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/company-info'
2025-10-21 22:58:47 +08:00
});
}
2025-10-20 11:43:44 +08:00
}
});
}
});
},
fail: (err) => {
console.error('微信登录失败:', err);
$api.msg('微信登录失败');
}
});
}
}
});
// #endif
};
// 测试账号登录(仅开发环境)
const testLogin = () => {
2026-02-04 13:17:01 +08:00
// 验证角色、机构类型和用户协议
if (!validateRole()) {
return;
}
2025-10-20 11:43:44 +08:00
uni.showLoading({ title: '登录中...' });
const params = {
username: 'test',
password: 'test',
};
$api.createRequest('/app/login', params, 'post').then((resData) => {
uni.hideLoading();
loginSetToken(resData.token).then((resume) => {
2025-10-23 17:16:16 +08:00
// 更新用户类型到缓存
if (resData.isCompanyUser !== undefined) {
const userInfo = uni.getStorageSync('userInfo') || {};
userInfo.isCompanyUser = resData.isCompanyUser ? 0 : 1; // 0-企业用户1-求职者
uni.setStorageSync('userInfo', userInfo);
}
2025-10-20 11:43:44 +08:00
$api.msg('测试登录成功');
2025-10-23 17:16:16 +08:00
// 刷新tabbar以显示正确的用户类型
tabbarManager.refreshTabBar();
2025-10-20 11:43:44 +08:00
close();
emit('success');
if (!resume.data.jobTitleId) {
2025-10-21 22:58:47 +08:00
if (userType.value === 1) {
// 求职者跳转到个人信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/complete-info?step=1'
2025-10-21 22:58:47 +08:00
});
} else if (userType.value === 0) {
// 招聘者跳转到企业信息补全页面
uni.navigateTo({
2026-01-22 18:58:19 +08:00
url: '/packageA/pages/complete-info/company-info'
2025-10-21 22:58:47 +08:00
});
}
2025-10-20 11:43:44 +08:00
}
}).catch(() => {
$api.msg('获取用户信息失败');
});
}).catch((err) => {
uni.hideLoading();
$api.msg(err.msg || '登录失败');
});
};
2026-02-04 13:17:01 +08:00
// 切换协议同意状态
const toggleAgreement = () => {
agreedToAgreement.value = !agreedToAgreement.value;
};
2025-10-20 11:43:44 +08:00
// 打开用户协议
const openAgreement = (type) => {
const urls = {
2026-02-04 13:17:01 +08:00
user: '/packageA/pages/agreement/user',
privacy: '/packageA/pages/agreement/privacy'
2025-10-20 11:43:44 +08:00
};
2026-02-04 13:17:01 +08:00
uni.navigateTo({
url: urls[type]
});
2025-10-20 11:43:44 +08:00
};
// 暴露方法供父组件调用
defineExpose({
open,
close
});
</script>
<style lang="stylus" scoped>
.auth-modal
width: 620rpx
background: #FFFFFF
border-radius: 24rpx
overflow: hidden
.modal-content
2026-01-27 13:58:46 +08:00
padding: 40rpx 40rpx 40rpx
2025-10-20 11:43:44 +08:00
position: relative
.close-btn
position: absolute
right: 20rpx
top: 20rpx
width: 60rpx
height: 60rpx
display: flex
align-items: center
justify-content: center
z-index: 10
.auth-header
text-align: center
2026-01-27 13:58:46 +08:00
margin-bottom: 20rpx
2025-10-20 11:43:44 +08:00
.auth-logo
2026-01-27 13:58:46 +08:00
width: 90rpx
height: 90rpx
2025-10-20 11:43:44 +08:00
margin: 0 auto 24rpx
.auth-title
font-size: 36rpx
font-weight: 600
color: #333333
margin-bottom: 12rpx
.auth-subtitle
font-size: 28rpx
color: #666666
2025-10-21 22:58:47 +08:00
.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
2026-01-27 13:58:46 +08:00
.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
2025-10-20 11:43:44 +08:00
.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
2025-10-20 16:15:29 +08:00
background: linear-gradient(135deg, #256BFA 0%, #1E5BFF 100%)
2025-10-20 11:43:44 +08:00
color: #FFFFFF
2025-10-20 16:15:29 +08:00
box-shadow: 0 8rpx 20rpx rgba(37, 107, 250, 0.3)
2025-10-20 11:43:44 +08:00
&.secondary
background: #F7F8FA
color: #666666
text
margin-left: 12rpx
.auth-agreement
2026-02-04 13:17:01 +08:00
display: flex
align-items: center
justify-content: center
2025-10-20 11:43:44 +08:00
font-size: 24rpx
color: #999999
line-height: 1.6
2026-02-04 13:17:01 +08:00
flex-wrap: wrap
gap: 8rpx
.agreement-checkbox
display: flex
align-items: center
cursor: pointer
.agreement-text
margin-left: 8rpx
color: #666666
2025-10-20 11:43:44 +08:00
.link
color: #256BFA
text-decoration: underline
// 按钮重置样式
button::after
border: none
</style>