flat: bug修复

This commit is contained in:
Apcallover
2025-12-25 12:52:54 +08:00
parent ef669f23be
commit 8280cc9fae
5 changed files with 305 additions and 55 deletions

View File

@@ -64,7 +64,7 @@
<view v-if="hasLogin" :class="{ 'match-move-top': isMachineEnv }" class="match-card-out"> <view v-if="hasLogin" :class="{ 'match-move-top': isMachineEnv }" class="match-card-out">
<view class="match-card"> <view class="match-card">
<image class="match-card-bg" src="@/static/icon/match-card-bg.png" /> <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"> <view class="match-item-container">
<AIMatch :tags="matchTags" :loading="matchLoading" @tag-click="handleTagClick"></AIMatch> <AIMatch :tags="matchTags" :loading="matchLoading" @tag-click="handleTagClick"></AIMatch>
</view> </view>

View File

@@ -1,15 +1,8 @@
<template> <template>
<AppLayout title="就业服务程序"> <AppLayout :title="pageTitle">
<view v-if="isMachineEnv && !hasLogin" class="alipay-login-container"> <view v-if="isMachineEnv && !hasLogin" class="alipay-login-container">
<!-- 切换 --> <!-- 切换 -->
<view class="login-method-switch"> <view class="login-method-switch">
<view
class="method-item"
:class="{ active: loginMethod === 'face' }"
@click="switchLoginMethod('face')"
>
扫脸登录
</view>
<view <view
class="method-item" class="method-item"
:class="{ active: loginMethod === 'qrcode' }" :class="{ active: loginMethod === 'qrcode' }"
@@ -17,6 +10,13 @@
> >
扫码登录 扫码登录
</view> </view>
<view
class="method-item"
:class="{ active: loginMethod === 'face' }"
@click="switchLoginMethod('face')"
>
扫脸登录
</view>
</view> </view>
<view class="login-scan-area"> <view class="login-scan-area">
@@ -64,7 +64,8 @@
</view> </view>
<view class="countdown-container"> <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-number">{{ countdown }}</text>
<text class="countdown-text">秒后自动返回</text> <text class="countdown-text">秒后自动返回</text>
</view> </view>
@@ -236,7 +237,7 @@ let scanInterval = null;
const countdown = ref(60); const countdown = ref(60);
let countdownTimer = null; let countdownTimer = null;
const loginMethod = ref('qrcode'); // 'qrcode' / 'face' const loginMethod = ref('qrcode'); // 'qrcode' / 'face'
const pageTitle = ref('就享家服务程序');
onLoad((parmas) => { onLoad((parmas) => {
getTreeselect(); getTreeselect();
if (!isMachineEnv.value) { if (!isMachineEnv.value) {
@@ -264,6 +265,7 @@ onMounted(() => {
}, 1000); }, 1000);
} }
}); });
onUnmounted(() => { onUnmounted(() => {
stopScanAnimation(); stopScanAnimation();
stopCountdown(); stopCountdown();
@@ -274,7 +276,7 @@ const startCountdown = () => {
countdown.value = 60; countdown.value = 60;
countdownTimer = setInterval(() => { countdownTimer = setInterval(() => {
countdown.value--; countdown.value--;
if (countdown.value <= 0) { if (countdown.value <= 0 && loginMethod.value === 'qrcode') {
returnToHome(); returnToHome();
} }
}, 1000); }, 1000);
@@ -316,6 +318,7 @@ const switchLoginMethod = (method) => {
faceService.close(); faceService.close();
qrHandler.start(); qrHandler.start();
playTextDirectly('扫码登录'); playTextDirectly('扫码登录');
resetCountdown();
break; break;
case 'face': case 'face':
qrHandler.close(); qrHandler.close();
@@ -323,14 +326,16 @@ const switchLoginMethod = (method) => {
playTextDirectly('扫脸登录'); playTextDirectly('扫脸登录');
break; break;
} }
resetCountdown();
} }
}; };
async function handleFaceLogin() { async function handleFaceLogin() {
try { try {
const authCode = await faceService.startFaceLogin(); 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) => { $api.createRequest('/app/alipay/scanLogin', authCode, 'POST').then((resData) => {
loginSetToken(resData.token).then((resume) => { loginSetToken(resData.token).then((resume) => {
if (resume.data.jobTitleId) { if (resume.data.jobTitleId) {
@@ -338,6 +343,11 @@ async function handleFaceLogin() {
uni.reLaunch({ uni.reLaunch({
url: '/pages/index/index', url: '/pages/index/index',
}); });
// 登录成功、数据回填
if (resume.data.sex) {
pageTitle.value = `就享家服务程序(${name})`;
fromValue.sex = resume.data.sex === '男' ? 0 : 1;
}
} }
}); });
}); });

View File

@@ -1,6 +1,6 @@
<template> <template>
<view class="container"> <view class="container">
<view v-show="searchFocus" class="search-mask" ></view> <view v-show="searchFocus" class="search-mask"></view>
<view> <view>
<view class="top"> <view class="top">
<image <image
@@ -12,15 +12,15 @@
<view class="search-box"> <view class="search-box">
<!-- 修改为左右布局的切换按钮 --> <!-- 修改为左右布局的切换按钮 -->
<view class="search-type-tabs"> <view class="search-type-tabs">
<view <view
class="type-tab button-click" class="type-tab button-click"
:class="{ active: searchType === 'job' }" :class="{ active: searchType === 'job' }"
@click="setSearchType('job')" @click="setSearchType('job')"
> >
职位 职位
</view> </view>
<view <view
class="type-tab button-click" class="type-tab button-click"
:class="{ active: searchType === 'major' }" :class="{ active: searchType === 'major' }"
@click="setSearchType('major')" @click="setSearchType('major')"
> >
@@ -40,8 +40,12 @@
@confirm="searchBtn" @confirm="searchBtn"
/> />
<!-- 联想搜索下拉列表 --> <!-- 联想搜索下拉列表 -->
<scroll-view scroll-y class="search-suggestions" v-show="showSuggestions && filteredSuggestions.length"> <scroll-view
<view scroll-y
class="search-suggestions"
v-show="showSuggestions && filteredSuggestions.length"
>
<view
class="suggestion-item" class="suggestion-item"
v-for="(item, index) in filteredSuggestions" v-for="(item, index) in filteredSuggestions"
:key="index" :key="index"
@@ -144,13 +148,10 @@ const filteredSuggestions = computed(() => {
const searchText = searchValue.value.toLowerCase(); const searchText = searchValue.value.toLowerCase();
try { try {
return majorDataSource.value.filter(item => return majorDataSource.value.filter((item) => item.toLowerCase().includes(searchText));
item.toLowerCase().includes(searchText)
);
} catch (error) { } catch (error) {
return [] return [];
} }
}); });
const pageState = reactive({ const pageState = reactive({
@@ -207,48 +208,50 @@ const { columnCount, columnSpace } = useColumnCount(() => {
}); });
onLoad((options) => { onLoad((options) => {
if(options.keyWord){ if (options.keyWord) {
searchValue.value = decodeURIComponent(options.keyWord) searchValue.value = decodeURIComponent(options.keyWord);
searchBtn() searchBtn();
} }
let arr = uni.getStorageSync('searchList'); let arr = uni.getStorageSync('searchList');
if (arr) { if (arr) {
historyList.value = uni.getStorageSync('searchList'); historyList.value = uni.getStorageSync('searchList');
} }
getMajorDataSource() getMajorDataSource();
}); });
function getMajorDataSource() { function getMajorDataSource() {
const LoadCache = (resData) => { const LoadCache = (resData) => {
if (resData.code === 200) { if (resData.code === 200) {
majorDataSource.value = resData.data; 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
);
} }
// 设置搜索类型并触发搜索 // 设置搜索类型并触发搜索
function setSearchType(type) { function setSearchType(type) {
if (searchType.value === type) return; if (searchType.value === type) return;
searchType.value = type; searchType.value = type;
// 如果输入框有值且当前是专业搜索,显示联想列表 // 如果输入框有值且当前是专业搜索,显示联想列表
if (searchValue.value && searchType.value === 'major' && searchFocus.value) { if (searchValue.value && searchType.value === 'major' && searchFocus.value) {
showSuggestions.value = true; showSuggestions.value = true;
} else { } else {
showSuggestions.value = false; showSuggestions.value = false;
} }
// 如果有搜索值,触发搜索 // 如果有搜索值,触发搜索
if (searchValue.value) { if (searchValue.value) {
// 重置页码 // 重置页码
pageState.page = 0; pageState.page = 0;
// 触发搜索 // 触发搜索
playTextDirectly('正在为您查找岗位') playTextDirectly('正在为您查找岗位');
// 根据搜索类型设置不同的参数 // 根据搜索类型设置不同的参数
if (searchType.value === 'job') { if (searchType.value === 'job') {
searchParams.value = { searchParams.value = {
@@ -259,14 +262,14 @@ function setSearchType(type) {
majorTitle: searchValue.value, majorTitle: searchValue.value,
}; };
} }
if (currentTab.value === 0) { if (currentTab.value === 0) {
getJobList('refresh'); getJobList('refresh');
} else { } else {
refresh(); refresh();
waterfallsFlowRef.value?.refresh?.(); waterfallsFlowRef.value?.refresh?.();
} }
// 隐藏联想列表 // 隐藏联想列表
showSuggestions.value = false; showSuggestions.value = false;
} }
@@ -295,7 +298,7 @@ function searchFn(item) {
function handleInputChange(e) { function handleInputChange(e) {
const val = e.detail.value; const val = e.detail.value;
searchValue.value = val; searchValue.value = val;
// 如果是专业搜索且输入框有值,显示联想列表 // 如果是专业搜索且输入框有值,显示联想列表
if (searchType.value === 'major' && val && searchFocus.value) { if (searchType.value === 'major' && val && searchFocus.value) {
showSuggestions.value = true; showSuggestions.value = true;
@@ -332,13 +335,13 @@ function searchBtn() {
if (!searchValue.value) { if (!searchValue.value) {
return; return;
} }
playTextDirectly('正在为您查找岗位') playTextDirectly('正在为您查找岗位');
// 保存到历史记录(仅当搜索成功时保存) // 保存到历史记录(仅当搜索成功时保存)
historyList.value.unshift(searchValue.value); historyList.value.unshift(searchValue.value);
historyList.value = unique(historyList.value); historyList.value = unique(historyList.value);
uni.setStorageSync('searchList', historyList.value); uni.setStorageSync('searchList', historyList.value);
// 根据搜索类型设置不同的参数 // 根据搜索类型设置不同的参数
if (searchType.value === 'job') { if (searchType.value === 'job') {
// 职位搜索 // 职位搜索
@@ -351,17 +354,17 @@ function searchBtn() {
majorTitle: searchValue.value, majorTitle: searchValue.value,
}; };
} }
// 重置页码 // 重置页码
pageState.page = 0; pageState.page = 0;
if (currentTab.value === 0) { if (currentTab.value === 0) {
getJobList('refresh'); getJobList('refresh');
} else { } else {
refresh(); refresh();
waterfallsFlowRef.value?.refresh?.(); waterfallsFlowRef.value?.refresh?.();
} }
// 隐藏联想列表 // 隐藏联想列表
showSuggestions.value = false; showSuggestions.value = false;
} }
@@ -414,14 +417,14 @@ function getJobList(type = 'add') {
pageState.page = 1; pageState.page = 1;
pageState.maxPage = 2; pageState.maxPage = 2;
} }
// 根据搜索类型构建参数 // 根据搜索类型构建参数
let params = { let params = {
current: pageState.page, current: pageState.page,
pageSize: pageState.pageSize, pageSize: pageState.pageSize,
...pageState.search, ...pageState.search,
}; };
// 移除之前的搜索参数,根据当前搜索类型添加 // 移除之前的搜索参数,根据当前搜索类型添加
if (searchType.value === 'job') { if (searchType.value === 'job') {
params.jobTitle = searchValue.value; params.jobTitle = searchValue.value;
@@ -443,6 +446,16 @@ function getJobList(type = 'add') {
listCom.value = [...listCom.value, ...reslist]; listCom.value = [...listCom.value, ...reslist];
} else { } else {
listCom.value = [...rows]; listCom.value = [...rows];
// 一体机语音提示
if (searchType.value === 'major') {
if (rows.length) {
$api.msg('为您找到相关专业的职位');
playTextDirectly(`为您找到相关专业的职位`);
} else {
$api.msg('未找到相关专业的职位,请尝试其他关键词');
playTextDirectly(`未找到相关专业的职位,请尝试其他关键词`);
}
}
} }
pageState.total = resData.total; pageState.total = resData.total;
pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize); pageState.maxPage = Math.ceil(pageState.total / pageState.pageSize);
@@ -589,13 +602,13 @@ function dataToImg(data) {
font-weight: 400; font-weight: 400;
line-height: 36rpx; line-height: 36rpx;
color: #666666; color: #666666;
padding: 0 30rpx 0 230rpx; padding: 0 30rpx 0 230rpx;
box-sizing: border-box; box-sizing: border-box;
height: 80rpx; height: 80rpx;
background: #F5F5F5; background: #F5F5F5;
border-radius: 73rpx; border-radius: 73rpx;
} }
.search-suggestions { .search-suggestions {
position: absolute; position: absolute;
top: 105%; top: 105%;
@@ -614,18 +627,18 @@ function dataToImg(data) {
align-items: center; align-items: center;
justify-items: flex-start; justify-items: flex-start;
border-top: 2rpx dashed #e3e3e3; border-top: 2rpx dashed #e3e3e3;
.item-txt { .item-txt {
font-size: 28rpx; font-size: 28rpx;
color: #333333; color: #333333;
width: 100%; width: 100%;
} }
} }
.suggestion-item:hover { .suggestion-item:hover {
background: #e5e5e5; background: #e5e5e5;
} }
.suggestion-item:first-child { .suggestion-item:first-child {
border-top: none; border-top: none;
} }

227
utils/IDCardParser.js Normal file
View 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;

View File

@@ -119,8 +119,8 @@ export function createRequest(url, data = {}, method = 'GET', loading = false, h
// ------------------------------------------------------------------ // ------------------------------------------------------------------
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
uni.request({ uni.request({
// url: config.baseUrl + '/app/proxy', url: config.baseUrl + '/app/proxy',
url: config.baseUrl + url, // url: config.baseUrl + url,
method: method, method: method,
data: requestData, data: requestData,
header, header,