2024-11-08 11:55:23 +08:00
|
|
|
|
import useUserStore from "../stores/useUserStore";
|
2025-03-28 15:19:42 +08:00
|
|
|
|
import {
|
|
|
|
|
request,
|
|
|
|
|
createRequest,
|
|
|
|
|
uploadFile
|
|
|
|
|
} from "../utils/request";
|
|
|
|
|
import streamRequest, {
|
|
|
|
|
chatRequest
|
|
|
|
|
} from "../utils/streamRequest.js";
|
2024-11-08 11:55:23 +08:00
|
|
|
|
|
2025-03-28 15:19:42 +08:00
|
|
|
|
export const CloneDeep = (props) => {
|
|
|
|
|
if (typeof props !== 'object' || props === null) {
|
|
|
|
|
return props
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let result
|
|
|
|
|
if (props) {
|
|
|
|
|
result = []
|
|
|
|
|
} else {
|
|
|
|
|
result = {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let key in props) {
|
|
|
|
|
if (props.hasOwnProperty(key)) {
|
|
|
|
|
result[key] = CloneDeep(props[key])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const msg = (title, duration = 1500, mask = false, icon = 'none', image) => {
|
2024-11-08 11:55:23 +08:00
|
|
|
|
if (Boolean(title) === false) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
uni.showToast({
|
|
|
|
|
title,
|
|
|
|
|
duration,
|
|
|
|
|
mask,
|
|
|
|
|
icon,
|
|
|
|
|
image
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const prePage = () => {
|
|
|
|
|
let pages = getCurrentPages();
|
|
|
|
|
let prePage = pages[pages.length - 2];
|
|
|
|
|
return prePage.$vm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-05-13 11:10:38 +08:00
|
|
|
|
/**
|
|
|
|
|
* 页面跳转封装,支持 query 参数传递和返回回调
|
|
|
|
|
* @param {string} url - 跳转路径
|
|
|
|
|
* @param {object} options
|
|
|
|
|
* @param {boolean} options.needLogin - 是否需要登录
|
|
|
|
|
* @param {object} options.query - 携带参数
|
|
|
|
|
* @param {function} options.onBack - 页面返回时的回调(目标页调用 uni.navigateBack 时传递数据)
|
|
|
|
|
*/
|
|
|
|
|
export const navTo = function(url, {
|
|
|
|
|
needLogin = false,
|
|
|
|
|
query = {},
|
|
|
|
|
onBack = null
|
|
|
|
|
} = {}) {
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
|
|
|
|
if (needLogin && !userStore.hasLogin) {
|
2024-11-08 11:55:23 +08:00
|
|
|
|
uni.navigateTo({
|
2025-10-21 22:58:47 +08:00
|
|
|
|
url: '/pages/complete-info/complete-info'
|
2024-11-08 11:55:23 +08:00
|
|
|
|
});
|
2025-05-13 11:10:38 +08:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const queryStr = Object.entries(query)
|
|
|
|
|
.map(([key, val]) => `${key}=${encodeURIComponent(val)}`)
|
|
|
|
|
.join('&');
|
|
|
|
|
const finalUrl = queryStr ? `${url}?${queryStr}` : url;
|
|
|
|
|
|
|
|
|
|
if (onBack) {
|
|
|
|
|
const pages = getCurrentPages();
|
|
|
|
|
const currentPage = pages[pages.length - 1];
|
|
|
|
|
currentPage.__onBackCallback__ = onBack;
|
2024-11-08 11:55:23 +08:00
|
|
|
|
}
|
2025-05-13 11:10:38 +08:00
|
|
|
|
|
2024-11-08 11:55:23 +08:00
|
|
|
|
uni.navigateTo({
|
2025-05-13 11:10:38 +08:00
|
|
|
|
url: finalUrl
|
2024-11-08 11:55:23 +08:00
|
|
|
|
});
|
2025-05-13 11:10:38 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const navBack = function({
|
|
|
|
|
delta = 1,
|
|
|
|
|
data = null,
|
|
|
|
|
fallbackUrl = '/pages/index/index'
|
|
|
|
|
} = {}) {
|
|
|
|
|
const pages = getCurrentPages();
|
|
|
|
|
|
|
|
|
|
if (pages.length > 1) {
|
|
|
|
|
const prevPage = pages[pages.length - 1 - delta];
|
|
|
|
|
|
|
|
|
|
// 如果上一页存在回调函数,调用
|
|
|
|
|
if (data && prevPage?.__onBackCallback__) {
|
|
|
|
|
prevPage.__onBackCallback__(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uni.navigateBack({
|
|
|
|
|
delta
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// 没有可返回的页面,直接跳转 fallback 页面
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
url: fallbackUrl
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
// // 默认返回上一页
|
|
|
|
|
// navBack();
|
|
|
|
|
|
|
|
|
|
// // 返回上两层
|
|
|
|
|
// navBack(2);
|
|
|
|
|
|
|
|
|
|
// // 没有历史页面时跳转首页
|
|
|
|
|
// navBack(1, '/pages/home/home');
|
2024-11-08 11:55:23 +08:00
|
|
|
|
|
|
|
|
|
function getdeviceInfo() {
|
|
|
|
|
const globalData = {
|
|
|
|
|
statusBarHeight: 0, // 状态导航栏高度
|
|
|
|
|
topHeight: 0, // 距离顶部高度
|
|
|
|
|
navHeight: 0, // 总体高度
|
|
|
|
|
windowHeight: 0, // 可使用窗口高度
|
|
|
|
|
tabBarHight: 0, //底部导航栏高度
|
|
|
|
|
};
|
|
|
|
|
let systemInfo = uni.getSystemInfoSync()
|
|
|
|
|
globalData.windowHeight = systemInfo.screenHeight
|
|
|
|
|
// 底部导航栏
|
|
|
|
|
globalData.tabBarHight = systemInfo.screenHeight - systemInfo.safeArea.bottom
|
|
|
|
|
// 状态栏高度
|
|
|
|
|
globalData.statusBarHeight = systemInfo.statusBarHeight
|
|
|
|
|
// #ifdef MP-MP-WEIXIN
|
|
|
|
|
let menuButtonInfo = uni.getMenuButtonBoundingClientRect()
|
|
|
|
|
// 胶囊距离顶部高度
|
|
|
|
|
globalData.topHeight = menuButtonInfo.top
|
|
|
|
|
// 胶囊高度
|
|
|
|
|
globalData.navHeight = menuButtonInfo.height
|
|
|
|
|
// #endif
|
|
|
|
|
return {
|
|
|
|
|
...globalData
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sleep(time) {
|
|
|
|
|
return new Promise((resolve) => setTimeout(resolve, time))
|
|
|
|
|
}
|
2025-03-28 15:19:42 +08:00
|
|
|
|
const cloneDeep = (obj) => {
|
|
|
|
|
// 1.1 判断是否是对象
|
|
|
|
|
const isObject = (obj) => (typeof obj === 'object' || typeof obj === 'function') && obj !== 'null'
|
|
|
|
|
|
|
|
|
|
if (!isObject(obj)) {
|
|
|
|
|
throw new Error('参数不是对象')
|
|
|
|
|
}
|
|
|
|
|
// 1.3 如果参数为数组,则复制数组各元素,否则复制对象属性
|
|
|
|
|
const newObject = Array.isArray(obj) ? [...obj] : {
|
|
|
|
|
...obj
|
|
|
|
|
}
|
|
|
|
|
// 1.4 迭代
|
|
|
|
|
Object.keys(newObject).forEach((key) => {
|
|
|
|
|
// 1.5 判断如果遍历到的属性值为对象,则继续递归cloneDeep
|
|
|
|
|
if (isObject(newObject[key])) {
|
|
|
|
|
newObject[key] = cloneDeep(newObject[key])
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return newObject
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CopyText = (text) => {
|
|
|
|
|
let input = document.createElement('textarea');
|
|
|
|
|
input.value = text;
|
|
|
|
|
document.body.appendChild(input);
|
|
|
|
|
input.select();
|
|
|
|
|
let flag = document.execCommand('copy')
|
|
|
|
|
if (flag) {
|
|
|
|
|
message.success('成功复制到剪贴板')
|
|
|
|
|
} else {
|
|
|
|
|
message.success('复制失败')
|
|
|
|
|
}
|
|
|
|
|
document.body.removeChild(input)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 柯里化 降低使用范围,提高适用性
|
|
|
|
|
function Exp(regExp) {
|
|
|
|
|
return (str) => {
|
|
|
|
|
return regExp.test(str)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const checkingPhoneRegExp = Exp(/^1[3-9]{1}\d{9}/)
|
|
|
|
|
// 手机号校验 checkingPhoneRegExp(phone)
|
|
|
|
|
|
|
|
|
|
const checkingEmailRegExp = Exp(/^[a-z0-9_\.-]+@[a-z0-9_\.-]+[a-z0-9]{2,6}$/i)
|
|
|
|
|
// 邮箱校验 checkingEmailRegExp(email)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function throttle(fn, delay = 300) {
|
|
|
|
|
let valid = true
|
|
|
|
|
let savedArgs = null // 参数存储器
|
|
|
|
|
let savedContext = null // 上下文存储器
|
|
|
|
|
|
|
|
|
|
return function(...args) {
|
|
|
|
|
// 保存当前参数和上下文
|
|
|
|
|
savedArgs = args
|
|
|
|
|
savedContext = this
|
|
|
|
|
|
|
|
|
|
if (!valid) return false
|
|
|
|
|
valid = false
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
fn.apply(savedContext, savedArgs)
|
|
|
|
|
valid = true
|
|
|
|
|
savedArgs = null // 清空存储
|
|
|
|
|
savedContext = null
|
|
|
|
|
}, delay)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function debounce(fun, delay) {
|
|
|
|
|
return function(args) {
|
|
|
|
|
let that = this
|
|
|
|
|
let _args = args
|
|
|
|
|
clearTimeout(fun.id)
|
|
|
|
|
fun.id = setTimeout(function() {
|
|
|
|
|
fun.call(that, _args)
|
|
|
|
|
}, delay)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function toRad(degree) {
|
|
|
|
|
return degree * Math.PI / 180;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function haversine(lat1, lon1, lat2, lon2) {
|
|
|
|
|
const R = 6371; // 地球半径,单位为公里
|
|
|
|
|
const a1 = toRad(lat1);
|
|
|
|
|
const a2 = toRad(lat2);
|
|
|
|
|
const b1 = toRad(lat2 - lat1);
|
|
|
|
|
const b2 = toRad(lon2 - lon1);
|
|
|
|
|
|
|
|
|
|
const a = Math.sin(b1 / 2) * Math.sin(b1 / 2) +
|
|
|
|
|
Math.cos(a1) * Math.cos(a2) * Math.sin(b2 / 2) * Math.sin(b2 / 2);
|
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
|
|
|
const distance = R * c; // 计算得到的距离,单位为公里
|
|
|
|
|
|
|
|
|
|
return distance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
|
|
|
|
|
const R = 6371; // 地球平均半径,单位为公里
|
|
|
|
|
const dLat = deg2rad(lat2 - lat1);
|
|
|
|
|
const dLon = deg2rad(lon2 - lon1);
|
|
|
|
|
const a =
|
|
|
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
|
|
|
Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
|
|
|
|
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
|
|
|
const d = R * c;
|
|
|
|
|
return {
|
|
|
|
|
km: d,
|
|
|
|
|
m: d * 1000
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 将角度转换为弧度
|
|
|
|
|
function deg2rad(deg) {
|
|
|
|
|
return deg * (Math.PI / 180);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function vacanciesTo(vacancies) {
|
|
|
|
|
if (vacancies >= 0) {
|
|
|
|
|
return vacancies + "人"
|
|
|
|
|
} else {
|
|
|
|
|
return '不限人数'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function salaryGlobal(type = 'min') {
|
|
|
|
|
const salay = [2, 5, 10, 15, 20, 25, 30, 50, 80];
|
|
|
|
|
const salaymax = [2, 5, 10, 15, 20, 25, 30, 50, 80, 100];
|
|
|
|
|
const salarys = salay.map((item, index) => ({
|
|
|
|
|
label: item + 'k',
|
|
|
|
|
value: item * 1000,
|
|
|
|
|
children: CloneDeep(salaymax).splice(index).map((vItem) => ({
|
|
|
|
|
label: vItem + 'k',
|
|
|
|
|
value: vItem * 1000,
|
|
|
|
|
}))
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
return salarys
|
|
|
|
|
}
|
2024-11-08 11:55:23 +08:00
|
|
|
|
|
2025-03-28 15:19:42 +08:00
|
|
|
|
class CustomSystem {
|
|
|
|
|
constructor() {
|
|
|
|
|
const systemInfo = uni.getSystemInfoSync();
|
|
|
|
|
this.systemInfo = systemInfo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const customSystem = new CustomSystem()
|
|
|
|
|
|
|
|
|
|
function setCheckedNodes(nodes, ids) {
|
2025-05-13 11:10:38 +08:00
|
|
|
|
const isClear = ids.length === 0;
|
|
|
|
|
|
2025-03-28 15:19:42 +08:00
|
|
|
|
nodes.forEach((firstLayer) => {
|
2025-05-13 11:10:38 +08:00
|
|
|
|
// 每次处理都先重置
|
2025-03-28 15:19:42 +08:00
|
|
|
|
firstLayer.checkednumber = 0;
|
|
|
|
|
|
|
|
|
|
const traverse = (node) => {
|
2025-05-13 11:10:38 +08:00
|
|
|
|
if (isClear) {
|
|
|
|
|
node.checked = false;
|
|
|
|
|
} else {
|
|
|
|
|
node.checked = ids.includes(node.id);
|
|
|
|
|
}
|
2025-03-28 15:19:42 +08:00
|
|
|
|
|
|
|
|
|
if (node !== firstLayer && node.checked) {
|
|
|
|
|
firstLayer.checkednumber++;
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-13 11:10:38 +08:00
|
|
|
|
if (node.children && node.children.length) {
|
|
|
|
|
node.children.forEach(child => traverse(child));
|
2025-03-28 15:19:42 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
traverse(firstLayer);
|
|
|
|
|
});
|
|
|
|
|
|
2025-05-13 11:10:38 +08:00
|
|
|
|
return nodes;
|
|
|
|
|
}
|
2025-03-28 15:19:42 +08:00
|
|
|
|
const formatTotal = (total) => {
|
|
|
|
|
if (total < 10) return total.toString(); // 直接返回小于 10 的数
|
|
|
|
|
|
|
|
|
|
const magnitude = Math.pow(10, Math.floor(Math.log10(total))); // 计算数量级
|
|
|
|
|
const roundedTotal = Math.floor(total / magnitude) * magnitude; // 去掉零头
|
|
|
|
|
|
|
|
|
|
return `${roundedTotal}+`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function formatDate(isoString) {
|
|
|
|
|
const date = new Date(isoString);
|
|
|
|
|
const year = date.getFullYear();
|
|
|
|
|
const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需要 +1
|
|
|
|
|
const day = String(date.getDate()).padStart(2, '0');
|
|
|
|
|
const hours = String(date.getHours()).padStart(2, '0');
|
|
|
|
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
|
|
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
|
|
|
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function insertSortData(data, attribute = 'createTime') {
|
|
|
|
|
const sortedData = data.sort((a, b) => new Date(b[attribute]) - new Date(a[attribute])); // 按时间降序排序
|
|
|
|
|
const result = [];
|
|
|
|
|
let lastDate = '';
|
|
|
|
|
let lastTitle = ''
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const now = new Date();
|
|
|
|
|
const todayStr = now.toISOString().split('T')[0]; // 获取今天的日期字符串
|
|
|
|
|
const yesterday = new Date(now.setDate(now.getDate() - 1)).toISOString().split('T')[0]; // 获取昨天的日期字符串
|
|
|
|
|
const twoDaysAgo = new Date(now.setDate(now.getDate() - 1)).toISOString().split('T')[0]; // 获取前天的日期字符串
|
|
|
|
|
|
|
|
|
|
sortedData.forEach(item => {
|
|
|
|
|
const itemAttribute = item[attribute].replace('T', ' ')
|
|
|
|
|
const itemDate = itemAttribute.split(' ')[0]; // 提取日期部分
|
|
|
|
|
|
|
|
|
|
let title = itemDate;
|
|
|
|
|
if (itemDate === todayStr) {
|
|
|
|
|
title = '今天';
|
|
|
|
|
} else if (itemDate === yesterday) {
|
|
|
|
|
title = '昨天';
|
|
|
|
|
} else if (itemDate === twoDaysAgo) {
|
|
|
|
|
title = '前天';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lastDate !== itemDate) {
|
|
|
|
|
result.push({
|
|
|
|
|
title,
|
|
|
|
|
isTitle: true
|
|
|
|
|
});
|
|
|
|
|
lastDate = itemDate;
|
|
|
|
|
lastTitle = title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result.push({
|
|
|
|
|
...item,
|
|
|
|
|
isTitle: false
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return [result, lastTitle];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getWeeksOfMonth(year, month) {
|
|
|
|
|
const firstDay = new Date(year, month - 1, 1); // 当月第一天
|
|
|
|
|
const lastDay = new Date(year, month, 0); // 当月最后一天
|
|
|
|
|
const weeks = [];
|
|
|
|
|
let week = [];
|
|
|
|
|
|
|
|
|
|
for (let d = new Date(firstDay); d <= lastDay; d.setDate(d.getDate() + 1)) {
|
|
|
|
|
// 补充第一周的上个月日期
|
|
|
|
|
if (week.length === 0 && d.getDay() !== 1) {
|
|
|
|
|
let prevMonday = new Date(d);
|
|
|
|
|
prevMonday.setDate(d.getDate() - (d.getDay() === 0 ? 6 : d.getDay() - 1));
|
|
|
|
|
while (prevMonday < d) {
|
|
|
|
|
week.push({
|
|
|
|
|
year: prevMonday.getFullYear(),
|
|
|
|
|
month: prevMonday.getMonth() + 1,
|
|
|
|
|
day: prevMonday.getDate(),
|
|
|
|
|
fullDate: getLocalYYYYMMDD(prevMonday), // 修正
|
|
|
|
|
weekday: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][prevMonday.getDay()],
|
|
|
|
|
isCurrent: false // 上个月日期
|
|
|
|
|
});
|
|
|
|
|
prevMonday.setDate(prevMonday.getDate() + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加当前月份的日期
|
|
|
|
|
week.push({
|
|
|
|
|
year: d.getFullYear(),
|
|
|
|
|
month: d.getMonth() + 1,
|
|
|
|
|
day: d.getDate(),
|
|
|
|
|
fullDate: getLocalYYYYMMDD(d), // 修正
|
|
|
|
|
weekday: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][d.getDay()],
|
|
|
|
|
isCurrent: true // 当前月的日期
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 如果到了月末但当前周未满7天,需要补足到周日
|
|
|
|
|
if (d.getTime() === lastDay.getTime() && week.length < 7) {
|
|
|
|
|
let nextDay = new Date(d);
|
|
|
|
|
nextDay.setDate(d.getDate() + 1);
|
|
|
|
|
while (week.length < 7) {
|
|
|
|
|
week.push({
|
|
|
|
|
year: nextDay.getFullYear(),
|
|
|
|
|
month: nextDay.getMonth() + 1,
|
|
|
|
|
day: nextDay.getDate(),
|
|
|
|
|
fullDate: getLocalYYYYMMDD(nextDay), // 修正
|
|
|
|
|
weekday: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][nextDay.getDay()],
|
|
|
|
|
isCurrent: false // 下个月日期
|
|
|
|
|
});
|
|
|
|
|
nextDay.setDate(nextDay.getDate() + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果本周满了(7天)或者到了月末
|
|
|
|
|
if (week.length === 7 || d.getTime() === lastDay.getTime()) {
|
|
|
|
|
weeks.push([...week]); // 存入当前周
|
|
|
|
|
week = []; // 清空,准备下一周
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return weeks;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 新增工具函数:将日期格式化为本地 YYYY-MM-DD 字符串
|
|
|
|
|
function getLocalYYYYMMDD(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}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isFutureDate(dateStr) {
|
|
|
|
|
const inputDate = new Date(dateStr);
|
|
|
|
|
const today = new Date();
|
|
|
|
|
|
|
|
|
|
// 只比较年月日,不考虑具体时间
|
|
|
|
|
today.setHours(0, 0, 0, 0);
|
|
|
|
|
inputDate.setHours(0, 0, 0, 0);
|
|
|
|
|
|
|
|
|
|
return inputDate > today;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseQueryParams(url = window.location.href) {
|
|
|
|
|
const queryString = url.split('?')[1]?.split('#')[0];
|
|
|
|
|
const params = {};
|
|
|
|
|
if (!queryString) return params;
|
|
|
|
|
|
|
|
|
|
queryString.split('&').forEach(param => {
|
|
|
|
|
const [key, value] = param.split('=');
|
|
|
|
|
if (key) {
|
|
|
|
|
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return params;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-28 18:19:05 +08:00
|
|
|
|
function formatFileSize(bytes) {
|
|
|
|
|
if (bytes < 1024) return bytes + ' B'
|
|
|
|
|
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'
|
|
|
|
|
else if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
|
|
|
|
|
else return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
|
|
|
|
|
}
|
2025-03-29 12:06:35 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function sendingMiniProgramMessage(data = {
|
|
|
|
|
text: 'hello'
|
|
|
|
|
}, action = 'defalut') {
|
|
|
|
|
jWeixin.miniProgram.postMessage({
|
|
|
|
|
data,
|
|
|
|
|
action
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-07 09:10:55 +08:00
|
|
|
|
function copyText(text) {
|
|
|
|
|
uni.setClipboardData({
|
|
|
|
|
data: text,
|
|
|
|
|
showToast: false,
|
|
|
|
|
success(res) {
|
|
|
|
|
msg('复制成功')
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function appendScriptTagElement(src) {
|
|
|
|
|
if (!src) return null;
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
const script = document.createElement('script');
|
|
|
|
|
script.src = src;
|
|
|
|
|
script.onload = () => {
|
|
|
|
|
resolve()
|
|
|
|
|
};
|
|
|
|
|
script.onerror = () => {
|
|
|
|
|
reject()
|
|
|
|
|
};
|
|
|
|
|
document.body.appendChild(script);
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-15 14:17:51 +08:00
|
|
|
|
function isInWechatMiniProgramWebview() {
|
|
|
|
|
const ua = navigator.userAgent.toLowerCase()
|
|
|
|
|
return ua.includes('miniprogram') || window.__wxjs_environment === 'miniprogram'
|
|
|
|
|
}
|
2025-04-07 09:10:55 +08:00
|
|
|
|
|
2025-06-10 09:36:04 +08:00
|
|
|
|
function isEmptyObject(obj) {
|
|
|
|
|
return obj && typeof obj === 'object' && !Array.isArray(obj) && Object.keys(obj).length === 0;
|
|
|
|
|
}
|
2025-09-29 11:53:10 +08:00
|
|
|
|
/**
|
|
|
|
|
* 身份证号码校验工具
|
|
|
|
|
* 支持15位和18位身份证号码校验
|
|
|
|
|
* 提供详细的校验结果和错误信息
|
|
|
|
|
*/
|
|
|
|
|
export const IdCardValidator = {
|
|
|
|
|
// 每位加权因子
|
|
|
|
|
powers: ['7', '9', '10', '5', '8', '4', '2', '1', '6', '3', '7', '9', '10', '5', '8', '4', '2'],
|
|
|
|
|
|
|
|
|
|
// 第18位校检码
|
|
|
|
|
parityBit: ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'],
|
|
|
|
|
|
|
|
|
|
// 省市地区码映射
|
|
|
|
|
provinceAndCitys: {
|
|
|
|
|
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: '澳门',
|
|
|
|
|
91: '国外'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证身份证号码
|
|
|
|
|
* @param {string} idCardNo - 身份证号码
|
|
|
|
|
* @returns {Object} 校验结果 { valid: boolean, message: string, info: Object }
|
|
|
|
|
*/
|
|
|
|
|
validate(idCardNo) {
|
|
|
|
|
// 检查是否为空
|
|
|
|
|
if (this._isEmpty(idCardNo)) {
|
|
|
|
|
return this._createResult(false, '身份证号码不能为空');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 去除空格
|
|
|
|
|
idCardNo = idCardNo.trim();
|
|
|
|
|
|
|
|
|
|
// 检查长度,支持15位和18位
|
|
|
|
|
if (idCardNo.length === 15) {
|
|
|
|
|
return this._validate15IdCardNo(idCardNo);
|
|
|
|
|
} else if (idCardNo.length === 18) {
|
|
|
|
|
return this._validate18IdCardNo(idCardNo);
|
|
|
|
|
} else {
|
|
|
|
|
return this._createResult(false, '身份证号码长度必须是15位或18位');
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证18位身份证号码
|
|
|
|
|
* @param {string} idCardNo - 18位身份证号码
|
|
|
|
|
* @returns {Object} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_validate18IdCardNo(idCardNo) {
|
|
|
|
|
// 18位身份证号码的基本格式校验
|
|
|
|
|
if (!/^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/.test(
|
|
|
|
|
idCardNo)) {
|
|
|
|
|
return this._createResult(false, '身份证号码格式不正确');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验地址码
|
|
|
|
|
const addressCode = idCardNo.substring(0, 6);
|
|
|
|
|
const addressResult = this._checkAddressCode(addressCode);
|
|
|
|
|
if (!addressResult.valid) {
|
|
|
|
|
return addressResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验日期码
|
|
|
|
|
const birDayCode = idCardNo.substring(6, 14);
|
|
|
|
|
const birthResult = this._checkBirthDayCode(birDayCode);
|
|
|
|
|
if (!birthResult.valid) {
|
|
|
|
|
return birthResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 验证校检码
|
|
|
|
|
if (!this._checkParityBit(idCardNo)) {
|
|
|
|
|
return this._createResult(false, '身份证号码校验码错误');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提取身份证信息
|
|
|
|
|
const info = this._extractInfo(idCardNo, 18);
|
|
|
|
|
|
|
|
|
|
return this._createResult(true, '身份证号码校验通过', info);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证15位身份证号码
|
|
|
|
|
* @param {string} idCardNo - 15位身份证号码
|
|
|
|
|
* @returns {Object} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_validate15IdCardNo(idCardNo) {
|
|
|
|
|
// 15位身份证号码的基本格式校验
|
|
|
|
|
if (!/^[1-9]\d{5}\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(idCardNo)) {
|
|
|
|
|
return this._createResult(false, '身份证号码格式不正确');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验地址码
|
|
|
|
|
const addressCode = idCardNo.substring(0, 6);
|
|
|
|
|
const addressResult = this._checkAddressCode(addressCode);
|
|
|
|
|
if (!addressResult.valid) {
|
|
|
|
|
return addressResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 校验日期码(15位身份证年份是两位,这里转换为四位)
|
|
|
|
|
const year = `19${idCardNo.substring(6, 8)}`; // 15位身份证一般是1900年以后出生
|
|
|
|
|
const month = idCardNo.substring(8, 10);
|
|
|
|
|
const day = idCardNo.substring(10, 12);
|
|
|
|
|
const birDayCode = `${year}${month}${day}`;
|
|
|
|
|
|
|
|
|
|
const birthResult = this._checkBirthDayCode(birDayCode);
|
|
|
|
|
if (!birthResult.valid) {
|
|
|
|
|
return birthResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 提取身份证信息
|
|
|
|
|
const info = this._extractInfo(idCardNo, 15);
|
|
|
|
|
|
|
|
|
|
return this._createResult(true, '身份证号码校验通过', info);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 校验地址码
|
|
|
|
|
* @param {string} addressCode - 地址码
|
|
|
|
|
* @returns {Object} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_checkAddressCode(addressCode) {
|
|
|
|
|
if (!/^[1-9]\d{5}$/.test(addressCode)) {
|
|
|
|
|
return this._createResult(false, '地址码格式不正确');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const provinceCode = parseInt(addressCode.substring(0, 2));
|
|
|
|
|
if (!this.provinceAndCitys[provinceCode]) {
|
|
|
|
|
return this._createResult(false, '地址码对应的地区不存在');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this._createResult(true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 校验日期码
|
|
|
|
|
* @param {string} birDayCode - 日期码 (格式:YYYYMMDD)
|
|
|
|
|
* @returns {Object} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_checkBirthDayCode(birDayCode) {
|
|
|
|
|
if (!/^[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))$/.test(birDayCode)) {
|
|
|
|
|
return this._createResult(false, '出生日期格式不正确');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const year = parseInt(birDayCode.substring(0, 4), 10);
|
|
|
|
|
const month = parseInt(birDayCode.substring(4, 6), 10);
|
|
|
|
|
const day = parseInt(birDayCode.substring(6), 10);
|
|
|
|
|
|
|
|
|
|
// 检查年份范围(合理的出生年份范围)
|
|
|
|
|
const currentYear = new Date().getFullYear();
|
|
|
|
|
if (year < 1900 || year > currentYear) {
|
|
|
|
|
return this._createResult(false, '出生年份超出合理范围');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查日期有效性
|
|
|
|
|
const date = new Date(year, month - 1, day);
|
|
|
|
|
if (
|
|
|
|
|
date.getFullYear() !== year ||
|
|
|
|
|
date.getMonth() !== month - 1 ||
|
|
|
|
|
date.getDate() !== day
|
|
|
|
|
) {
|
|
|
|
|
return this._createResult(false, '出生日期无效');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否为未来日期
|
|
|
|
|
if (date > new Date()) {
|
|
|
|
|
return this._createResult(false, '出生日期不能是未来日期');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this._createResult(true);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 验证校检码
|
|
|
|
|
* @param {string} idCardNo - 18位身份证号码
|
|
|
|
|
* @returns {boolean} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_checkParityBit(idCardNo) {
|
|
|
|
|
const parityBit = idCardNo.charAt(17).toUpperCase();
|
|
|
|
|
return this._getParityBit(idCardNo) === parityBit;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算校检码
|
|
|
|
|
* @param {string} idCardNo - 18位身份证号码
|
|
|
|
|
* @returns {string} 校检码
|
|
|
|
|
*/
|
|
|
|
|
_getParityBit(idCardNo) {
|
|
|
|
|
const id17 = idCardNo.substring(0, 17);
|
|
|
|
|
let power = 0;
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < 17; i++) {
|
|
|
|
|
power += parseInt(id17.charAt(i), 10) * parseInt(this.powers[i], 10);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mod = power % 11;
|
|
|
|
|
return this.parityBit[mod];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 提取身份证信息
|
|
|
|
|
* @param {string} idCardNo - 身份证号码
|
|
|
|
|
* @param {number} type - 类型 15或18
|
|
|
|
|
* @returns {Object} 身份证信息
|
|
|
|
|
*/
|
|
|
|
|
_extractInfo(idCardNo, type) {
|
|
|
|
|
let addressCode, birthYear, birthMonth, birthDay, genderCode, gender;
|
|
|
|
|
|
|
|
|
|
// 地址码
|
|
|
|
|
addressCode = idCardNo.substring(0, 6);
|
|
|
|
|
const provinceCode = parseInt(addressCode.substring(0, 2));
|
|
|
|
|
const province = this.provinceAndCitys[provinceCode] || '';
|
|
|
|
|
|
|
|
|
|
// 出生日期
|
|
|
|
|
if (type === 18) {
|
|
|
|
|
birthYear = idCardNo.substring(6, 10);
|
|
|
|
|
birthMonth = idCardNo.substring(10, 12);
|
|
|
|
|
birthDay = idCardNo.substring(12, 14);
|
|
|
|
|
genderCode = idCardNo.substring(14, 17);
|
|
|
|
|
} else {
|
|
|
|
|
birthYear = `19${idCardNo.substring(6, 8)}`;
|
|
|
|
|
birthMonth = idCardNo.substring(8, 10);
|
|
|
|
|
birthDay = idCardNo.substring(10, 12);
|
|
|
|
|
genderCode = idCardNo.substring(12, 15);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 性别
|
|
|
|
|
gender = parseInt(genderCode, 10) % 2 === 1 ? '男' : '女';
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
addressCode,
|
|
|
|
|
province,
|
|
|
|
|
birthday: `${birthYear}-${birthMonth}-${birthDay}`,
|
|
|
|
|
gender,
|
|
|
|
|
length: type
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查是否为空
|
|
|
|
|
* @param {*} value - 要检查的值
|
|
|
|
|
* @returns {boolean} 是否为空
|
|
|
|
|
*/
|
|
|
|
|
_isEmpty(value) {
|
|
|
|
|
return value === null || value === undefined || value === '';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建校验结果对象
|
|
|
|
|
* @param {boolean} valid - 是否有效
|
|
|
|
|
* @param {string} message - 消息
|
|
|
|
|
* @param {Object} info - 附加信息
|
|
|
|
|
* @returns {Object} 校验结果
|
|
|
|
|
*/
|
|
|
|
|
_createResult(valid, message = '', info = {}) {
|
|
|
|
|
return {
|
|
|
|
|
valid,
|
|
|
|
|
message,
|
|
|
|
|
info
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将15位身份证升级为18位
|
|
|
|
|
* @param {string} idCardNo - 15位身份证号码
|
|
|
|
|
* @returns {string|boolean} 18位身份证号码或false(无效的15位身份证)
|
|
|
|
|
*/
|
|
|
|
|
upgrade15To18(idCardNo) {
|
|
|
|
|
if (idCardNo.length !== 15) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 先验证15位身份证是否有效
|
|
|
|
|
const validateResult = this._validate15IdCardNo(idCardNo);
|
|
|
|
|
if (!validateResult.valid) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 转换出生年份为4位
|
|
|
|
|
const year = `19${idCardNo.substring(6, 8)}`;
|
|
|
|
|
const rest = idCardNo.substring(8);
|
|
|
|
|
|
|
|
|
|
// 拼接17位
|
|
|
|
|
const id17 = `${idCardNo.substring(0, 6)}${year}${rest}`;
|
|
|
|
|
|
|
|
|
|
// 计算校验码
|
|
|
|
|
let power = 0;
|
|
|
|
|
for (let i = 0; i < 17; i++) {
|
|
|
|
|
power += parseInt(id17.charAt(i), 10) * parseInt(this.powers[i], 10);
|
|
|
|
|
}
|
|
|
|
|
const mod = power % 11;
|
|
|
|
|
const parityBit = this.parityBit[mod];
|
|
|
|
|
|
|
|
|
|
// 拼接18位身份证
|
|
|
|
|
return `${id17}${parityBit}`;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-10 09:36:04 +08:00
|
|
|
|
|
2025-07-14 15:38:39 +08:00
|
|
|
|
|
2025-03-28 15:19:42 +08:00
|
|
|
|
export const $api = {
|
|
|
|
|
msg,
|
|
|
|
|
prePage,
|
|
|
|
|
sleep,
|
|
|
|
|
request,
|
|
|
|
|
createRequest,
|
|
|
|
|
streamRequest,
|
|
|
|
|
chatRequest,
|
|
|
|
|
insertSortData,
|
2025-03-28 18:19:05 +08:00
|
|
|
|
uploadFile,
|
2025-03-29 12:06:35 +08:00
|
|
|
|
formatFileSize,
|
2025-04-07 09:10:55 +08:00
|
|
|
|
sendingMiniProgramMessage,
|
|
|
|
|
copyText
|
2025-03-28 15:19:42 +08:00
|
|
|
|
}
|
2024-11-08 11:55:23 +08:00
|
|
|
|
|
2025-03-29 12:06:35 +08:00
|
|
|
|
|
|
|
|
|
|
2024-11-08 11:55:23 +08:00
|
|
|
|
export default {
|
2025-03-28 15:19:42 +08:00
|
|
|
|
$api,
|
2024-11-08 11:55:23 +08:00
|
|
|
|
navTo,
|
2025-05-13 11:10:38 +08:00
|
|
|
|
navBack,
|
2025-03-28 15:19:42 +08:00
|
|
|
|
cloneDeep,
|
|
|
|
|
formatDate,
|
2025-09-29 11:53:10 +08:00
|
|
|
|
IdCardValidator,
|
2025-03-28 15:19:42 +08:00
|
|
|
|
getdeviceInfo,
|
|
|
|
|
checkingPhoneRegExp,
|
|
|
|
|
checkingEmailRegExp,
|
|
|
|
|
throttle,
|
|
|
|
|
debounce,
|
|
|
|
|
haversine,
|
|
|
|
|
getDistanceFromLatLonInKm,
|
|
|
|
|
vacanciesTo,
|
|
|
|
|
salaryGlobal,
|
|
|
|
|
customSystem,
|
|
|
|
|
setCheckedNodes,
|
|
|
|
|
formatTotal,
|
|
|
|
|
getWeeksOfMonth,
|
|
|
|
|
isFutureDate,
|
2025-04-07 09:10:55 +08:00
|
|
|
|
parseQueryParams,
|
2025-05-13 11:10:38 +08:00
|
|
|
|
appendScriptTagElement,
|
2025-05-15 14:17:51 +08:00
|
|
|
|
insertSortData,
|
2025-06-10 09:36:04 +08:00
|
|
|
|
isInWechatMiniProgramWebview,
|
2025-07-14 15:38:39 +08:00
|
|
|
|
isEmptyObject,
|
2024-11-08 11:55:23 +08:00
|
|
|
|
}
|