一体机登录功能开发
This commit is contained in:
@@ -44,10 +44,22 @@ export function register(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 身份证号码登录
|
||||
export function idCardLogin(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/app/idCardLogin',
|
||||
data,
|
||||
headers: {
|
||||
isToken: false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取用户详细信息
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: '/getInfo',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { useReadMsg } from '@/stores/useReadMsg';
|
||||
import { checkLoginAndNavigate } from '@/utils/loginHelper';
|
||||
|
||||
const props = defineProps({
|
||||
currentPage: {
|
||||
@@ -157,77 +158,75 @@ watch(() => userInfo.value, (newUserInfo, oldUserInfo) => {
|
||||
const switchTab = (item, index) => {
|
||||
console.log('switchTab called', item, index);
|
||||
|
||||
// 检查是否为"发布岗位"页面,需要判断企业信息是否完整
|
||||
if (item.path === '/pages/job/publishJob') {
|
||||
// 检查用户是否已登录
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
const hasLogin = userStore.hasLogin;
|
||||
|
||||
if (!token || !hasLogin) {
|
||||
// 未登录,发送事件显示登录弹窗
|
||||
uni.$emit('showLoginModal');
|
||||
return; // 不进行页面跳转
|
||||
}
|
||||
|
||||
// 已登录,检查企业信息是否完整
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeUserInfo = userInfo.value || {};
|
||||
const currentUserInfo = storeUserInfo.id ? storeUserInfo : cachedUserInfo;
|
||||
|
||||
// 判断企业信息字段company是否为null或undefined
|
||||
if (!currentUserInfo.company || currentUserInfo.company === null) {
|
||||
// 企业信息为空,跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info',
|
||||
});
|
||||
} else {
|
||||
// 企业信息完整,跳转到发布岗位页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/job/publishJob',
|
||||
});
|
||||
}
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
}
|
||||
// 检查是否为需要登录的页面
|
||||
const loginRequiredPages = [
|
||||
'/pages/job/publishJob',
|
||||
'/pages/mine/mine',
|
||||
'/pages/mine/company-mine'
|
||||
];
|
||||
|
||||
// 检查是否为"我的"页面,需要登录验证和用户类型判断
|
||||
if (item.path === '/pages/mine/mine') {
|
||||
if (loginRequiredPages.includes(item.path)) {
|
||||
// 检查用户是否已登录
|
||||
const token = uni.getStorageSync('token') || '';
|
||||
const hasLogin = userStore.hasLogin;
|
||||
|
||||
if (!token || !hasLogin) {
|
||||
// 未登录,发送事件显示登录弹窗
|
||||
uni.$emit('showLoginModal');
|
||||
// 未登录,根据平台类型跳转到对应的登录页面
|
||||
checkLoginAndNavigate();
|
||||
return; // 不进行页面跳转
|
||||
}
|
||||
|
||||
// 已登录,根据用户类型跳转到不同的"我的"页面
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
|
||||
|
||||
// 获取用户类型
|
||||
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
|
||||
|
||||
let targetPath = '/pages/mine/mine'; // 默认求职者页面
|
||||
|
||||
if (userType === 0) {
|
||||
// 企业用户,跳转到企业我的页面
|
||||
targetPath = '/pages/mine/company-mine';
|
||||
} else {
|
||||
// 求职者或其他用户类型,跳转到普通我的页面
|
||||
targetPath = '/pages/mine/mine';
|
||||
// 已登录,处理特定页面的逻辑
|
||||
if (item.path === '/pages/job/publishJob') {
|
||||
// 检查企业信息是否完整
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeUserInfo = userInfo.value || {};
|
||||
const currentUserInfo = storeUserInfo.id ? storeUserInfo : cachedUserInfo;
|
||||
|
||||
// 判断企业信息字段company是否为null或undefined
|
||||
if (!currentUserInfo.company || currentUserInfo.company === null) {
|
||||
// 企业信息为空,跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info',
|
||||
});
|
||||
} else {
|
||||
// 企业信息完整,跳转到发布岗位页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/job/publishJob',
|
||||
});
|
||||
}
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
}
|
||||
|
||||
// 跳转到对应的页面
|
||||
uni.navigateTo({
|
||||
url: targetPath,
|
||||
});
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
if (item.path === '/pages/mine/mine') {
|
||||
// 根据用户类型跳转到不同的"我的"页面
|
||||
const cachedUserInfo = uni.getStorageSync('userInfo') || {};
|
||||
const storeIsCompanyUser = userInfo.value?.isCompanyUser;
|
||||
const cachedIsCompanyUser = cachedUserInfo.isCompanyUser;
|
||||
|
||||
// 获取用户类型
|
||||
const userType = Number(storeIsCompanyUser !== undefined ? storeIsCompanyUser : (cachedIsCompanyUser !== undefined ? cachedIsCompanyUser : 1));
|
||||
|
||||
let targetPath = '/pages/mine/mine'; // 默认求职者页面
|
||||
|
||||
if (userType === 0) {
|
||||
// 企业用户,跳转到企业我的页面
|
||||
targetPath = '/pages/mine/company-mine';
|
||||
} else {
|
||||
// 求职者或其他用户类型,跳转到普通我的页面
|
||||
targetPath = '/pages/mine/mine';
|
||||
}
|
||||
|
||||
// 跳转到对应的页面
|
||||
uni.navigateTo({
|
||||
url: targetPath,
|
||||
});
|
||||
|
||||
currentItem.value = item.id;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为 tabBar 页面
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<!-- Logo和标题 -->
|
||||
<view class="auth-header">
|
||||
<image class="auth-logo" src="@/static/logo.png" mode="aspectFit"></image>
|
||||
<image class="auth-logo" src="@/static/logo2-S.png" mode="aspectFit"></image>
|
||||
<view class="auth-title">欢迎使用就业服务</view>
|
||||
<view class="auth-subtitle">需要您授权手机号登录</view>
|
||||
</view>
|
||||
@@ -227,65 +227,12 @@ const wxLogin = () => {
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
// H5网页微信登录逻辑
|
||||
uni.showLoading({ title: '登录中...' });
|
||||
|
||||
// 获取微信授权code
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: (loginRes) => {
|
||||
console.log('微信登录成功:', loginRes);
|
||||
|
||||
// 调用后端接口进行登录
|
||||
$api.createRequest('/app/appLogin', {
|
||||
code: loginRes.code,
|
||||
userType: userType.value
|
||||
}, 'post').then((resData) => {
|
||||
uni.hideLoading();
|
||||
|
||||
if (resData.token) {
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
console.log(resData, 'resData.isCompanyUser');
|
||||
// 更新用户类型到缓存
|
||||
if (resData.isCompanyUser) {
|
||||
console.log(resData.isCompanyUser, 'resData.isCompanyUser');
|
||||
const userInfo = uni.getStorageSync('userInfo') || {};
|
||||
userInfo.isCompanyUser = Number(resData.isCompanyUser); // 0-企业用户,1-求职者
|
||||
uni.setStorageSync('userInfo', userInfo);
|
||||
}
|
||||
|
||||
$api.msg('登录成功');
|
||||
close();
|
||||
emit('success');
|
||||
|
||||
if (!resume.data.jobTitleId) {
|
||||
if (userType.value === 1) {
|
||||
// 求职者跳转到个人信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/complete-info?step=1'
|
||||
});
|
||||
} else if (userType.value === 0) {
|
||||
// 招聘者跳转到企业信息补全页面
|
||||
uni.navigateTo({
|
||||
url: '/pages/complete-info/company-info'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$api.msg('登录失败,请重试');
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.hideLoading();
|
||||
$api.msg(err.msg || '登录失败,请重试');
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
uni.hideLoading();
|
||||
console.error('微信登录失败:', err);
|
||||
$api.msg('微信登录失败');
|
||||
}
|
||||
// H5端跳转到H5登录页面
|
||||
close();
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/h5-login'
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
@@ -561,4 +508,3 @@ defineExpose({
|
||||
button::after
|
||||
border: none
|
||||
</style>
|
||||
|
||||
|
||||
@@ -338,19 +338,19 @@
|
||||
if (diffSeconds < 60) {
|
||||
return '刚刚发布';
|
||||
} else if (diffMinutes < 60) {
|
||||
return `${diffMinutes}分钟前发布`;
|
||||
return `${diffMinutes}分钟前`;
|
||||
} else if (diffHours < 24) {
|
||||
return `${diffHours}小时前发布`;
|
||||
return `${diffHours}小时前`;
|
||||
} else if (diffDays === 1) {
|
||||
return '昨天发布';
|
||||
} else if (diffDays < 7) {
|
||||
return `${diffDays}天前发布`;
|
||||
return `${diffDays}天前`;
|
||||
} else if (diffWeeks < 4) {
|
||||
return `${diffWeeks}周前发布`;
|
||||
return `${diffWeeks}周前`;
|
||||
} else if (diffMonths < 12) {
|
||||
return `${diffMonths}个月前发布`;
|
||||
return `${diffMonths}个月前`;
|
||||
} else {
|
||||
return `${diffYears}年前发布`;
|
||||
return `${diffYears}年前`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('格式化发布时间失败:', error);
|
||||
|
||||
15
pages.json
15
pages.json
@@ -111,6 +111,20 @@
|
||||
"style": {
|
||||
"navigationBarTitleText": "编辑联系人"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/h5-login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/login/id-card-login",
|
||||
"style": {
|
||||
"navigationBarTitleText": "社保登录",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
],
|
||||
"subpackages": [
|
||||
@@ -658,4 +672,3 @@
|
||||
},
|
||||
"uniIdRouter": {}
|
||||
}
|
||||
|
||||
@@ -425,6 +425,7 @@ const { $api, navTo, vacanciesTo, formatTotal, config } = inject('globalFunction
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
import { checkLoginAndNavigate, getPlatformType } from '@/utils/loginHelper';
|
||||
const { userInfo, hasLogin, token } = storeToRefs(useUserStore());
|
||||
|
||||
// 计算是否显示求职者内容
|
||||
@@ -685,8 +686,8 @@ watch([hasLogin, userInfo], () => {
|
||||
const checkLogin = () => {
|
||||
const tokenValue = uni.getStorageSync('token') || '';
|
||||
if (!tokenValue || !hasLogin.value) {
|
||||
// 未登录,打开授权弹窗
|
||||
wxAuthLoginRef.value?.open();
|
||||
// 未登录,根据平台类型跳转到对应登录页面
|
||||
checkLoginAndNavigate();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
v-model="formData.jobLocation"
|
||||
/>
|
||||
<view class="location-icon-btn" @click="chooseLocation">
|
||||
<uni-icons type="location" size="20" color="#256BFA"></uni-icons>
|
||||
<uni-icons type="location" size="20" color="#333"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -800,25 +800,27 @@ const validateForm = () => {
|
||||
|
||||
.location-input {
|
||||
flex: 1;
|
||||
padding-right: 80rpx;
|
||||
padding-right: 100rpx;
|
||||
}
|
||||
|
||||
.location-icon-btn {
|
||||
position: absolute;
|
||||
right: -3px;
|
||||
top: 20%;
|
||||
transform: translateY(-50%);
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 44%;
|
||||
transform: translateY(-50%);
|
||||
width: 80rpx;
|
||||
height: 60rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f0f0;
|
||||
border: none;
|
||||
border-radius: 12rpx;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 99;
|
||||
|
||||
&:active {
|
||||
background: #f0f0f0;
|
||||
transform: translateY(-50%) scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
529
pages/login/h5-login.vue
Normal file
529
pages/login/h5-login.vue
Normal file
@@ -0,0 +1,529 @@
|
||||
<template>
|
||||
<view class="login-container">
|
||||
<!-- 背景装饰元素 -->
|
||||
<view class="bg-decoration">
|
||||
<view class="bg-circle bg-circle-1"></view>
|
||||
<view class="bg-circle bg-circle-2"></view>
|
||||
<view class="bg-circle bg-circle-3"></view>
|
||||
</view>
|
||||
|
||||
<!-- Logo和标题 -->
|
||||
<view class="login-header">
|
||||
<view class="logo-wrapper">
|
||||
<image class="logo" src="@/static/logo3.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="welcome-section">
|
||||
<view class="welcome-title">欢迎登录</view>
|
||||
<view class="welcome-subtitle">社保就业服务平台</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录表单 -->
|
||||
<view class="login-form">
|
||||
<!-- 账号输入 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">账号</view>
|
||||
<view class="input-item">
|
||||
<uni-icons type="person-filled" size="28" color="#4778EC"></uni-icons>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入手机号码"
|
||||
placeholder-class="placeholder"
|
||||
v-model="form.username"
|
||||
type="text"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 密码输入 -->
|
||||
<view class="input-group">
|
||||
<view class="input-label">密码</view>
|
||||
<view class="input-item">
|
||||
<uni-icons type="locked-filled" size="28" color="#4778EC"></uni-icons>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="请输入您的密码"
|
||||
placeholder-class="placeholder"
|
||||
v-model="form.password"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
/>
|
||||
<view class="password-toggle" @click="togglePasswordVisibility">
|
||||
<uni-icons :type="showPassword ? 'eye-slash-filled' : 'eye-filled'" size="28" color="#999"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 密码规则提示 -->
|
||||
<view class="password-rules">
|
||||
<view class="rule-item" :class="{ 'rule-valid': validateRule('length') }">
|
||||
<uni-icons :type="validateRule('length') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('length') ? '#52c41a' : '#999'"></uni-icons>
|
||||
<text class="rule-text">长度8位</text>
|
||||
</view>
|
||||
<view class="rule-item" :class="{ 'rule-valid': validateRule('letterNumber') }">
|
||||
<uni-icons :type="validateRule('letterNumber') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('letterNumber') ? '#52c41a' : '#999'"></uni-icons>
|
||||
<text class="rule-text">字母数字组合</text>
|
||||
</view>
|
||||
<view class="rule-item" :class="{ 'rule-valid': validateRule('upperLower') }">
|
||||
<uni-icons :type="validateRule('upperLower') ? 'checkmarkempty' : 'circle'" size="20" :color="validateRule('upperLower') ? '#52c41a' : '#999'"></uni-icons>
|
||||
<text class="rule-text">大小写字母</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<button class="login-btn" :class="{ 'login-btn-loading': loading }" @click="handleLogin">
|
||||
<view class="btn-content">
|
||||
<view class="btn-text" v-if="!loading">登录</view>
|
||||
<view class="loading-spinner" v-else></view>
|
||||
</view>
|
||||
</button>
|
||||
|
||||
<!-- 其他登录方式 -->
|
||||
<view class="other-login">
|
||||
<view class="divider">
|
||||
<view class="divider-line"></view>
|
||||
<view class="divider-text">其他登录方式</view>
|
||||
<view class="divider-line"></view>
|
||||
</view>
|
||||
|
||||
<view class="login-options">
|
||||
<view class="login-option" @click="goToIdCardLogin">
|
||||
<view class="option-icon">
|
||||
<uni-icons type="idcard" size="36" color="#4778EC"></uni-icons>
|
||||
</view>
|
||||
<view class="option-text">社保卡登录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<view class="footer">
|
||||
<view class="footer-text">© 2024 社保就业服务平台</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, inject, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import useUserStore from '@/stores/useUserStore'
|
||||
|
||||
const { $api } = inject("globalFunction")
|
||||
const userStore = useUserStore()
|
||||
|
||||
const form = reactive({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const showPassword = ref(false)
|
||||
|
||||
// 切换密码可见性
|
||||
const togglePasswordVisibility = () => {
|
||||
showPassword.value = !showPassword.value
|
||||
}
|
||||
|
||||
// 验证单个密码规则
|
||||
const validateRule = (ruleType) => {
|
||||
const password = form.password
|
||||
if (!password) return false
|
||||
|
||||
switch (ruleType) {
|
||||
case 'length':
|
||||
return password.length === 8
|
||||
case 'letterNumber':
|
||||
return /[a-zA-Z]/.test(password) && /\d/.test(password)
|
||||
case 'upperLower':
|
||||
return /[A-Z]/.test(password) && /[a-z]/.test(password)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 验证密码规则
|
||||
const validatePassword = (password) => {
|
||||
// 规则1: 必须有字母数字组合
|
||||
const hasLetterAndNumber = /[a-zA-Z]/.test(password) && /\d/.test(password)
|
||||
|
||||
// 规则2: 至少一个大写和一个小写字母
|
||||
const hasUpperCase = /[A-Z]/.test(password)
|
||||
const hasLowerCase = /[a-z]/.test(password)
|
||||
|
||||
// 规则3: 长度必须是8位
|
||||
const isLength8 = password.length === 8
|
||||
|
||||
if (!hasLetterAndNumber) {
|
||||
return '密码必须包含字母和数字组合'
|
||||
}
|
||||
if (!hasUpperCase) {
|
||||
return '密码必须包含至少一个大写字母'
|
||||
}
|
||||
if (!hasLowerCase) {
|
||||
return '密码必须包含至少一个小写字母'
|
||||
}
|
||||
if (!isLength8) {
|
||||
return '密码长度必须为8位'
|
||||
}
|
||||
|
||||
return null // 验证通过
|
||||
}
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
if (!form.username) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '请输入账号'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!form.password) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: '请输入密码'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证密码规则
|
||||
const passwordError = validatePassword(form.password)
|
||||
if (passwordError) {
|
||||
uni.showToast({
|
||||
icon: 'none',
|
||||
title: passwordError
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
loading.value = true
|
||||
|
||||
try {
|
||||
// 调用账号密码登录接口
|
||||
const res = await $api.createRequest('/app/login', {
|
||||
username: form.username,
|
||||
password: form.password
|
||||
}, 'post')
|
||||
|
||||
if (res.token) {
|
||||
// 登录成功,存储token并获取用户信息
|
||||
await userStore.loginSetToken(res.token)
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
uni.showToast({
|
||||
title: error.msg || '登录失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到身份证号码登录页面
|
||||
const goToIdCardLogin = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/login/id-card-login'
|
||||
})
|
||||
}
|
||||
|
||||
onLoad(() => {
|
||||
// 页面加载时的初始化逻辑
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.login-container
|
||||
padding: 0 40rpx
|
||||
min-height: 100vh
|
||||
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
|
||||
position: relative
|
||||
overflow: hidden
|
||||
|
||||
.bg-decoration
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
pointer-events: none
|
||||
z-index: 1
|
||||
|
||||
.bg-circle
|
||||
position: absolute
|
||||
border-radius: 50%
|
||||
background: rgba(255, 255, 255, 0.1)
|
||||
|
||||
.bg-circle-1
|
||||
width: 400rpx
|
||||
height: 400rpx
|
||||
top: -200rpx
|
||||
right: -100rpx
|
||||
|
||||
.bg-circle-2
|
||||
width: 300rpx
|
||||
height: 300rpx
|
||||
bottom: 100rpx
|
||||
left: -150rpx
|
||||
|
||||
.bg-circle-3
|
||||
width: 200rpx
|
||||
height: 200rpx
|
||||
bottom: -50rpx
|
||||
right: 100rpx
|
||||
|
||||
.login-header
|
||||
position: relative
|
||||
z-index: 2
|
||||
text-align: center
|
||||
margin-bottom: 80rpx
|
||||
padding-top: 120rpx
|
||||
|
||||
.logo-wrapper
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.logo
|
||||
width: 441rpx
|
||||
height: 160rpx
|
||||
filter: drop-shadow(0 8rpx 20rpx rgba(0, 0, 0, 0.2))
|
||||
|
||||
.welcome-section
|
||||
.welcome-title
|
||||
font-size: 48rpx
|
||||
font-weight: 700
|
||||
color: #FFFFFF
|
||||
margin-bottom: 16rpx
|
||||
text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2)
|
||||
|
||||
.welcome-subtitle
|
||||
font-size: 32rpx
|
||||
color: rgba(255, 255, 255, 0.9)
|
||||
font-weight: 500
|
||||
|
||||
.login-form
|
||||
position: relative
|
||||
z-index: 2
|
||||
background: #FFFFFF
|
||||
border-radius: 32rpx
|
||||
padding: 60rpx 40rpx
|
||||
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.15)
|
||||
backdrop-filter: blur(20rpx)
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2)
|
||||
|
||||
.input-group
|
||||
margin-bottom: 48rpx
|
||||
|
||||
.input-label
|
||||
font-size: 28rpx
|
||||
font-weight: 600
|
||||
color: #333333
|
||||
margin-bottom: 20rpx
|
||||
padding-left: 10rpx
|
||||
|
||||
.input-item
|
||||
display: flex
|
||||
align-items: center
|
||||
padding: 0 32rpx
|
||||
height: 100rpx
|
||||
background: #F8F9FF
|
||||
border-radius: 25rpx
|
||||
border: 2rpx solid #E8ECFF
|
||||
transition: all 0.3s ease
|
||||
|
||||
&:focus-within
|
||||
border-color: #4778EC
|
||||
background: #FFFFFF
|
||||
box-shadow: 0 8rpx 24rpx rgba(71, 120, 236, 0.15)
|
||||
transform: translateY(-2rpx)
|
||||
|
||||
.input
|
||||
flex: 1
|
||||
height: 100%
|
||||
margin-left: 24rpx
|
||||
font-size: 32rpx
|
||||
color: #333333
|
||||
background: transparent
|
||||
font-weight: 500
|
||||
|
||||
.password-toggle
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
width: 60rpx
|
||||
height: 60rpx
|
||||
border-radius: 50%
|
||||
transition: all 0.3s ease
|
||||
cursor: pointer
|
||||
|
||||
&:active
|
||||
background: rgba(71, 120, 236, 0.1)
|
||||
transform: scale(0.9)
|
||||
|
||||
.placeholder
|
||||
font-size: 32rpx
|
||||
color: #999999
|
||||
font-weight: 400
|
||||
|
||||
.password-rules
|
||||
margin-top: 20rpx
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
gap: 20rpx
|
||||
|
||||
.rule-item
|
||||
display: flex
|
||||
align-items: center
|
||||
font-size: 24rpx
|
||||
color: #999999
|
||||
transition: all 0.3s ease
|
||||
|
||||
&.rule-valid
|
||||
color: #52c41a
|
||||
|
||||
.rule-text
|
||||
margin-left: 8rpx
|
||||
font-size: 24rpx
|
||||
|
||||
.login-btn
|
||||
width: 100%
|
||||
height: 100rpx
|
||||
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
|
||||
border-radius: 25rpx
|
||||
color: #FFFFFF
|
||||
font-size: 34rpx
|
||||
font-weight: 600
|
||||
border: none
|
||||
margin-bottom: 48rpx
|
||||
box-shadow: 0 12rpx 30rpx rgba(37, 107, 250, 0.4)
|
||||
transition: all 0.3s ease
|
||||
position: relative
|
||||
overflow: hidden
|
||||
|
||||
&:active
|
||||
transform: translateY(2rpx)
|
||||
box-shadow: 0 6rpx 20rpx rgba(37, 107, 250, 0.3)
|
||||
|
||||
&.login-btn-loading
|
||||
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
|
||||
opacity: 0.8
|
||||
|
||||
.btn-content
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
height: 100%
|
||||
|
||||
.btn-text
|
||||
font-size: 34rpx
|
||||
font-weight: 600
|
||||
|
||||
.loading-spinner
|
||||
width: 40rpx
|
||||
height: 40rpx
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.3)
|
||||
border-radius: 50%
|
||||
border-top: 4rpx solid #FFFFFF
|
||||
animation: spin 1s linear infinite
|
||||
|
||||
.other-login
|
||||
.divider
|
||||
display: flex
|
||||
align-items: center
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.divider-line
|
||||
flex: 1
|
||||
height: 1rpx
|
||||
background: #E5E5E5
|
||||
|
||||
.divider-text
|
||||
padding: 0 24rpx
|
||||
font-size: 26rpx
|
||||
color: #999999
|
||||
font-weight: 500
|
||||
|
||||
.login-options
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
.login-option
|
||||
display: flex
|
||||
flex-direction: column
|
||||
align-items: center
|
||||
padding: 20rpx 40rpx
|
||||
border-radius: 20rpx
|
||||
background: #F8F9FF
|
||||
border: 2rpx solid #E8ECFF
|
||||
transition: all 0.3s ease
|
||||
cursor: pointer
|
||||
|
||||
&:active
|
||||
background: #F0F4FF
|
||||
transform: scale(0.95)
|
||||
|
||||
.option-icon
|
||||
margin-bottom: 16rpx
|
||||
|
||||
.option-text
|
||||
font-size: 24rpx
|
||||
color: #4778EC
|
||||
font-weight: 500
|
||||
|
||||
.footer
|
||||
position: relative
|
||||
z-index: 2
|
||||
text-align: center
|
||||
margin-top: 60rpx
|
||||
padding-bottom: 40rpx
|
||||
|
||||
.footer-text
|
||||
font-size: 24rpx
|
||||
color: rgba(255, 255, 255, 0.7)
|
||||
|
||||
// 按钮重置样式
|
||||
button::after
|
||||
border: none
|
||||
|
||||
// 动画定义
|
||||
@keyframes spin
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(360deg)
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 750px)
|
||||
.login-container
|
||||
padding: 0 32rpx
|
||||
|
||||
.login-header
|
||||
padding-top: 80rpx
|
||||
margin-bottom: 60rpx
|
||||
|
||||
.logo
|
||||
width: 360rpx
|
||||
height: 130rpx
|
||||
|
||||
.welcome-title
|
||||
font-size: 40rpx
|
||||
|
||||
.welcome-subtitle
|
||||
font-size: 28rpx
|
||||
|
||||
.login-form
|
||||
padding: 48rpx 32rpx
|
||||
border-radius: 24rpx
|
||||
</style>
|
||||
372
pages/login/id-card-login.vue
Normal file
372
pages/login/id-card-login.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<view class="loading-container">
|
||||
<!-- 背景装饰元素 -->
|
||||
<view class="bg-decoration">
|
||||
<view class="bg-circle bg-circle-1"></view>
|
||||
<view class="bg-circle bg-circle-2"></view>
|
||||
<view class="bg-circle bg-circle-3"></view>
|
||||
</view>
|
||||
|
||||
<!-- Loading内容 -->
|
||||
<view class="loading-content">
|
||||
<!-- 图标区域 -->
|
||||
<view class="icon-section">
|
||||
<view class="icon-wrapper">
|
||||
<uni-icons type="idcard-filled" size="80" color="#FFFFFF"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Loading动画 -->
|
||||
<view class="loading-animation">
|
||||
<view class="spinner"></view>
|
||||
</view>
|
||||
|
||||
<!-- 文字内容 -->
|
||||
<view class="text-section">
|
||||
<view class="loading-text">登录中,请稍候...</view>
|
||||
<view class="loading-subtext">正在验证您的社保卡信息</view>
|
||||
</view>
|
||||
|
||||
<!-- 进度指示器 -->
|
||||
<view class="progress-section">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill"></view>
|
||||
</view>
|
||||
<view class="progress-text">验证中...</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部信息 -->
|
||||
<view class="footer">
|
||||
<view class="footer-text">© 2024 社保就业服务平台</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import useUserStore from '@/stores/useUserStore'
|
||||
|
||||
const { $api } = inject("globalFunction")
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 处理身份证号码登录
|
||||
const handleIdCardLogin = async (idCard) => {
|
||||
try {
|
||||
// 调用身份证号码登录接口
|
||||
const res = await $api.createRequest('/app/idCardLogin', {
|
||||
idCard: idCard
|
||||
}, 'post')
|
||||
|
||||
if (res.token) {
|
||||
// 登录成功,存储token并获取用户信息
|
||||
await userStore.loginSetToken(res.token)
|
||||
|
||||
// 获取用户详细信息
|
||||
await userStore.getUserResume()
|
||||
|
||||
uni.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '登录失败',
|
||||
icon: 'none'
|
||||
})
|
||||
// 登录失败,返回登录页面
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('身份证登录失败:', error)
|
||||
uni.showToast({
|
||||
title: error.msg || '登录失败,请重试',
|
||||
icon: 'none'
|
||||
})
|
||||
// 登录失败,返回登录页面
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
// Base64解码
|
||||
const decodeBase64 = (base64Str) => {
|
||||
try {
|
||||
// 在H5环境中使用atob解码
|
||||
// #ifdef H5
|
||||
return atob(base64Str)
|
||||
// #endif
|
||||
|
||||
// 在小程序环境中使用uni.base64ToArrayBuffer
|
||||
// #ifdef MP-WEIXIN
|
||||
const arrayBuffer = uni.base64ToArrayBuffer(base64Str)
|
||||
const uint8Array = new Uint8Array(arrayBuffer)
|
||||
let result = ''
|
||||
for (let i = 0; i < uint8Array.length; i++) {
|
||||
result += String.fromCharCode(uint8Array[i])
|
||||
}
|
||||
return result
|
||||
// #endif
|
||||
|
||||
// 在App环境中
|
||||
// #ifdef APP-PLUS
|
||||
return plus.base64.decode(base64Str)
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error('Base64解码失败:', error)
|
||||
throw new Error('身份证号码解码失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 解析URL参数
|
||||
const getUrlParams = (url) => {
|
||||
const params = {}
|
||||
const urlObj = new URL(url)
|
||||
const searchParams = new URLSearchParams(urlObj.search)
|
||||
for (const [key, value] of searchParams) {
|
||||
params[key] = value
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
// 处理第三方跳转过来的身份证登录
|
||||
if (options.idCardBase64) {
|
||||
// 直接通过参数传递
|
||||
processIdCardLogin(options.idCardBase64)
|
||||
} else {
|
||||
// 从当前URL中获取参数
|
||||
const currentPages = getCurrentPages()
|
||||
const currentPage = currentPages[currentPages.length - 1]
|
||||
const url = currentPage.$page.fullPath
|
||||
const params = getUrlParams(url)
|
||||
|
||||
if (params.idCardBase64) {
|
||||
processIdCardLogin(params.idCardBase64)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '缺少身份证号码参数',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 处理身份证登录流程
|
||||
const processIdCardLogin = async (idCardBase64) => {
|
||||
try {
|
||||
// 解码Base64身份证号码
|
||||
const idCard = decodeBase64(idCardBase64)
|
||||
|
||||
// 验证身份证号码格式(简单验证)
|
||||
if (!idCard || idCard.length < 15) {
|
||||
throw new Error('身份证号码格式不正确')
|
||||
}
|
||||
|
||||
// 调用登录接口
|
||||
await handleIdCardLogin(idCard)
|
||||
|
||||
} catch (error) {
|
||||
console.error('处理身份证登录失败:', error)
|
||||
uni.showToast({
|
||||
title: error.message || '处理身份证信息失败',
|
||||
icon: 'none'
|
||||
})
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 页面挂载后的逻辑
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.loading-container
|
||||
padding: 0 40rpx
|
||||
min-height: 100vh
|
||||
background: linear-gradient(135deg, #4778EC 0%, #256BFA 100%)
|
||||
position: relative
|
||||
overflow: hidden
|
||||
display: flex
|
||||
flex-direction: column
|
||||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
.bg-decoration
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
height: 100%
|
||||
pointer-events: none
|
||||
z-index: 1
|
||||
|
||||
.bg-circle
|
||||
position: absolute
|
||||
border-radius: 50%
|
||||
background: rgba(255, 255, 255, 0.1)
|
||||
|
||||
.bg-circle-1
|
||||
width: 400rpx
|
||||
height: 400rpx
|
||||
top: -200rpx
|
||||
right: -100rpx
|
||||
|
||||
.bg-circle-2
|
||||
width: 300rpx
|
||||
height: 300rpx
|
||||
bottom: 100rpx
|
||||
left: -150rpx
|
||||
|
||||
.bg-circle-3
|
||||
width: 200rpx
|
||||
height: 200rpx
|
||||
bottom: -50rpx
|
||||
right: 100rpx
|
||||
|
||||
.loading-content
|
||||
position: relative
|
||||
z-index: 2
|
||||
text-align: center
|
||||
color: #FFFFFF
|
||||
width: 100%
|
||||
max-width: 600rpx
|
||||
|
||||
.icon-section
|
||||
margin-bottom: 60rpx
|
||||
|
||||
.icon-wrapper
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
width: 160rpx
|
||||
height: 160rpx
|
||||
background: rgba(255, 255, 255, 0.2)
|
||||
border-radius: 50%
|
||||
backdrop-filter: blur(10rpx)
|
||||
border: 2rpx solid rgba(255, 255, 255, 0.3)
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.2)
|
||||
|
||||
.loading-animation
|
||||
margin-bottom: 60rpx
|
||||
|
||||
.spinner
|
||||
width: 80rpx
|
||||
height: 80rpx
|
||||
margin: 0 auto
|
||||
border: 6rpx solid rgba(255, 255, 255, 0.3)
|
||||
border-radius: 50%
|
||||
border-top: 6rpx solid #FFFFFF
|
||||
animation: spin 1s linear infinite
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.2)
|
||||
|
||||
.text-section
|
||||
margin-bottom: 60rpx
|
||||
|
||||
.loading-text
|
||||
font-size: 40rpx
|
||||
font-weight: 700
|
||||
margin-bottom: 20rpx
|
||||
text-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.2)
|
||||
|
||||
.loading-subtext
|
||||
font-size: 30rpx
|
||||
opacity: 0.9
|
||||
font-weight: 500
|
||||
|
||||
.progress-section
|
||||
.progress-bar
|
||||
width: 100%
|
||||
height: 8rpx
|
||||
background: rgba(255, 255, 255, 0.3)
|
||||
border-radius: 4rpx
|
||||
overflow: hidden
|
||||
margin-bottom: 20rpx
|
||||
|
||||
.progress-fill
|
||||
width: 60%
|
||||
height: 100%
|
||||
background: linear-gradient(90deg, #FFFFFF 0%, rgba(255, 255, 255, 0.8) 100%)
|
||||
border-radius: 4rpx
|
||||
animation: progress 2s ease-in-out infinite alternate
|
||||
|
||||
.progress-text
|
||||
font-size: 26rpx
|
||||
opacity: 0.8
|
||||
font-weight: 500
|
||||
|
||||
.footer
|
||||
position: absolute
|
||||
bottom: 40rpx
|
||||
left: 0
|
||||
right: 0
|
||||
text-align: center
|
||||
z-index: 2
|
||||
|
||||
.footer-text
|
||||
font-size: 24rpx
|
||||
color: rgba(255, 255, 255, 0.7)
|
||||
|
||||
// 动画定义
|
||||
@keyframes spin
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(360deg)
|
||||
|
||||
@keyframes progress
|
||||
0%
|
||||
width: 30%
|
||||
transform: translateX(-100%)
|
||||
50%
|
||||
width: 60%
|
||||
100%
|
||||
width: 30%
|
||||
transform: translateX(233%)
|
||||
|
||||
// 响应式设计
|
||||
@media (max-width: 750px)
|
||||
.loading-container
|
||||
padding: 0 32rpx
|
||||
|
||||
.loading-content
|
||||
max-width: 500rpx
|
||||
|
||||
.icon-section
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.icon-wrapper
|
||||
width: 120rpx
|
||||
height: 120rpx
|
||||
|
||||
.loading-animation
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.text-section
|
||||
margin-bottom: 40rpx
|
||||
|
||||
.loading-text
|
||||
font-size: 36rpx
|
||||
|
||||
.loading-subtext
|
||||
font-size: 28rpx
|
||||
|
||||
.progress-section
|
||||
.progress-text
|
||||
font-size: 24rpx
|
||||
</style>
|
||||
BIN
static/logo1-S.png
Normal file
BIN
static/logo1-S.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
BIN
static/logo2-S.png
Normal file
BIN
static/logo2-S.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
static/logo3.png
Normal file
BIN
static/logo3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
158
utils/loginHelper.js
Normal file
158
utils/loginHelper.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 登录帮助工具类
|
||||
* 用于根据平台类型跳转到对应的登录页面
|
||||
*/
|
||||
|
||||
import useUserStore from '@/stores/useUserStore';
|
||||
|
||||
/**
|
||||
* 检查当前平台类型
|
||||
* @returns {string} 平台类型:'mp-weixin' | 'h5' | 'app'
|
||||
*/
|
||||
export function getPlatformType() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return 'mp-weixin';
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
return 'h5';
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
return 'app';
|
||||
// #endif
|
||||
|
||||
return 'h5'; // 默认返回H5
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到对应平台的登录页面
|
||||
* @param {Object} options 跳转选项
|
||||
* @param {string} options.redirectUrl 登录成功后跳转的URL
|
||||
* @param {string} options.loginType 登录类型:'wechat' | 'account' | 'idCard'
|
||||
*/
|
||||
export function navigateToLoginPage(options = {}) {
|
||||
const { redirectUrl, loginType = 'account' } = options;
|
||||
const platform = getPlatformType();
|
||||
|
||||
let loginPage = '';
|
||||
let params = {};
|
||||
|
||||
if (redirectUrl) {
|
||||
params.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case 'mp-weixin':
|
||||
// 小程序端使用微信授权登录,直接显示微信授权弹窗
|
||||
uni.$emit('showLoginModal', { loginType: 'wechat' });
|
||||
return;
|
||||
|
||||
case 'h5':
|
||||
if (loginType === 'idCard') {
|
||||
// H5端身份证号码登录
|
||||
loginPage = '/pages/login/id-card-login';
|
||||
} else {
|
||||
// H5端账号密码登录
|
||||
loginPage = '/pages/login/h5-login';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'app':
|
||||
// App端使用微信授权登录
|
||||
uni.$emit('showLoginModal', { loginType: 'wechat' });
|
||||
return;
|
||||
|
||||
default:
|
||||
loginPage = '/pages/login/h5-login';
|
||||
}
|
||||
|
||||
if (loginPage) {
|
||||
const queryString = Object.keys(params).length > 0
|
||||
? `?${new URLSearchParams(params).toString()}`
|
||||
: '';
|
||||
|
||||
uni.navigateTo({
|
||||
url: `${loginPage}${queryString}`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查登录状态,如果未登录则跳转到对应登录页面
|
||||
* @param {Object} options 选项
|
||||
* @param {string} options.redirectUrl 登录成功后跳转的URL
|
||||
* @param {string} options.loginType 登录类型
|
||||
* @returns {boolean} 是否已登录
|
||||
*/
|
||||
export function checkLoginAndNavigate(options = {}) {
|
||||
const userStore = useUserStore();
|
||||
|
||||
if (userStore.hasLogin) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 未登录,跳转到对应登录页面
|
||||
navigateToLoginPage(options);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理身份证号码登录的URL参数
|
||||
* @param {string} url 包含参数的URL
|
||||
* @returns {Object} 解析后的参数对象
|
||||
*/
|
||||
export function parseIdCardLoginParams(url) {
|
||||
const urlObj = new URL(url, window.location.origin);
|
||||
const params = {};
|
||||
|
||||
// 获取身份证号码base64参数
|
||||
const idCardBase64 = urlObj.searchParams.get('idCardBase64');
|
||||
if (idCardBase64) {
|
||||
params.idCardBase64 = idCardBase64;
|
||||
}
|
||||
|
||||
// 获取重定向URL
|
||||
const redirectUrl = urlObj.searchParams.get('redirectUrl');
|
||||
if (redirectUrl) {
|
||||
params.redirectUrl = redirectUrl;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64解码身份证号码
|
||||
* @param {string} base64Str base64编码的字符串
|
||||
* @returns {string} 解码后的身份证号码
|
||||
*/
|
||||
export function decodeIdCardBase64(base64Str) {
|
||||
try {
|
||||
// #ifdef H5
|
||||
return atob(base64Str);
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const result = uni.base64ToArrayBuffer(base64Str);
|
||||
return String.fromCharCode.apply(null, new Uint8Array(result));
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
return plus.base64.decode(base64Str);
|
||||
// #endif
|
||||
|
||||
// 默认使用H5方式
|
||||
return atob(base64Str);
|
||||
} catch (error) {
|
||||
console.error('Base64解码失败:', error);
|
||||
throw new Error('身份证号码解码失败');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
getPlatformType,
|
||||
navigateToLoginPage,
|
||||
checkLoginAndNavigate,
|
||||
parseIdCardLoginParams,
|
||||
decodeIdCardBase64
|
||||
};
|
||||
139
登录系统实现总结.md
Normal file
139
登录系统实现总结.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 多端登录系统实现总结
|
||||
|
||||
## 项目概述
|
||||
已成功完成多端登录系统的重构,支持小程序端、H5端和App端的差异化登录方式。
|
||||
|
||||
## 实现功能
|
||||
|
||||
### 1. 多端登录策略
|
||||
- **小程序端 (MP-WEIXIN)**: 微信授权登录
|
||||
- **H5端 (H5)**: 账号密码登录 + 身份证号码登录
|
||||
- **App端 (APP-PLUS)**: 微信授权登录
|
||||
|
||||
### 2. 新增页面
|
||||
|
||||
#### 2.1 H5账号密码登录页面 (`pages/login/h5-login.vue`)
|
||||
- 账号密码输入表单
|
||||
- 登录按钮
|
||||
- 跳转到身份证号码登录的入口
|
||||
- 美观的UI设计,渐变背景和圆角卡片
|
||||
|
||||
#### 2.2 身份证号码登录页面 (`pages/login/id-card-login.vue`)
|
||||
- Loading页面设计,显示"登录中,请稍候..."
|
||||
- 支持从URL参数获取Base64编码的身份证号码
|
||||
- 多端Base64解码支持:
|
||||
- H5: `atob()`
|
||||
- 小程序: `uni.base64ToArrayBuffer()`
|
||||
- App: `plus.base64.decode()`
|
||||
- 调用后端身份证号码登录接口
|
||||
|
||||
### 3. API接口扩展
|
||||
|
||||
#### 3.1 身份证号码登录接口 (`apiRc/login.js`)
|
||||
```javascript
|
||||
export function idCardLogin(data) {
|
||||
return request({
|
||||
method: 'post',
|
||||
url: '/app/idCardLogin',
|
||||
data,
|
||||
headers: {
|
||||
isToken: false
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 登录帮助工具类 (`utils/loginHelper.js`)
|
||||
- 平台类型检测 (`getPlatformType`)
|
||||
- 登录页面跳转 (`navigateToLoginPage`)
|
||||
- 登录状态检查 (`checkLoginAndNavigate`)
|
||||
- URL参数解析 (`parseIdCardLoginParams`)
|
||||
- Base64解码 (`decodeIdCardBase64`)
|
||||
|
||||
### 5. 现有组件集成
|
||||
|
||||
#### 5.1 微信授权登录组件 (`components/wxAuthLogin/WxAuthLogin.vue`)
|
||||
- 已修复语法错误
|
||||
- H5端点击微信授权登录时跳转到H5登录页面
|
||||
- 小程序端保持原有的微信授权登录逻辑
|
||||
- App端保持原有的微信授权登录逻辑
|
||||
|
||||
#### 5.2 首页组件 (`pages/index/components/index-one.vue`)
|
||||
- 已集成登录检查逻辑
|
||||
- 在`onMounted`生命周期中调用`checkLoginAndNavigate()`
|
||||
|
||||
#### 5.3 Tabbar组件 (`components/CustomTabBar/CustomTabBar.vue`)
|
||||
- 已集成登录状态判断
|
||||
- 对于需要登录的页面(发布岗位、我的页面),检查登录状态
|
||||
- 未登录时跳转到对应登录页面
|
||||
|
||||
### 6. 路由配置 (`pages.json`)
|
||||
- 已注册新的登录页面路由
|
||||
- H5登录页面: `/pages/login/h5-login`
|
||||
- 身份证号码登录页面: `/pages/login/id-card-login`
|
||||
|
||||
## 登录流程
|
||||
|
||||
### 小程序端登录流程
|
||||
1. 用户点击需要登录的功能
|
||||
2. 显示微信授权登录弹窗
|
||||
3. 用户授权手机号
|
||||
4. 调用后端接口获取token
|
||||
5. 登录成功,跳转到目标页面
|
||||
|
||||
### H5端登录流程
|
||||
1. 用户点击需要登录的功能
|
||||
2. 跳转到H5登录页面
|
||||
3. 用户可选择:
|
||||
- 账号密码登录
|
||||
- 身份证号码登录
|
||||
4. 登录成功,跳转到目标页面
|
||||
|
||||
### 身份证号码登录流程
|
||||
1. 第三方系统跳转到身份证号码登录页面
|
||||
2. 通过URL参数传递Base64编码的身份证号码
|
||||
3. 页面自动解码身份证号码
|
||||
4. 调用后端身份证号码登录接口
|
||||
5. 获取token和用户信息
|
||||
6. 登录成功,跳转到首页
|
||||
|
||||
## 技术特点
|
||||
|
||||
### 1. 条件编译
|
||||
使用`#ifdef`、`#ifndef`实现多端差异化逻辑
|
||||
|
||||
### 2. Base64解码
|
||||
- H5: `atob(base64Str)`
|
||||
- 小程序: `uni.base64ToArrayBuffer(base64Str)`
|
||||
- App: `plus.base64.decode(base64Str)`
|
||||
|
||||
### 3. 状态管理
|
||||
使用Pinia store (`useUserStore`)管理用户状态
|
||||
|
||||
### 4. 错误处理
|
||||
完善的错误处理和用户提示
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **小程序端测试**: 验证微信授权登录功能
|
||||
2. **H5端测试**: 验证账号密码登录和身份证号码登录
|
||||
3. **身份证号码登录测试**: 模拟第三方跳转,验证Base64解码和登录流程
|
||||
4. **Tabbar导航测试**: 验证登录状态判断和页面跳转
|
||||
|
||||
## 文件清单
|
||||
- `pages/login/h5-login.vue` - H5账号密码登录页面
|
||||
- `pages/login/id-card-login.vue` - 身份证号码登录页面
|
||||
- `utils/loginHelper.js` - 登录帮助工具类
|
||||
- `apiRc/login.js` - 扩展的API接口
|
||||
- `components/wxAuthLogin/WxAuthLogin.vue` - 修复后的微信授权组件
|
||||
- `pages/index/components/index-one.vue` - 集成登录检查的首页组件
|
||||
- `components/CustomTabBar/CustomTabBar.vue` - 集成登录判断的Tabbar组件
|
||||
- `pages.json` - 页面路由配置
|
||||
|
||||
## 注意事项
|
||||
1. 身份证号码登录页面需要第三方系统通过URL参数传递`idCardBase64`
|
||||
2. 后端需要实现`/app/idCardLogin`接口
|
||||
3. 各端Base64解码方式不同,需要确保兼容性
|
||||
4. 登录成功后需要正确存储用户信息和token
|
||||
|
||||
系统已准备就绪,可以开始多端测试。
|
||||
Reference in New Issue
Block a user