This commit is contained in:
史典卓
2025-03-28 15:19:42 +08:00
parent ad4eb162a5
commit 0216f6053a
396 changed files with 18278 additions and 9899 deletions

292
common/IndexedDBHelper.js Normal file
View File

@@ -0,0 +1,292 @@
class IndexedDBHelper {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
/**
* 初始化数据库(打开/创建)
* @param {Array} stores [{ name: "storeName", keyPath: "id", indexes: [{ name: "indexName", key: "keyPath", unique: false }] }]
* @returns {Promise}
*/
openDB(stores = []) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onupgradeneeded = (event) => {
this.db = event.target.result;
stores.forEach(store => {
if (!this.db.objectStoreNames.contains(store.name)) {
const objectStore = this.db.createObjectStore(store.name, {
keyPath: store.keyPath,
autoIncrement: store.autoIncrement || false
});
if (store.indexes) {
store.indexes.forEach(index => {
objectStore.createIndex(index.name, index.key, {
unique: index.unique
});
});
}
}
});
};
request.onsuccess = (event) => {
this.db = event.target.result;
console.log("✅ IndexedDB 连接成功");
resolve(this.db);
};
request.onerror = (event) => {
reject(`IndexedDB Error: ${event.target.error}`);
};
});
}
// 通用查询方法,按指定字段查询
async queryByField(storeName, fieldName, value) {
return new Promise(async (resolve, reject) => {
try {
if (!this.db) {
await this.openDB();
}
const transaction = this.db.transaction(storeName, 'readonly');
const store = transaction.objectStore(storeName);
if (!store.indexNames.contains(fieldName)) {
return reject(`索引 ${fieldName} 不存在`);
}
const index = store.index(fieldName);
const request = index.getAll(value);
request.onsuccess = (event) => {
resolve(event.target.result);
};
request.onerror = (event) => {
reject('查询失败: ' + event.target.error);
};
} catch (error) {
reject('查询错误: ' + error);
}
});
}
/**
* 添加数据(支持单条或批量)
* @param {string} storeName - 存储空间名称
* @param {Object|Array} data - 要添加的数据(单条对象或数组)
* @returns {Promise<number|Array<number>>} - 返回添加数据的ID单条返回数字批量返回数组
*/
add(storeName, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
// 统一处理为数组格式
const items = Array.isArray(data) ? data : [data];
const results = [];
// 监听每个添加操作的成功事件
items.forEach((item, index) => {
const request = store.add(item);
request.onsuccess = (event) => {
results[index] = event.target.result; // 保存生成的ID
};
request.onerror = (event) => {
transaction.abort(); // 遇到错误时中止事务
reject(`${index + 1} 条数据添加失败: ${event.target.error}`);
};
});
// 监听事务完成事件
transaction.oncomplete = () => {
// 单条数据返回单个ID批量返回数组
resolve(items.length === 1 ? results[0] : results);
};
// 统一错误处理
transaction.onerror = (event) => {
reject(`添加失败: ${event.target.error}`);
};
});
}
/**
* 读取数据(根据主键)
* @param {string} storeName
* @param {any} key
* @returns {Promise}
*/
get(storeName, key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = (event) => reject(`Get Error: ${event.target.error}`);
});
}
/**
* 读取所有数据兼容X5内核方案
* @param {string} storeName
* @returns {Promise}
*/
getAll(storeName) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
// 兼容性检测优先尝试原生getAll方法
if (typeof store.getAll === 'function') {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = (e) => reject(`GetAll Error: ${e.target.error}`);
}
// 降级方案:使用游标手动遍历
else {
const results = [];
const request = store.openCursor();
request.onsuccess = (e) => {
const cursor = e.target.result;
if (cursor) {
results.push(cursor.value);
cursor.continue();
} else {
resolve(results);
}
};
request.onerror = (e) => reject(`Cursor Error: ${e.target.error}`);
}
});
}
/**
* 获取表的总记录数
* @param {string} storeName - 表名Object Store 名称)
* @returns {Promise<number>} - 记录总数
*/
async getRecordCount(storeName) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
const request = store.count();
request.onsuccess = () => resolve(request.result);
request.onerror = (event) => reject(`❌ Count Error: ${event.target.error}`);
});
}
/**
* 更新数据
* @param {string} storeName
* @param {Object} data
* @returns {Promise}
*/
update(storeName, data) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.put(data);
request.onsuccess = () => resolve("Data updated successfully");
request.onerror = (event) => reject(`Update Error: ${event.target.error}`);
});
}
/**
* 删除数据(根据主键)
* @param {string} storeName
* @param {any} key
* @returns {Promise}
*/
delete(storeName, key) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = () => resolve("Data deleted successfully");
request.onerror = (event) => reject(`Delete Error: ${event.target.error}`);
});
}
/**
* 通过索引查询数据
* @param {string} storeName
* @param {string} indexName
* @param {any} value
* @returns {Promise}
*/
getByIndex(storeName, indexName, value) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readonly");
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.get(value);
request.onsuccess = () => resolve(request.result);
request.onerror = (event) => reject(`Get By Index Error: ${event.target.error}`);
});
}
/**
* 清空表
* @param {string} storeName
* @returns {Promise}
*/
clearStore(storeName) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.clear();
request.onsuccess = () => resolve("Store cleared successfully");
request.onerror = (event) => reject(`Clear Store Error: ${event.target.error}`);
});
}
/**
* 删除数据库
* @returns {Promise}
*/
deleteDB(dbNamed = null) {
return new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(dbNamed || this.dbName);
request.onsuccess = () => resolve("Database deleted successfully");
request.onerror = (event) => reject(`Delete DB Error: ${event.target.error}`);
});
}
async deleteOldestRecord(storeName) {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([storeName], "readwrite");
const store = transaction.objectStore(storeName);
const request = store.openCursor(); // 🔹 获取最早的记录(按主键 ID 排序)
request.onsuccess = function(event) {
const cursor = event.target.result;
if (cursor) {
console.log(`🗑️ 删除最早的记录 ID: ${cursor.key}`);
store.delete(cursor.key); // 🔥 删除最小 ID最早记录
resolve();
} else {
resolve(); // 没有记录时跳过
}
};
request.onerror = (event) => reject(`❌ Cursor Error: ${event.target.error}`);
});
}
}
export default IndexedDBHelper

