Merge branch 'main' of http://124.243.245.42:3000/sdz/qingdao-employment-service
This commit is contained in:
@@ -101,10 +101,6 @@ export class FaceLoginService {
|
||||
this._retryTimer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增】生命周期 - 开始
|
||||
* 统一入口,通常在页面加载 (onLoad/onMounted) 时调用
|
||||
*/
|
||||
start(scope = null) {
|
||||
// 启动前先清理可能存在的旧状态
|
||||
this.close();
|
||||
@@ -113,10 +109,6 @@ export class FaceLoginService {
|
||||
this.init(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增】生命周期 - 关闭
|
||||
* 统一出口,通常在页面卸载 (onUnload/onUnmounted) 时调用
|
||||
*/
|
||||
close() {
|
||||
// 1. 清除正在等待执行的重试定时器
|
||||
if (this._retryTimer) {
|
||||
@@ -131,10 +123,6 @@ export class FaceLoginService {
|
||||
console.log("[FaceLogin] 服务已关闭");
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 初始化刷脸服务
|
||||
* (已修改:增加了对定时器的管理)
|
||||
*/
|
||||
async init(scope = null, retryCount = 1) {
|
||||
const params = {
|
||||
action: "initFace",
|
||||
@@ -154,7 +142,6 @@ export class FaceLoginService {
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(`[FaceLogin] 初始化失败: ${err.message}`);
|
||||
// 如果还有重试次数,且服务没有被手动 close 关闭
|
||||
if (retryCount > 0) {
|
||||
console.log("[FaceLogin] 3秒后尝试重新初始化...");
|
||||
this._retryTimer = setTimeout(() => {
|
||||
@@ -162,8 +149,7 @@ export class FaceLoginService {
|
||||
this.init(scope, retryCount - 1); // 递归重试
|
||||
}, 3000);
|
||||
} else {
|
||||
// 重试耗尽,抛出错误
|
||||
// throw err; // 可选:视业务逻辑决定是否阻断
|
||||
// throw err; // 视业务逻辑决定是否阻断
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,11 +158,9 @@ export class FaceLoginService {
|
||||
* 2. 唤起 1:N 刷脸并获取 AuthCode
|
||||
*/
|
||||
async startFaceLogin() {
|
||||
// 防御性编程:确保已初始化
|
||||
if (!this.isInitialized) {
|
||||
console.warn("[FaceLogin] 服务未初始化,尝试自动补救初始化...");
|
||||
try {
|
||||
// 尝试一次即时初始化(不重试)
|
||||
await this.init(null, 0);
|
||||
if (!this.isInitialized) throw new Error("初始化未完成");
|
||||
} catch (e) {
|
||||
@@ -218,9 +202,6 @@ export class FaceLoginService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础 Bridge 调用封装
|
||||
*/
|
||||
_bridgeCall(params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof hh === 'undefined' || !hh.call) {
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<view v-if="hasLogin" :class="{ 'match-move-top': isMachineEnv }" class="match-card-out">
|
||||
<view class="match-card">
|
||||
<image class="match-card-bg" src="@/static/icon/match-card-bg.png" />
|
||||
<view class="title">简历匹配职位</view>
|
||||
<view class="title">为您匹配的职位</view>
|
||||
<view class="match-item-container">
|
||||
<AIMatch :tags="matchTags" :loading="matchLoading" @tag-click="handleTagClick"></AIMatch>
|
||||
</view>
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
<template>
|
||||
<AppLayout title="就业服务程序">
|
||||
<AppLayout :title="pageTitle">
|
||||
<view v-if="isMachineEnv && !hasLogin" class="alipay-login-container">
|
||||
<!-- 切换 -->
|
||||
<view class="login-method-switch">
|
||||
<view
|
||||
class="method-item"
|
||||
:class="{ active: loginMethod === 'face' }"
|
||||
@click="switchLoginMethod('face')"
|
||||
>
|
||||
扫脸登录
|
||||
</view>
|
||||
<view
|
||||
class="method-item"
|
||||
:class="{ active: loginMethod === 'qrcode' }"
|
||||
@@ -17,6 +10,13 @@
|
||||
>
|
||||
扫码登录
|
||||
</view>
|
||||
<view
|
||||
class="method-item"
|
||||
:class="{ active: loginMethod === 'face' }"
|
||||
@click="switchLoginMethod('face')"
|
||||
>
|
||||
扫脸登录
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="login-scan-area">
|
||||
@@ -64,7 +64,8 @@
|
||||
</view>
|
||||
|
||||
<view class="countdown-container">
|
||||
<view class="countdown-wrapper">
|
||||
<!-- 刷脸不限时间 -->
|
||||
<view class="countdown-wrapper" v-if="loginMethod === 'qrcode'">
|
||||
<text class="countdown-number">{{ countdown }}</text>
|
||||
<text class="countdown-text">秒后自动返回</text>
|
||||
</view>
|
||||
@@ -236,7 +237,7 @@ let scanInterval = null;
|
||||
const countdown = ref(60);
|
||||
let countdownTimer = null;
|
||||
const loginMethod = ref('qrcode'); // 'qrcode' / 'face'
|
||||
|
||||
const pageTitle = ref('就享家服务程序');
|
||||
onLoad((parmas) => {
|
||||
getTreeselect();
|
||||
if (!isMachineEnv.value) {
|
||||
@@ -264,6 +265,7 @@ onMounted(() => {
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
stopScanAnimation();
|
||||
stopCountdown();
|
||||
@@ -274,7 +276,7 @@ const startCountdown = () => {
|
||||
countdown.value = 60;
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value--;
|
||||
if (countdown.value <= 0) {
|
||||
if (countdown.value <= 0 && loginMethod.value === 'qrcode') {
|
||||
returnToHome();
|
||||
}
|
||||
}, 1000);
|
||||
@@ -316,6 +318,7 @@ const switchLoginMethod = (method) => {
|
||||
faceService.close();
|
||||
qrHandler.start();
|
||||
playTextDirectly('扫码登录');
|
||||
resetCountdown();
|
||||
break;
|
||||
case 'face':
|
||||
qrHandler.close();
|
||||
@@ -323,15 +326,32 @@ const switchLoginMethod = (method) => {
|
||||
playTextDirectly('扫脸登录');
|
||||
break;
|
||||
}
|
||||
resetCountdown();
|
||||
}
|
||||
};
|
||||
|
||||
async function handleFaceLogin() {
|
||||
try {
|
||||
const authCode = await faceService.startFaceLogin();
|
||||
console.log('拿到 AuthCode:', authCode);
|
||||
// 调用后端登录接口...
|
||||
console.log('authCode获取:', authCode);
|
||||
if (authCode.name) {
|
||||
pageTitle.value = `就享家服务程序(${authCode.name})`;
|
||||
}
|
||||
$api.createRequest('/app/alipay/scanLogin', authCode, 'POST').then((resData) => {
|
||||
loginSetToken(resData.token).then((resume) => {
|
||||
if (resume.data.jobTitleId) {
|
||||
useUserStore().initSeesionId();
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index',
|
||||
});
|
||||
} else {
|
||||
if (resume.data.sex) {
|
||||
pageTitle.value = `就享家服务程序(${name})`;
|
||||
fromValue.sex = resume.data.sex === '男' ? 0 : 1;
|
||||
}
|
||||
playTextDirectly('登录成功,请完善简历信息');
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
this.$api.msg(err.message);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<view v-show="searchFocus" class="search-mask" ></view>
|
||||
<view v-show="searchFocus" class="search-mask"></view>
|
||||
<view>
|
||||
<view class="top">
|
||||
<image
|
||||
@@ -40,8 +40,12 @@
|
||||
@confirm="searchBtn"
|
||||
/>
|
||||
<!-- 联想搜索下拉列表 -->
|
||||
<scroll-view scroll-y class="search-suggestions" v-show="showSuggestions && filteredSuggestions.length">
|
||||
<view
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="search-suggestions"
|
||||
v-show="showSuggestions && filteredSuggestions.length"
|
||||
>
|
||||
<view
|
||||
class="suggestion-item"
|
||||
v-for="(item, index) in filteredSuggestions"
|
||||
:key="index"
|
||||
@@ -144,13 +148,10 @@ const filteredSuggestions = computed(() => {
|
||||
|
||||
const searchText = searchValue.value.toLowerCase();
|
||||
try {
|
||||
return majorDataSource.value.filter(item =>
|
||||
item.toLowerCase().includes(searchText)
|
||||
);
|
||||
return majorDataSource.value.filter((item) => item.toLowerCase().includes(searchText));
|
||||
} catch (error) {
|
||||
return []
|
||||
return [];
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
const pageState = reactive({
|
||||
@@ -207,26 +208,28 @@ const { columnCount, columnSpace } = useColumnCount(() => {
|
||||
});
|
||||
|
||||
onLoad((options) => {
|
||||
if(options.keyWord){
|
||||
searchValue.value = decodeURIComponent(options.keyWord)
|
||||
searchBtn()
|
||||
if (options.keyWord) {
|
||||
searchValue.value = decodeURIComponent(options.keyWord);
|
||||
searchBtn();
|
||||
}
|
||||
let arr = uni.getStorageSync('searchList');
|
||||
if (arr) {
|
||||
historyList.value = uni.getStorageSync('searchList');
|
||||
}
|
||||
|
||||
getMajorDataSource()
|
||||
getMajorDataSource();
|
||||
});
|
||||
|
||||
function getMajorDataSource() {
|
||||
const LoadCache = (resData) => {
|
||||
if (resData.code === 200) {
|
||||
majorDataSource.value = resData.data;
|
||||
console.log(majorDataSource.value)
|
||||
console.log(majorDataSource.value);
|
||||
}
|
||||
};
|
||||
$api.createRequestWithCache('/app/common/majorManagement/getMajorList', {}, 'GET', false, LoadCache).then(LoadCache);
|
||||
$api.createRequestWithCache('/app/common/majorManagement/getMajorList', {}, 'GET', false, LoadCache).then(
|
||||
LoadCache
|
||||
);
|
||||
}
|
||||
|
||||
// 设置搜索类型并触发搜索
|
||||
@@ -247,7 +250,7 @@ function setSearchType(type) {
|
||||
pageState.page = 0;
|
||||
|
||||
// 触发搜索
|
||||
playTextDirectly('正在为您查找岗位')
|
||||
playTextDirectly('正在为您查找岗位');
|
||||
|
||||
// 根据搜索类型设置不同的参数
|
||||
if (searchType.value === 'job') {
|
||||
@@ -332,7 +335,7 @@ function searchBtn() {
|
||||
if (!searchValue.value) {
|
||||
return;
|
||||
}
|
||||
playTextDirectly('正在为您查找岗位')
|
||||
playTextDirectly('正在为您查找岗位');
|
||||
|
||||
// 保存到历史记录(仅当搜索成功时保存)
|
||||
historyList.value.unshift(searchValue.value);
|
||||
@@ -443,6 +446,16 @@ function getJobList(type = 'add') {
|
||||
listCom.value = [...listCom.value, ...reslist];
|
||||
} else {
|
||||
listCom.value = [...rows];
|
||||
// 一体机语音提示
|
||||
if (searchType.value === 'major') {
|
||||
if (rows.length) {
|
||||
$api.msg('为您找到相关专业的职位');
|
||||
playTextDirectly(`为您找到相关专业的职位`);
|
||||
} else {
|
||||
$api.msg('未找到相关专业的职位,请尝试其他关键词');
|
||||
playTextDirectly(`未找到相关专业的职位,请尝试其他关键词`);
|
||||
}
|
||||
}
|
||||
}
|
||||
pageState.total = resData.total;
|
||||
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
|
||||
|
||||
227
utils/IDCardParser.js
Normal file
227
utils/IDCardParser.js
Normal file
@@ -0,0 +1,227 @@
|
||||
// 使用示例:
|
||||
// import IDCardParser from '@/utils/IDCardParser';
|
||||
|
||||
// const handleCheck = (idStr) => {
|
||||
// const parser = new IDCardParser(idStr);
|
||||
// const result = parser.getInfo();
|
||||
// if(result.valid) {
|
||||
// // 填充表单
|
||||
// console.log(result.age, result.gender);
|
||||
// } else {
|
||||
// alert(result.message);
|
||||
// }
|
||||
// }
|
||||
|
||||
class IDCardParser {
|
||||
constructor(idNumber) {
|
||||
this.idNumber = idNumber ? idNumber.trim().toUpperCase() : "";
|
||||
this.provinceMap = {
|
||||
11: "北京市",
|
||||
12: "天津市",
|
||||
13: "河北省",
|
||||
14: "山西省",
|
||||
15: "内蒙古自治区",
|
||||
21: "辽宁省",
|
||||
22: "吉林省",
|
||||
23: "黑龙江省",
|
||||
31: "上海市",
|
||||
32: "江苏省",
|
||||
33: "浙江省",
|
||||
34: "安徽省",
|
||||
35: "福建省",
|
||||
36: "江西省",
|
||||
37: "山东省",
|
||||
41: "河南省",
|
||||
42: "湖北省",
|
||||
43: "湖南省",
|
||||
44: "广东省",
|
||||
45: "广西壮族自治区",
|
||||
46: "海南省",
|
||||
50: "重庆市",
|
||||
51: "四川省",
|
||||
52: "贵州省",
|
||||
53: "云南省",
|
||||
54: "西藏自治区",
|
||||
61: "陕西省",
|
||||
62: "甘肃省",
|
||||
63: "青海省",
|
||||
64: "宁夏回族自治区",
|
||||
65: "新疆维吾尔自治区",
|
||||
71: "台湾省",
|
||||
81: "香港特别行政区",
|
||||
82: "澳门特别行政区"
|
||||
};
|
||||
|
||||
// 初始化验证结果
|
||||
const validation = this._validate();
|
||||
this.isValid = validation.isValid;
|
||||
this.errorMsg = validation.msg;
|
||||
|
||||
// 如果合法,预先解析数据
|
||||
if (this.isValid) {
|
||||
this._parseData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心校验逻辑
|
||||
*/
|
||||
_validate() {
|
||||
// 1. 基础正则 (18位,末位数字或X)
|
||||
if (!/^\d{17}[\d|X]$/.test(this.idNumber)) {
|
||||
return {
|
||||
isValid: false,
|
||||
msg: "格式错误:必须是18位,末位为数字或X"
|
||||
};
|
||||
}
|
||||
|
||||
// 2. 省份校验
|
||||
const provinceCode = parseInt(this.idNumber.substring(0, 2));
|
||||
if (!this.provinceMap[provinceCode]) {
|
||||
return {
|
||||
isValid: false,
|
||||
msg: "省份代码错误"
|
||||
};
|
||||
}
|
||||
|
||||
// 3. 日期合法性严格校验
|
||||
const year = parseInt(this.idNumber.substring(6, 10));
|
||||
const month = parseInt(this.idNumber.substring(10, 12));
|
||||
const day = parseInt(this.idNumber.substring(12, 14));
|
||||
|
||||
const date = new Date(year, month - 1, day);
|
||||
// JS Date会自动纠错(例如2月30会变成3月),所以必须反向比对
|
||||
if (
|
||||
date.getFullYear() !== year ||
|
||||
date.getMonth() + 1 !== month ||
|
||||
date.getDate() !== day
|
||||
) {
|
||||
return {
|
||||
isValid: false,
|
||||
msg: "出生日期无效"
|
||||
};
|
||||
}
|
||||
|
||||
// 检查年份范围(可选,例如限制在1900年以后)
|
||||
if (year < 1900 || date > new Date()) {
|
||||
return {
|
||||
isValid: false,
|
||||
msg: "出生日期不在合理范围内"
|
||||
};
|
||||
}
|
||||
|
||||
// 4. 校验码计算 (ISO 7064:1983.MOD 11-2)
|
||||
if (!this._checkSum()) {
|
||||
return {
|
||||
isValid: false,
|
||||
msg: "校验码错误(身份证可能无效)"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
msg: "验证通过"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验码算法
|
||||
*/
|
||||
_checkSum() {
|
||||
const factors = [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(this.idNumber[i]) * factors[i];
|
||||
}
|
||||
|
||||
const mod = sum % 11;
|
||||
return checkCodes[mod] === this.idNumber[17];
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析内部数据
|
||||
*/
|
||||
_parseData() {
|
||||
const year = parseInt(this.idNumber.substring(6, 10));
|
||||
const month = parseInt(this.idNumber.substring(10, 12));
|
||||
const day = parseInt(this.idNumber.substring(12, 14));
|
||||
|
||||
this.birthDate = new Date(year, month - 1, day);
|
||||
this.province = this.provinceMap[parseInt(this.idNumber.substring(0, 2))];
|
||||
|
||||
// 性别:第17位,奇数男,偶数女
|
||||
const genderCode = parseInt(this.idNumber.substring(16, 17));
|
||||
this.gender = genderCode % 2 !== 0 ? "男" : "女";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有信息
|
||||
*/
|
||||
getInfo() {
|
||||
if (!this.isValid) {
|
||||
return {
|
||||
valid: false,
|
||||
message: this.errorMsg
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
idNumber: this.idNumber,
|
||||
province: this.province,
|
||||
birthday: this._formatDate(this.birthDate),
|
||||
gender: this.gender,
|
||||
age: this._calculateAge(),
|
||||
zodiac: this._getZodiac(), // 生肖
|
||||
constellation: this._getConstellation() // 星座
|
||||
};
|
||||
}
|
||||
|
||||
_formatDate(date) {
|
||||
const y = date.getFullYear();
|
||||
const m = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const d = String(date.getDate()).padStart(2, '0');
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
_calculateAge() {
|
||||
const today = new Date();
|
||||
let age = today.getFullYear() - this.birthDate.getFullYear();
|
||||
const m = today.getMonth() - this.birthDate.getMonth();
|
||||
|
||||
// 如果没过生日,年龄减1
|
||||
if (m < 0 || (m === 0 && today.getDate() < this.birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
return age;
|
||||
}
|
||||
|
||||
_getZodiac() {
|
||||
// 猴鸡狗猪鼠牛虎兔龙蛇马羊 (对应余数 0-11)
|
||||
const zodiacs = "猴鸡狗猪鼠牛虎兔龙蛇马羊";
|
||||
return zodiacs.charAt(this.birthDate.getFullYear() % 12);
|
||||
}
|
||||
|
||||
_getConstellation() {
|
||||
const month = this.birthDate.getMonth() + 1;
|
||||
const day = this.birthDate.getDate();
|
||||
|
||||
// 星座分割日
|
||||
const dates = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22];
|
||||
const constellations = [
|
||||
"摩羯座", "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座",
|
||||
"巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座"
|
||||
];
|
||||
|
||||
if (day < dates[month - 1]) {
|
||||
return constellations[month - 1];
|
||||
} else {
|
||||
return constellations[month];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default IDCardParser;
|
||||
@@ -18,7 +18,8 @@ const needToEncrypt = [
|
||||
["post", "/app/user/experience/edit"],
|
||||
["post", "/app/user/experience/delete"],
|
||||
["get", "/app/user/experience/getSingle/{value}"],
|
||||
["get", "/app/user/experience/list"]
|
||||
["get", "/app/user/experience/list"],
|
||||
["post", "/app/alipay/scanLogin"]
|
||||
]
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user