feta : 新增实名认证判断 公司/岗位收藏 需要认证

招聘会预约        需要认证
外部岗位申请    需要认证
内部岗位申请    不需要认证
我的页面-认证状态 改为真实数据
This commit is contained in:
2025-12-25 15:09:09 +08:00
parent ff71c50b9c
commit 1b332fbad8
9 changed files with 785 additions and 15 deletions

View File

@@ -75,8 +75,9 @@ import { reactive, inject, watch, ref, onMounted, computed } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app'; import { onLoad, onShow } from '@dcloudio/uni-app';
import dictLabel from '@/components/dict-Label/dict-Label.vue'; import dictLabel from '@/components/dict-Label/dict-Label.vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const { isMiniProgram } = storeToRefs(useUserStore());
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore());
const { checkAuth } = useUserStore();
import useLocationStore from '@/stores/useLocationStore'; import useLocationStore from '@/stores/useLocationStore';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction'); const { $api, navTo, vacanciesTo, navBack } = inject('globalFunction');
@@ -111,6 +112,9 @@ onLoad((options) => {
}); });
function companyCollection() { function companyCollection() {
if (!checkAuth()) {
return
}
if (dataType.value === 2) { if (dataType.value === 2) {
// 第三方数据收藏逻辑 // 第三方数据收藏逻辑
const id = companyInfo.value.id; const id = companyInfo.value.id;

View File

@@ -112,6 +112,7 @@ import { storeToRefs } from 'pinia';
const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore()); const { longitudeVal, latitudeVal } = storeToRefs(useLocationStore());
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const { isMiniProgram } = storeToRefs(useUserStore()); const { isMiniProgram } = storeToRefs(useUserStore());
const { checkAuth } = useUserStore();
import { playTextDirectly } from '@/hook/useTTSPlayer-all-in-one'; import { playTextDirectly } from '@/hook/useTTSPlayer-all-in-one';
const isExpanded = ref(false); const isExpanded = ref(false);
@@ -204,6 +205,10 @@ function expand() {
// 取消/预约招聘会 // 取消/预约招聘会
function applyExhibitors() { function applyExhibitors() {
if (!checkAuth()) {
return
}
const fairId = fairInfo.value.zphID; const fairId = fairInfo.value.zphID;
if (fairInfo.value.isCollection) { if (fairInfo.value.isCollection) {
$api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => { $api.createRequest(`/app/fair/collection/${fairId}`, {}, 'DELETE').then((resData) => {

View File

@@ -187,6 +187,7 @@ import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
import { playTextDirectly } from '@/hook/useTTSPlayer-all-in-one'; import { playTextDirectly } from '@/hook/useTTSPlayer-all-in-one';
const { isMiniProgram, hasLogin } = storeToRefs(useUserStore()); const { isMiniProgram, hasLogin } = storeToRefs(useUserStore());
const { checkAuth } = useUserStore();
const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction'); const { $api, navTo, getLenPx, parseQueryParams, navBack, isEmptyObject } = inject('globalFunction');
import config from '@/config.js'; import config from '@/config.js';
@@ -322,10 +323,14 @@ function getCompetivetuveness(jobId) {
} }
} }
// 申请岗位 // 申请岗位 第三方需要认证
function jobApply() { function jobApply() {
if (dataType.value === 2) { if (dataType.value === 2) {
// 第三方数据申请逻辑 // 第三方数据申请逻辑
if (!checkAuth()) {
return
}
const params = { const params = {
jobid: jobInfo.value.id, jobid: jobInfo.value.id,
jobname: jobInfo.value.gwmc, jobname: jobInfo.value.gwmc,
@@ -359,8 +364,11 @@ function jobApply() {
} }
} }
// 取消/收藏岗位 // 取消/收藏岗位 都需要认证
function jobCollection() { function jobCollection() {
if (!checkAuth()) {
return
}
if (dataType.value === 2) { if (dataType.value === 2) {
// 第三方数据收藏逻辑 // 第三方数据收藏逻辑
const id = jobInfo.value.id; const id = jobInfo.value.id;

View File

@@ -63,7 +63,14 @@
{ {
"path": "pages/search/search", "path": "pages/search/search",
"style": { "style": {
"navigationBarTitleText": "", "navigationBarTitleText": "搜索",
"navigationStyle": "custom"
}
},
{
"path": "pages/auth/index",
"style": {
"navigationBarTitleText": "实名认证",
"navigationStyle": "custom" "navigationStyle": "custom"
} }
} }

729
pages/auth/index.vue Normal file
View File

@@ -0,0 +1,729 @@
<template>
<AppLayout title="">
<template #headerleft v-if="isMiniProgram">
<view >
<image class="btnback" src="@/static/icon/back.png" @click="navBack"></image>
</view>
</template>
<view class="auth-container">
<view class="auth-header">
<view class="auth-title">身份认证</view>
<view class="auth-subtitle">请填写您的身份信息进行验证</view>
</view>
<view class="auth-form">
<view class="form-item" >
<view class="form-label">
身份证号
<text v-if="idCardError" class="error-text">{{ idCardError }}</text>
</view>
<view class="form-input-wrapper" :class="{ 'error': idCardError }">
<input
class="form-input"
type="idcard"
v-model="formData.idCard"
placeholder="请输入18位身份证号码"
maxlength="18"
@input="onIdCardInput"
@blur="validateIdCard"
/>
<view class="input-clear" v-if="formData.idCard" @click="clearField('idCard')">
<my-icons type="close-circle-filled" size="36" color="#ccc"></my-icons>
</view>
</view>
</view>
<!-- 手机号 -->
<view class="form-item" >
<view class="form-label">
手机号
<text v-if="phoneError" class="error-text">{{ phoneError }}</text>
</view>
<view class="form-input-wrapper" :class="{ 'error': phoneError }">
<input
class="form-input"
type="number"
v-model="formData.phone"
placeholder="请输入11位手机号码"
maxlength="11"
@input="onPhoneInput"
@blur="validatePhone"
/>
<view class="input-clear" v-if="formData.phone" @click="clearField('phone')">
<my-icons type="close-circle-filled" size="36" color="#ccc"></my-icons>
</view>
</view>
</view>
<!-- 验证码 -->
<view class="form-item" >
<view class="form-label">
验证码
<text v-if="codeError" class="error-text">{{ codeError }}</text>
</view>
<view class="form-input-wrapper" :class="{ 'error': codeError }">
<input
class="form-input code-input"
type="number"
v-model="formData.code"
placeholder="请输入验证码"
maxlength="6"
@input="onCodeInput"
@blur="validateCode"
/>
<view class="send-code-btn"
:class="{ 'disabled': !canSendCode }"
@click="sendCode">
{{ codeBtnText }}
</view>
</view>
</view>
</view>
<!-- 认证按钮 -->
<view class="auth-btn-container">
<button class="auth-btn" :class="{ 'disabled': !canSubmit }" @click="submitAuth">
确认认证
</button>
<view class="auth-tips">
认证信息仅用于身份验证我们将严格保护您的隐私
</view>
</view>
<!-- 认证状态提示 -->
<view v-if="authStatus" class="auth-status" :class="authStatus.type">
<my-icons :type="authStatus.icon" size="48" :color="authStatus.color"></my-icons>
<text class="status-text">{{ authStatus.message }}</text>
</view>
</view>
</AppLayout>
</template>
<script setup>
import { reactive, ref, computed, onMounted ,onUnmounted ,inject} from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { storeToRefs } from 'pinia';
import useUserStore from '@/stores/useUserStore';
const { $api ,navBack} = inject('globalFunction');
const { isMiniProgram } = storeToRefs(useUserStore());
// 表单数据
const formData = ref({
idCard: '',
phone: '',
code: ''
});
// 错误提示
const idCardError = ref('');
const phoneError = ref('');
const codeError = ref('');
// 验证码倒计时
const codeCountdown = ref(0);
const codeTimer = ref(null);
const codeBtnText = ref('发送验证码');
// 认证状态
const authStatus = ref(null);
// 计算属性
const canSendCode = computed(() => {
return codeCountdown.value === 0 && formData.value.phone.length === 11 && !phoneError.value;
});
const canSubmit = computed(() => {
return formData.value.idCard && formData.value.phone && formData.value.code &&
!idCardError.value && !phoneError.value && !codeError.value;
});
// 身份证输入处理
const onIdCardInput = (e) => {
formData.value.idCard = e.detail.value.toUpperCase();
idCardError.value = '';
};
// 手机号输入处理
const onPhoneInput = (e) => {
formData.value.phone = e.detail.value.replace(/[^\d]/g, '');
phoneError.value = '';
// 如果手机号变化且验证码已发送,清空验证码
if (formData.value.code) {
formData.value.code = '';
codeError.value = '';
}
};
// 验证码输入处理
const onCodeInput = (e) => {
formData.value.code = e.detail.value.replace(/[^\d]/g, '');
codeError.value = '';
};
// 清空字段
const clearField = (field) => {
formData.value[field] = '';
switch(field) {
case 'idCard':
idCardError.value = '';
break;
case 'phone':
phoneError.value = '';
break;
case 'code':
codeError.value = '';
break;
}
};
// 验证身份证号
const validateIdCard = () => {
if (!formData.value.idCard) {
idCardError.value = '请输入身份证号码';
return false;
}
const idCard = formData.value.idCard.trim();
if (idCard.length !== 18) {
idCardError.value = '身份证号码必须为18位';
return false;
}
// 身份证号格式校验
const idCardPattern = /^\d{17}[\dXx]$/;
if (!idCardPattern.test(idCard)) {
idCardError.value = '身份证号码格式不正确';
return false;
}
// 校验码验证
if (!validateIdCardCheckCode(idCard)) {
idCardError.value = '身份证号码校验失败';
return false;
}
idCardError.value = '';
return true;
};
// 身份证校验码验证
const validateIdCardCheckCode = (idCard) => {
if (idCard.length !== 18) return false;
const weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(idCard[i]) * weight[i];
}
const checkCode = checkCodes[sum % 11];
return checkCode === idCard[17].toUpperCase();
};
// 验证手机号
const validatePhone = () => {
if (!formData.value.phone) {
phoneError.value = '请输入手机号码';
return false;
}
const phone = formData.value.phone.trim();
if (phone.length !== 11) {
phoneError.value = '手机号码必须为11位';
return false;
}
// 手机号格式校验
const phonePattern = /^1[3-9]\d{9}$/;
if (!phonePattern.test(phone)) {
phoneError.value = '手机号码格式不正确';
return false;
}
phoneError.value = '';
return true;
};
// 验证验证码
const validateCode = () => {
if (!formData.value.code) {
codeError.value = '请输入验证码';
return false;
}
if (formData.value.code.length < 4) {
codeError.value = '请输入正确的验证码';
return false;
}
codeError.value = '';
return true;
};
// 发送验证码
const sendCode = async () => {
if (!canSendCode.value) {
if (!formData.value.phone) {
phoneError.value = '请输入手机号码';
} else if (formData.value.phone.length !== 11) {
phoneError.value = '手机号码必须为11位';
} else if (phoneError.value) {
// 已有错误提示
}
return;
}
// 验证手机号格式
if (!validatePhone()) {
return;
}
// 开始倒计时
codeCountdown.value = 60;
updateCodeBtnText();
codeTimer.value = setInterval(() => {
codeCountdown.value--;
updateCodeBtnText();
if (codeCountdown.value <= 0) {
clearInterval(codeTimer.value);
codeTimer.value = null;
}
}, 1000);
try {
// 调用发送短信验证码接口
const params = {
phone: formData.value.phone,
type: 'auth' // 身份认证类型
};
await $api.createRequest('/app/auth/send-code', params, 'post');
// 发送成功提示
showAuthStatus('success', '验证码已发送至您的手机');
if (isMachineEnv.value) {
playTextDirectly('验证码已发送,请注意查收');
}
} catch (error) {
// 发送失败,重置倒计时
codeCountdown.value = 0;
clearInterval(codeTimer.value);
codeTimer.value = null;
updateCodeBtnText();
// 错误提示
showAuthStatus('error', '验证码发送失败,请重试');
if (isMachineEnv.value) {
playTextDirectly('验证码发送失败');
}
}
};
// 更新验证码按钮文字
const updateCodeBtnText = () => {
if (codeCountdown.value > 0) {
codeBtnText.value = `${codeCountdown.value}s后重新发送`;
} else {
codeBtnText.value = '发送验证码';
}
};
// 显示认证状态
const showAuthStatus = (type, message) => {
const statusMap = {
success: {
icon: 'check-circle-filled',
color: '#52c41a',
message
},
error: {
icon: 'close-circle-filled',
color: '#ff4d4f',
message
},
loading: {
icon: 'loading',
color: '#1677ff',
message
}
};
authStatus.value = {
type,
...statusMap[type]
};
// 3秒后自动清除成功/错误状态
if (type !== 'loading') {
setTimeout(() => {
authStatus.value = null;
}, 3000);
}
};
// 提交认证
const submitAuth = async () => {
if (!canSubmit.value) {
// 触发表单验证
validateIdCard();
validatePhone();
validateCode();
return;
}
// 显示加载状态
showAuthStatus('loading', '正在认证中...');
try {
// 调用身份认证接口
const params = {
idCard: formData.value.idCard.toUpperCase(),
phone: formData.value.phone,
code: formData.value.code
};
const result = await $api.createRequest('/app/auth/verify', params, 'post');
// 认证成功
showAuthStatus('success', '身份认证成功');
// 语音提示
if (isMachineEnv.value) {
playTextDirectly('身份认证成功');
}
// 保存认证信息到store
useUserStore().getUserResume().then(()=>{
setTimeout(() => {
navBack()
}, 500);
})
} catch (error) {
// 认证失败
let errorMsg = '身份认证失败';
if (error.code === 'ID_CARD_ERROR') {
idCardError.value = '身份证号验证失败';
errorMsg = '身份证信息有误';
} else if (error.code === 'PHONE_ERROR') {
phoneError.value = '手机号与身份证不匹配';
errorMsg = '手机号与身份证信息不匹配';
} else if (error.code === 'CODE_ERROR') {
codeError.value = '验证码错误或已过期';
errorMsg = '验证码错误,请重新获取';
}
showAuthStatus('error', errorMsg);
// 语音提示
if (isMachineEnv.value) {
playTextDirectly(errorMsg);
}
}
};
// TTS语音播放假设已引入
const playTextDirectly = (text) => {
// 这里调用TTS功能
console.log('TTS播放:', text);
};
// 组件卸载时清理定时器
onUnmounted(() => {
if (codeTimer.value) {
clearInterval(codeTimer.value);
}
});
</script>
<style lang="scss" scoped>
.auth-container {
width: 100%;
height: 100%;
padding: 20rpx 40rpx 40rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.auth-header {
text-align: center;
margin-bottom: 60rpx;
}
.auth-title {
font-size: 48rpx;
font-weight: 600;
color: #1677ff;
margin-bottom: 16rpx;
}
.auth-subtitle {
font-size: 28rpx;
color: #666;
}
.auth-form {
flex: 1;
}
.form-item {
margin-bottom: 48rpx;
animation: slideIn 0.3s ease-out;
animation-fill-mode: both;
&:nth-child(1) { animation-delay: 0.1s; }
&:nth-child(2) { animation-delay: 0.2s; }
&:nth-child(3) { animation-delay: 0.3s; }
}
.form-label {
font-size: 28rpx;
color: #333;
margin-bottom: 20rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.error-text {
font-size: 24rpx;
color: #ff4d4f;
flex: 1;
text-align: right;
margin-left: 20rpx;
}
.form-input-wrapper {
position: relative;
height: 96rpx;
border: 2rpx solid #e8e8e8;
border-radius: 16rpx;
background: #fff;
display: flex;
align-items: center;
padding: 0 30rpx;
transition: all 0.3s;
&:focus-within {
border-color: #1677ff;
box-shadow: 0 0 0 2rpx rgba(22, 119, 255, 0.1);
}
}
.form-input {
flex: 1;
height: 100%;
font-size: 32rpx;
color: #333;
&::placeholder {
color: #999;
font-size: 28rpx;
}
}
.code-input {
padding-right: 220rpx;
}
.input-clear {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
margin-left: 20rpx;
}
.send-code-btn {
position: absolute;
right: 20rpx;
top: 50%;
transform: translateY(-50%);
height: 64rpx;
min-width: 120rpx;
padding: 0 20rpx;
background: #1677ff;
border-radius: 12rpx;
font-size: 26rpx;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
&:active {
background: #0958d9;
transform: translateY(-50%) scale(0.98);
}
&.disabled {
background: #d9d9d9;
color: #999;
}
}
.auth-btn-container {
margin-top: 40rpx;
}
.auth-btn {
width: 100%;
height: 100rpx;
background: #1677ff;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 500;
color: #fff;
border: none;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
&:active {
background: #0958d9;
transform: scale(0.98);
}
&.disabled {
background: #d9d9d9;
color: #999;
}
}
.auth-tips {
font-size: 24rpx;
color: #999;
text-align: center;
margin-top: 24rpx;
line-height: 1.5;
}
.auth-status {
margin-top: 40rpx;
padding: 32rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 20rpx;
&.success {
background: #f6ffed;
border: 1rpx solid #b7eb8f;
}
&.error {
background: #fff2f0;
border: 1rpx solid #ffccc7;
}
&.loading {
background: #f0f5ff;
border: 1rpx solid #adc6ff;
}
}
.status-text {
font-size: 28rpx;
color: #333;
}
/* 动画效果 */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.btnback{
width: 64rpx;
height: 64rpx;
}
</style>
<style lang="stylus" scoped>
.back-button {
position: absolute;
left: 40rpx;
top: 40rpx;
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: rgba(0, 0, 0, 0.05);
&:active {
background: rgba(0, 0, 0, 0.1);
}
}
.loading-spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.form-input:focus {
outline: none;
}
.code-input-container {
position: relative;
&:after {
content: '';
position: absolute;
right: 200rpx;
top: 20rpx;
bottom: 20rpx;
width: 1rpx;
background: #e8e8e8;
}
}
.form-input-wrapper.error {
border-color: #ff4d4f;
animation: shake 0.5s;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-10rpx); }
20%, 40%, 60%, 80% { transform: translateX(10rpx); }
}
</style>

View File

@@ -49,12 +49,12 @@
</view> </view>
<view class="card-main"> <view class="card-main">
<view class="main-title">服务专区</view> <view class="main-title">服务专区</view>
<view class="main-row btn-feel" @click="selectFile"> <view class="main-row btn-feel" @click="goAuth">
<view class="row-left"> <view class="row-left">
<image class="left-img" src="@/static/icon/server1.png"></image> <image class="left-img" src="@/static/icon/server1.png"></image>
<text class="left-text">实名认证</text> <text class="left-text">实名认证</text>
</view> </view>
<view class="row-right">已认证</view> <view class="row-right">{{isAuth ? '已认证' : '未认证'}}</view>
</view> </view>
<view v-if="!isMachineEnv" class="main-row btn-feel" @click="handleItemClick('素质测评')"> <view v-if="!isMachineEnv" class="main-row btn-feel" @click="handleItemClick('素质测评')">
<view class="row-left"> <view class="row-left">
@@ -112,7 +112,7 @@ const { $api, navTo } = inject('globalFunction');
import useUserStore from '@/stores/useUserStore'; import useUserStore from '@/stores/useUserStore';
const popup = ref(null); const popup = ref(null);
const { userInfo, Completion, counts, isMachineEnv } = storeToRefs(useUserStore()); const { userInfo, Completion, counts, isMachineEnv, isAuth } = storeToRefs(useUserStore());
import useScreenStore from '@/stores/useScreenStore'; import useScreenStore from '@/stores/useScreenStore';
const screenStore = useScreenStore(); const screenStore = useScreenStore();
@@ -144,12 +144,10 @@ function confirm() {
const isAbove90 = (percent) => parseFloat(percent) < 90; const isAbove90 = (percent) => parseFloat(percent) < 90;
function selectFile() { function goAuth() {
// FileUploader.showMenuAndUpload({ if(!isAuth.value){
// success: function (res) { navTo('/pages/auth/index')
// alert('上传成功: ' + JSON.stringify(res)); }
// },
// });
} }
function chooseFileUploadTest(pam) {} function chooseFileUploadTest(pam) {}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -2,7 +2,8 @@ import {
defineStore defineStore
} from 'pinia'; } from 'pinia';
import { import {
ref ref,
inject
} from 'vue' } from 'vue'
import { import {
createRequest createRequest
@@ -17,6 +18,7 @@ import {
import { import {
msg, msg,
$api, $api,
navTo
} from '../common/globalFunction'; } from '../common/globalFunction';
import baseDB from '@/utils/db.js'; import baseDB from '@/utils/db.js';
@@ -61,6 +63,7 @@ const useUserStore = defineStore("user", () => {
const counts = ref({}) const counts = ref({})
const isMiniProgram = ref(false) const isMiniProgram = ref(false)
const isMachineEnv = ref(false) const isMachineEnv = ref(false)
const isAuth = ref(false) //是否认证(身份证+手机号)
const login = (value) => { const login = (value) => {
hasLogin.value = true; hasLogin.value = true;
@@ -135,6 +138,19 @@ const useUserStore = defineStore("user", () => {
userInfo.value = values.data; userInfo.value = values.data;
// role.value = values.role; // role.value = values.role;
hasLogin.value = true; hasLogin.value = true;
isAuth.value = values.data?.isCert ?? false
}
const checkAuth = () => {
if (!hasLogin.value) {
logOut()
return false //验证失败
}
if (hasLogin.value && !isAuth.value) {
navTo('/pages/auth/index')
return false //验证失败
}
return true // 验证通过
} }
@@ -186,7 +202,9 @@ const useUserStore = defineStore("user", () => {
isMiniProgram, isMiniProgram,
changMiniProgramAppStatus, changMiniProgramAppStatus,
changMachineEnv, changMachineEnv,
isMachineEnv isMachineEnv,
isAuth,
checkAuth
} }
}, { }, {
unistorage: true, unistorage: true,

View File

@@ -137,6 +137,7 @@ export function createRequest(url, data = {}, method = 'GET', loading = false, h
} = resData.data } = resData.data
if (code === 200) { if (code === 200) {
resolve(resData.data) resolve(resData.data)
console.log(resData.data.data,'接口解密')
return return
} }
if (msg) { if (msg) {