View File

@@ -2,11 +2,20 @@
page {
min-height: calc(100vh - var(--window-top) - var(--status-bar-height) - var(--window-bottom));
font-size: 28rpx;
background-color: #f4f4f4;
background-color: #FFFFFF;
color: #333333;
overflow: hidden;
}
/* 禁止页面回弹 */
/* html,
body,
page {
overscroll-behavior: none;
overflow: hidden;
height: 100%;
} */
image {
width: 100%;
height: 100%;

View File

@@ -1,6 +1,36 @@
import useUserStore from "../stores/useUserStore";
import {
request,
createRequest,
uploadFile
} from "../utils/request";
import streamRequest, {
chatRequest
} from "../utils/streamRequest.js";
const msg = (title, duration = 1500, mask = false, icon = 'none', image) => {
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) => {
if (Boolean(title) === false) {
return;
}
@@ -63,14 +93,376 @@ function getdeviceInfo() {
function sleep(time) {
return new Promise((resolve) => setTimeout(resolve, time))
}
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
}
class CustomSystem {
constructor() {
const systemInfo = uni.getSystemInfoSync();
this.systemInfo = systemInfo
}
}
const customSystem = new CustomSystem()
function setCheckedNodes(nodes, ids) {
// 处理每个第一层节点
nodes.forEach((firstLayer) => {
// 初始化或重置计数器
firstLayer.checkednumber = 0;
// 递归处理子树
const traverse = (node) => {
// 设置当前节点选中状态
const shouldCheck = ids.includes(node.id);
if (shouldCheck) node.checked = true;
// 统计后代节点(排除首层自身)
if (node !== firstLayer && node.checked) {
firstLayer.checkednumber++;
}
// 递归子节点
if (node.children) {
node.children.forEach((child) => traverse(child));
}
};
// 启动当前首层节点的遍历
traverse(firstLayer);
});
}
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;
}
export const $api = {
msg,
prePage,
sleep,
request,
createRequest,
streamRequest,
chatRequest,
insertSortData,
uploadFile
}
export default {
'$api': {
msg,
prePage,
sleep
},
$api,
navTo,
getdeviceInfo
cloneDeep,
formatDate,
getdeviceInfo,
checkingPhoneRegExp,
checkingEmailRegExp,
throttle,
debounce,
haversine,
getDistanceFromLatLonInKm,
vacanciesTo,
salaryGlobal,
customSystem,
setCheckedNodes,
formatTotal,
getWeeksOfMonth,
isFutureDate,
parseQueryParams
}