You've already forked template-MP
新增:全量优化
This commit is contained in:
30
common/constants/app.js
Normal file
30
common/constants/app.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 应用相关常量
|
||||
*/
|
||||
|
||||
// 应用信息
|
||||
export const APP_NAME = '小程序模板'
|
||||
export const APP_VERSION = '1.0.0'
|
||||
|
||||
// 页面路径常量
|
||||
export const PAGE_PATHS = {
|
||||
INDEX: '/pages/index/index',
|
||||
WELCOME: '/subPages/welcome/index',
|
||||
LOGIN: '/pages/login/login', // 示例路径
|
||||
REGISTER: '/pages/register/register' // 示例路径
|
||||
}
|
||||
|
||||
// 存储键名常量
|
||||
export const STORAGE_KEYS = {
|
||||
TOKEN: 'token',
|
||||
USER_INFO: 'userInfo',
|
||||
THEME: 'theme',
|
||||
LANG: 'language'
|
||||
}
|
||||
|
||||
// 事件常量
|
||||
export const EVENTS = {
|
||||
USER_LOGIN: 'userLogin',
|
||||
USER_LOGOUT: 'userLogout',
|
||||
THEME_CHANGE: 'themeChange'
|
||||
}
|
||||
@@ -1,118 +1,97 @@
|
||||
// 定义内外边距,历遍1-100
|
||||
@for $i from 0 through 100 {
|
||||
// 只要双数和能被5除尽的数
|
||||
@if $i % 2==0 or $i % 5==0 {
|
||||
// 得出:u-margin-30或者u-m-30
|
||||
.w-#{$i} {
|
||||
width: calc($i * 1%) !important;
|
||||
}
|
||||
.p-x-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
padding-right: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-y-#{$i} {
|
||||
padding-top: $i + rpx !important;
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-x-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
margin-right: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-y-#{$i} {
|
||||
margin-top: $i + rpx !important;
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
margin-right: $i + rpx !important;
|
||||
margin-top: $i + rpx !important;
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
padding-right: $i + rpx !important;
|
||||
padding-top: $i + rpx !important;
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-l-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-t-#{$i} {
|
||||
margin-top: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-r-#{$i} {
|
||||
margin-right: $i + rpx !important;
|
||||
}
|
||||
|
||||
.m-b-#{$i} {
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-l-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-t-#{$i} {
|
||||
padding-top: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-r-#{$i} {
|
||||
padding-right: $i + rpx !important;
|
||||
}
|
||||
|
||||
.p-b-#{$i} {
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
|
||||
.l-p-#{$i} {
|
||||
letter-spacing: $i + rpx !important;
|
||||
}
|
||||
|
||||
.z-i-#{$i} {
|
||||
z-index: $i;
|
||||
}
|
||||
|
||||
.l-h-#{$i} {
|
||||
line-height: $i + rpx !important;
|
||||
}
|
||||
.r-#{$i} {
|
||||
right: $i + rpx !important;
|
||||
}
|
||||
.l-#{$i} {
|
||||
left: $i + rpx !important;
|
||||
}
|
||||
|
||||
.t-#{$i} {
|
||||
top: $i + rpx !important;
|
||||
}
|
||||
.b-#{$i} {
|
||||
bottom: $i + rpx !important;
|
||||
}
|
||||
// 定义内外边距,只生成常用的数值
|
||||
$spacing-sizes: (0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 64, 68, 72, 76, 80, 84, 88, 92, 96, 100);
|
||||
@each $i in $spacing-sizes {
|
||||
.w-#{$i} {
|
||||
width: calc($i * 1%) !important;
|
||||
}
|
||||
.p-x-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
padding-right: $i + rpx !important;
|
||||
}
|
||||
.p-y-#{$i} {
|
||||
padding-top: $i + rpx !important;
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
.m-x-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
margin-right: $i + rpx !important;
|
||||
}
|
||||
.m-y-#{$i} {
|
||||
margin-top: $i + rpx !important;
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
.m-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
margin-right: $i + rpx !important;
|
||||
margin-top: $i + rpx !important;
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
.p-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
padding-right: $i + rpx !important;
|
||||
padding-top: $i + rpx !important;
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
.m-l-#{$i} {
|
||||
margin-left: $i + rpx !important;
|
||||
}
|
||||
.m-t-#{$i} {
|
||||
margin-top: $i + rpx !important;
|
||||
}
|
||||
.m-r-#{$i} {
|
||||
margin-right: $i + rpx !important;
|
||||
}
|
||||
.m-b-#{$i} {
|
||||
margin-bottom: $i + rpx !important;
|
||||
}
|
||||
.p-l-#{$i} {
|
||||
padding-left: $i + rpx !important;
|
||||
}
|
||||
.p-t-#{$i} {
|
||||
padding-top: $i + rpx !important;
|
||||
}
|
||||
.p-r-#{$i} {
|
||||
padding-right: $i + rpx !important;
|
||||
}
|
||||
.p-b-#{$i} {
|
||||
padding-bottom: $i + rpx !important;
|
||||
}
|
||||
.l-p-#{$i} {
|
||||
letter-spacing: $i + rpx !important;
|
||||
}
|
||||
.z-i-#{$i} {
|
||||
z-index: $i;
|
||||
}
|
||||
.l-h-#{$i} {
|
||||
line-height: $i + rpx !important;
|
||||
}
|
||||
.r-#{$i} {
|
||||
right: $i + rpx !important;
|
||||
}
|
||||
.l-#{$i} {
|
||||
left: $i + rpx !important;
|
||||
}
|
||||
.t-#{$i} {
|
||||
top: $i + rpx !important;
|
||||
}
|
||||
.b-#{$i} {
|
||||
bottom: $i + rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 定义字体(rpx)单位,大于或等于20的都为rpx单位字体
|
||||
@for $i from 9 through 60 {
|
||||
// 定义字体(rpx)单位,只生成常用的字体大小
|
||||
$font-sizes: (9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 40, 44, 48, 52, 56, 60);
|
||||
@each $i in $font-sizes {
|
||||
.font-#{$i} {
|
||||
font-size: $i + rpx !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 圆角
|
||||
@for $i from 4 through 60 {
|
||||
$rounded-sizes: (4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60);
|
||||
@each $i in $rounded-sizes {
|
||||
.rounded-#{$i} {
|
||||
border-radius: $i + rpx;
|
||||
}
|
||||
}
|
||||
|
||||
// 多行文本溢出
|
||||
@for $i from 1 through 5 {
|
||||
.over-line-#{$i} {
|
||||
@@ -125,3 +104,120 @@
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
// 显示相关工具类
|
||||
.display-none {
|
||||
display: none !important;
|
||||
}
|
||||
.display-block {
|
||||
display: block !important;
|
||||
}
|
||||
.display-inline {
|
||||
display: inline !important;
|
||||
}
|
||||
.display-inline-block {
|
||||
display: inline-block !important;
|
||||
}
|
||||
.display-flex {
|
||||
display: flex !important;
|
||||
}
|
||||
// 浮动相关
|
||||
.float-left {
|
||||
float: left !important;
|
||||
}
|
||||
.float-right {
|
||||
float: right !important;
|
||||
}
|
||||
.float-none {
|
||||
float: none !important;
|
||||
}
|
||||
.clearfix::after {
|
||||
content: '';
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
// 文本对齐
|
||||
.text-left {
|
||||
text-align: left !important;
|
||||
}
|
||||
.text-center {
|
||||
text-align: center !important;
|
||||
}
|
||||
.text-right {
|
||||
text-align: right !important;
|
||||
}
|
||||
.text-justify {
|
||||
text-align: justify !important;
|
||||
}
|
||||
// 文本装饰
|
||||
.text-underline {
|
||||
text-decoration: underline !important;
|
||||
}
|
||||
.text-line-through {
|
||||
text-decoration: line-through !important;
|
||||
}
|
||||
.text-no-underline {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
// 字体粗细
|
||||
.font-normal {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
.font-bold {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
// 文本大小写
|
||||
.text-uppercase {
|
||||
text-transform: uppercase !important;
|
||||
}
|
||||
.text-lowercase {
|
||||
text-transform: lowercase !important;
|
||||
}
|
||||
.text-capitalize {
|
||||
text-transform: capitalize !important;
|
||||
}
|
||||
// 垂直对齐
|
||||
.align-baseline {
|
||||
vertical-align: baseline !important;
|
||||
}
|
||||
.align-top {
|
||||
vertical-align: top !important;
|
||||
}
|
||||
.align-middle {
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
.align-bottom {
|
||||
vertical-align: bottom !important;
|
||||
}
|
||||
// 位置相关
|
||||
.position-relative {
|
||||
position: relative !important;
|
||||
}
|
||||
.position-absolute {
|
||||
position: absolute !important;
|
||||
}
|
||||
.position-fixed {
|
||||
position: fixed !important;
|
||||
}
|
||||
.position-static {
|
||||
position: static !important;
|
||||
}
|
||||
// 溢出处理
|
||||
.overflow-auto {
|
||||
overflow: auto !important;
|
||||
}
|
||||
.overflow-hidden {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.overflow-visible {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.overflow-scroll {
|
||||
overflow: scroll !important;
|
||||
}
|
||||
// 可见性
|
||||
.visible {
|
||||
visibility: visible !important;
|
||||
}
|
||||
.invisible {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
88
common/styles/dark-mode.scss
Normal file
88
common/styles/dark-mode.scss
Normal file
@@ -0,0 +1,88 @@
|
||||
/* 暗黑模式样式 */
|
||||
:root {
|
||||
// 默认使用亮色模式变量
|
||||
--uni-color-primary: #007aff;
|
||||
--uni-color-success: #4cd964;
|
||||
--uni-color-warning: #f0ad4e;
|
||||
--uni-color-error: #dd524d;
|
||||
|
||||
--uni-text-color: #333;
|
||||
--uni-text-color-inverse: #fff;
|
||||
--uni-text-color-grey: #999;
|
||||
--uni-text-color-placeholder: #808080;
|
||||
--uni-text-color-disable: #c0c0c0;
|
||||
|
||||
--uni-bg-color: #ffffff;
|
||||
--uni-bg-color-grey: #f8f8f8;
|
||||
--uni-bg-color-hover: #f1f1f1;
|
||||
--uni-bg-color-mask: rgba(0, 0, 0, 0.4);
|
||||
|
||||
--uni-border-color: #c8c7cc;
|
||||
}
|
||||
|
||||
/* 暗黑模式 */
|
||||
[data-theme='dark'] {
|
||||
--uni-color-primary: #0a84ff;
|
||||
--uni-color-success: #34c759;
|
||||
--uni-color-warning: #ff9500;
|
||||
--uni-color-error: #ff3b30;
|
||||
|
||||
--uni-text-color: #f2f2f7;
|
||||
--uni-text-color-inverse: #000000;
|
||||
--uni-text-color-grey: #8e8e93;
|
||||
--uni-text-color-placeholder: #4c4c4c;
|
||||
--uni-text-color-disable: #636366;
|
||||
|
||||
--uni-bg-color: #1c1c1e;
|
||||
--uni-bg-color-grey: #2c2c2e;
|
||||
--uni-bg-color-hover: #3a3a3c;
|
||||
--uni-bg-color-mask: rgba(0, 0, 0, 0.6);
|
||||
|
||||
--uni-border-color: #434346;
|
||||
}
|
||||
|
||||
/* 系统级暗黑模式 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme='light']) {
|
||||
--uni-color-primary: #0a84ff;
|
||||
--uni-color-success: #34c759;
|
||||
--uni-color-warning: #ff9500;
|
||||
--uni-color-error: #ff3b30;
|
||||
|
||||
--uni-text-color: #f2f2f7;
|
||||
--uni-text-color-inverse: #000000;
|
||||
--uni-text-color-grey: #8e8e93;
|
||||
--uni-text-color-placeholder: #4c4c4c;
|
||||
--uni-text-color-disable: #636366;
|
||||
|
||||
--uni-bg-color: #1c1c1e;
|
||||
--uni-bg-color-grey: #2c2c2e;
|
||||
--uni-bg-color-hover: #3a3a3c;
|
||||
--uni-bg-color-mask: rgba(0, 0, 0, 0.6);
|
||||
|
||||
--uni-border-color: #434346;
|
||||
}
|
||||
}
|
||||
|
||||
/* 应用暗黑模式的通用类 */
|
||||
.dark-mode {
|
||||
background-color: var(--uni-bg-color);
|
||||
color: var(--uni-text-color);
|
||||
}
|
||||
|
||||
.dark-mode .dark-invert {
|
||||
background-color: var(--uni-bg-color-inverse);
|
||||
color: var(--uni-text-color-inverse);
|
||||
}
|
||||
|
||||
/* 通用暗黑模式切换类 */
|
||||
.light-mode {
|
||||
background-color: var(--uni-bg-color);
|
||||
color: var(--uni-text-color);
|
||||
}
|
||||
|
||||
/* 通用暗黑模式切换类 */
|
||||
.dark-mode {
|
||||
background-color: var(--uni-bg-color);
|
||||
color: var(--uni-text-color);
|
||||
}
|
||||
80
common/utils/env.js
Normal file
80
common/utils/env.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 环境变量验证工具
|
||||
*/
|
||||
/**
|
||||
* 验证必需的环境变量
|
||||
* @param {Array<string>} requiredVars 必需的环境变量键名数组
|
||||
* @throws {Error} 如果有任何必需的环境变量缺失则抛出错误
|
||||
*/
|
||||
export function validateEnv(requiredVars = []) {
|
||||
const missingVars = []
|
||||
for (const key of requiredVars) {
|
||||
if (!import.meta.env[key]) {
|
||||
missingVars.push(key)
|
||||
}
|
||||
}
|
||||
if (missingVars.length > 0) {
|
||||
throw new Error(`缺少必需的环境变量: ${missingVars.join(', ')}`)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取环境变量,带默认值和类型转换
|
||||
* @param {string} key 环境变量键名
|
||||
* @param {any} defaultValue 默认值
|
||||
* @param {string} type 类型 ('string' | 'number' | 'boolean' | 'array')
|
||||
* @returns {any} 环境变量值
|
||||
*/
|
||||
export function getEnv(key, defaultValue = null, type = 'string') {
|
||||
const value = import.meta.env[key]
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return defaultValue
|
||||
}
|
||||
switch (type) {
|
||||
case 'number':
|
||||
const numValue = Number(value)
|
||||
return isNaN(numValue) ? defaultValue : numValue
|
||||
case 'boolean':
|
||||
return value === 'true' || value === '1'
|
||||
case 'array':
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return Array.isArray(defaultValue) ? defaultValue : []
|
||||
}
|
||||
case 'string':
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 环境变量配置
|
||||
*/
|
||||
export const EnvConfig = {
|
||||
// API基础URL
|
||||
BASE_URL: getEnv('VITE_BASE_URL', '', 'string'),
|
||||
// 静态资源URL
|
||||
ASSETS_URL: getEnv('VITE_ASSETSURL', '', 'string'),
|
||||
// 小程序APPID
|
||||
APP_ID: getEnv('VITE_APPID', '', 'string'),
|
||||
// UNI-APPID
|
||||
UNI_APP_ID: getEnv('VITE_UNI_APPID', '', 'string'),
|
||||
// 是否为开发环境
|
||||
IS_DEV: getEnv('NODE_ENV', 'development', 'string') === 'development',
|
||||
// 是否为生产环境
|
||||
IS_PROD: getEnv('NODE_ENV', 'development', 'string') === 'production',
|
||||
// 调试模式
|
||||
DEBUG: getEnv('VITE_DEBUG', false, 'boolean'),
|
||||
// API超时时间(毫秒)
|
||||
API_TIMEOUT: getEnv('VITE_API_TIMEOUT', 10000, 'number'),
|
||||
}
|
||||
// 验证必需的环境变量
|
||||
try {
|
||||
validateEnv(['VITE_BASE_URL', 'VITE_APPID'])
|
||||
} catch (error) {
|
||||
console.error('环境变量验证失败:', error.message)
|
||||
// 在开发环境中抛出错误
|
||||
if (EnvConfig.IS_DEV) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
export default EnvConfig
|
||||
@@ -1,6 +1,5 @@
|
||||
const baseUrl = import.meta.env.VITE_BASE_URL
|
||||
const assetsUrl = import.meta.env.VITE_ASSETSURL
|
||||
|
||||
/**
|
||||
* 工具类 - 提供常用的工具方法
|
||||
* @class Tool
|
||||
@@ -13,11 +12,9 @@ class Tool {
|
||||
SUCCESS: 1,
|
||||
LOADING: 2,
|
||||
}
|
||||
|
||||
// 字体加载状态缓存
|
||||
this.loadedFonts = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
* 文字轻提示
|
||||
* @param {string} str 提示文字
|
||||
@@ -30,13 +27,11 @@ class Tool {
|
||||
console.warn('alert方法需要提供提示文字')
|
||||
return
|
||||
}
|
||||
|
||||
const iconMap = {
|
||||
[this.ICON_TYPES.NONE]: 'none',
|
||||
[this.ICON_TYPES.SUCCESS]: 'success',
|
||||
[this.ICON_TYPES.LOADING]: 'loading',
|
||||
}
|
||||
|
||||
uni.showToast({
|
||||
title: String(str),
|
||||
icon: iconMap[icon] || 'none',
|
||||
@@ -49,7 +44,6 @@ class Tool {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示loading加载
|
||||
* @param {string} [title=' '] 加载文案
|
||||
@@ -58,14 +52,12 @@ class Tool {
|
||||
loading(title = ' ', mask = true) {
|
||||
uni.showLoading({ title, mask })
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭loading提示框
|
||||
*/
|
||||
hideLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一处理URL格式,确保以/开头
|
||||
* @param {string} url 页面地址
|
||||
@@ -76,17 +68,14 @@ class Tool {
|
||||
if (!url || typeof url !== 'string') {
|
||||
throw new Error('URL必须是字符串')
|
||||
}
|
||||
|
||||
return url.startsWith('/') ? url : `/${url}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 可返回跳转(导航到新页面)
|
||||
* @param {string} url 页面地址
|
||||
*/
|
||||
navigateTo(url) {
|
||||
const formattedUrl = this._formatUrl(url)
|
||||
|
||||
uni.navigateTo({
|
||||
url: formattedUrl,
|
||||
fail: err => {
|
||||
@@ -95,7 +84,6 @@ class Tool {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 不可返回跳转(重定向到新页面)
|
||||
* @param {string} url 页面地址
|
||||
@@ -103,7 +91,6 @@ class Tool {
|
||||
redirectTo(url) {
|
||||
uni.redirectTo({ url: this._formatUrl(url) })
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除页面栈跳转(重新启动到新页面)
|
||||
* @param {string} url 页面地址
|
||||
@@ -111,7 +98,6 @@ class Tool {
|
||||
reLaunch(url) {
|
||||
uni.reLaunch({ url: this._formatUrl(url) })
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转tabBar页
|
||||
* @param {string} url 页面地址
|
||||
@@ -119,7 +105,6 @@ class Tool {
|
||||
switchTab(url) {
|
||||
uni.switchTab({ url: this._formatUrl(url) })
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上一页面或指定页面
|
||||
* @param {number} [delta=1] 返回的页面数
|
||||
@@ -127,7 +112,6 @@ class Tool {
|
||||
*/
|
||||
navigateBack(delta = 1, fallbackUrl = '/pages/index/index') {
|
||||
const pages = getCurrentPages()
|
||||
|
||||
if (pages.length <= 1) {
|
||||
console.warn('无上一页,使用回退地址')
|
||||
uni.reLaunch({ url: fallbackUrl })
|
||||
@@ -135,7 +119,6 @@ class Tool {
|
||||
uni.navigateBack({ delta })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作本地缓存
|
||||
* @param {string} key 缓存键值
|
||||
@@ -146,24 +129,20 @@ class Tool {
|
||||
if (typeof key !== 'string') {
|
||||
throw new Error('key必须是字符串')
|
||||
}
|
||||
|
||||
// 设置操作
|
||||
if (value !== undefined && value !== null) {
|
||||
uni.setStorageSync(key, value)
|
||||
return
|
||||
}
|
||||
|
||||
// 读取操作
|
||||
if (key !== '#') {
|
||||
return uni.getStorageSync(key)
|
||||
}
|
||||
|
||||
// 特殊操作
|
||||
if (key === '#') {
|
||||
uni.clearStorageSync()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定缓存
|
||||
* @param {string} key 要删除的缓存键
|
||||
@@ -172,10 +151,8 @@ class Tool {
|
||||
if (typeof key !== 'string') {
|
||||
throw new Error('key必须是字符串')
|
||||
}
|
||||
|
||||
uni.removeStorageSync(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存信息
|
||||
* @returns {Object} 缓存信息
|
||||
@@ -183,7 +160,6 @@ class Tool {
|
||||
getStorageInfo() {
|
||||
return uni.getStorageInfoSync()
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文本到剪贴板
|
||||
* @param {string} data 要复制的文本
|
||||
@@ -194,7 +170,6 @@ class Tool {
|
||||
this.alert('暂无内容')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.setClipboardData({
|
||||
@@ -203,7 +178,6 @@ class Tool {
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
|
||||
this.alert('复制成功')
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -212,7 +186,6 @@ class Tool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入外部字体
|
||||
* @param {string} fontName 字体文件名(不含路径)
|
||||
@@ -222,15 +195,12 @@ class Tool {
|
||||
if (!fontName || typeof fontName !== 'string') {
|
||||
throw new Error('字体名称必须是字符串')
|
||||
}
|
||||
|
||||
// 检查是否已加载过
|
||||
if (this.loadedFonts.has(fontName)) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
const fontFamily = fontName.replace(/\.[^/.]+$/, '') // 移除文件扩展名
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.loadFontFace({
|
||||
family: fontFamily,
|
||||
@@ -240,7 +210,6 @@ class Tool {
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
|
||||
this.loadedFonts.add(fontName)
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -248,7 +217,6 @@ class Tool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存图片到相册
|
||||
* @param {string} url 图片URL
|
||||
@@ -259,7 +227,6 @@ class Tool {
|
||||
this.alert('图片地址不能为空')
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查权限
|
||||
const { authSetting } = await new Promise((resolve, reject) => {
|
||||
@@ -268,7 +235,6 @@ class Tool {
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
|
||||
if (!authSetting['scope.writePhotosAlbum']) {
|
||||
// 请求权限
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -279,7 +245,6 @@ class Tool {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 获取图片信息
|
||||
const { path } = await new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
@@ -288,7 +253,6 @@ class Tool {
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
|
||||
// 保存到相册
|
||||
await new Promise((resolve, reject) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
@@ -297,12 +261,10 @@ class Tool {
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
|
||||
this.alert('已保存到相册')
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('保存图片失败:', error)
|
||||
|
||||
if (error.errMsg && error.errMsg.includes('auth')) {
|
||||
// 权限相关错误
|
||||
await new Promise(resolve => {
|
||||
@@ -313,16 +275,240 @@ class Tool {
|
||||
success: resolve,
|
||||
})
|
||||
})
|
||||
|
||||
uni.openSetting()
|
||||
} else {
|
||||
this.alert('保存失败,请重试')
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数
|
||||
* @param {Function} func 要防抖的函数
|
||||
* @param {number} wait 延迟时间(ms)
|
||||
* @param {boolean} immediate 是否立即执行
|
||||
* @returns {Function} 防抖后的函数
|
||||
*/
|
||||
debounce(func, wait, immediate = false) {
|
||||
let timeout
|
||||
return function (...args) {
|
||||
const later = () => {
|
||||
timeout = null
|
||||
if (!immediate) func.apply(this, args)
|
||||
}
|
||||
const callNow = immediate && !timeout
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(later, wait)
|
||||
if (callNow) func.apply(this, args)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 节流函数
|
||||
* @param {Function} func 要节流的函数
|
||||
* @param {number} wait 延迟时间(ms)
|
||||
* @returns {Function} 节流后的函数
|
||||
*/
|
||||
throttle(func, wait) {
|
||||
let timeout
|
||||
let previous = 0
|
||||
return function (...args) {
|
||||
const now = Date.now()
|
||||
const remaining = wait - (now - previous)
|
||||
const context = this
|
||||
if (remaining <= 0 || remaining > wait) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
timeout = null
|
||||
}
|
||||
previous = now
|
||||
func.apply(context, args)
|
||||
} else if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
previous = Date.now()
|
||||
timeout = null
|
||||
func.apply(context, args)
|
||||
}, remaining)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 日期格式化
|
||||
* @param {Date|string|number} date 日期对象、字符串或时间戳
|
||||
* @param {string} fmt 格式字符串, 默认为 'YYYY-MM-DD HH:mm:ss'
|
||||
* @returns {string} 格式化后的日期字符串
|
||||
*/
|
||||
formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
|
||||
// 如果传入的是时间戳,转换为Date对象
|
||||
if (typeof date === 'number') {
|
||||
date = new Date(date)
|
||||
} else if (typeof date === 'string') {
|
||||
// 如果传入的是字符串,尝试转换为Date对象
|
||||
date = new Date(date)
|
||||
}
|
||||
if (!(date instanceof Date) || isNaN(date.getTime())) {
|
||||
throw new Error('Invalid date')
|
||||
}
|
||||
const o = {
|
||||
'Y+': date.getFullYear(),
|
||||
'M+': date.getMonth() + 1,
|
||||
'D+': date.getDate(),
|
||||
'H+': date.getHours(),
|
||||
'm+': date.getMinutes(),
|
||||
's+': date.getSeconds(),
|
||||
'q+': Math.floor((date.getMonth() + 3) / 3),
|
||||
S: date.getMilliseconds(),
|
||||
}
|
||||
for (let k in o) {
|
||||
if (new RegExp('(' + k + ')').test(fmt)) {
|
||||
let str = o[k] + ''
|
||||
if (k === 'S') {
|
||||
fmt = fmt.replace(RegExp.$1, str.padStart(3, '0'))
|
||||
} else {
|
||||
fmt = fmt.replace(RegExp.$1, str.length === 1 ? '0' + str : str)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
/**
|
||||
* 深拷贝函数
|
||||
* @param {*} obj 要深拷贝的对象
|
||||
* @returns {*} 拷贝后的对象
|
||||
*/
|
||||
deepClone(obj) {
|
||||
// 处理null、undefined和原始类型
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj
|
||||
}
|
||||
// 处理日期对象
|
||||
if (obj instanceof Date) {
|
||||
return new Date(obj.getTime())
|
||||
}
|
||||
// 处理数组和对象
|
||||
if (obj instanceof Array) {
|
||||
return obj.map(item => this.deepClone(item))
|
||||
}
|
||||
// 处理普通对象
|
||||
const clonedObj = {}
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
clonedObj[key] = this.deepClone(obj[key])
|
||||
}
|
||||
}
|
||||
return clonedObj
|
||||
}
|
||||
/**
|
||||
* 表单验证工具
|
||||
* @class Validator
|
||||
*/
|
||||
getValidator() {
|
||||
const rules = {
|
||||
/**
|
||||
* 必填验证
|
||||
* @param {*} value 值
|
||||
* @param {boolean} required 是否必填
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
required(value, required = true) {
|
||||
if (!required) return true
|
||||
return value !== undefined && value !== null && value !== ''
|
||||
},
|
||||
/**
|
||||
* 邮箱验证
|
||||
* @param {string} value 值
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
email(value) {
|
||||
if (!value) return true
|
||||
const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
|
||||
return reg.test(value)
|
||||
},
|
||||
/**
|
||||
* 手机号验证
|
||||
* @param {string} value 值
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
mobile(value) {
|
||||
if (!value) return true
|
||||
const reg = /^1[3-9]\d{9}$/
|
||||
return reg.test(value)
|
||||
},
|
||||
/**
|
||||
* 最小长度验证
|
||||
* @param {string} value 值
|
||||
* @param {number} length 最小长度
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
minLength(value, length) {
|
||||
if (!value) return true
|
||||
return value.length >= length
|
||||
},
|
||||
/**
|
||||
* 最大长度验证
|
||||
* @param {string} value 值
|
||||
* @param {number} length 最大长度
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
maxLength(value, length) {
|
||||
if (!value) return true
|
||||
return value.length <= length
|
||||
},
|
||||
/**
|
||||
* 数值范围验证
|
||||
* @param {number} value 值
|
||||
* @param {number} min 最小值
|
||||
* @param {number} max 最大值
|
||||
* @returns {boolean} 验证结果
|
||||
*/
|
||||
range(value, min, max) {
|
||||
if (value === undefined || value === null) return true
|
||||
return value >= min && value <= max
|
||||
},
|
||||
}
|
||||
return {
|
||||
/**
|
||||
* 验证单个字段
|
||||
* @param {*} value 值
|
||||
* @param {Object} rule 规则
|
||||
* @returns {string|null} 错误信息
|
||||
*/
|
||||
validateField(value, rule) {
|
||||
// 必填验证
|
||||
if (rule.required !== undefined && !rules.required(value, rule.required)) {
|
||||
return rule.message || '此字段为必填项'
|
||||
}
|
||||
// 如果值为空且非必填,跳过其他验证
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null
|
||||
}
|
||||
// 其他验证规则
|
||||
if (rule.type && rules[rule.type]) {
|
||||
if (!rules[rule.type](value, rule.param1, rule.param2)) {
|
||||
return rule.message || '字段格式不正确'
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
/**
|
||||
* 验证整个表单
|
||||
* @param {Object} data 表单数据
|
||||
* @param {Object} rules 验证规则
|
||||
* @returns {Object} 验证结果 { isValid: boolean, errors: Object }
|
||||
*/
|
||||
validate(data, rules) {
|
||||
const errors = {}
|
||||
let isValid = true
|
||||
for (let field in rules) {
|
||||
const error = this.validateField(data[field], rules[field])
|
||||
if (error) {
|
||||
errors[field] = error
|
||||
isValid = false
|
||||
}
|
||||
}
|
||||
return { isValid, errors }
|
||||
},
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 微信支付
|
||||
* @param {Object} paymentData 支付参数
|
||||
@@ -338,6 +524,11 @@ class Tool {
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 文件上传
|
||||
* @param {string} filePath 文件路径
|
||||
* @returns {Promise<Object>} 上传结果
|
||||
*/
|
||||
upload(filePath) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.uploadFile({
|
||||
@@ -357,7 +548,120 @@ class Tool {
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 暗黑模式切换
|
||||
* @param {boolean} isDark 是否启用暗黑模式
|
||||
*/
|
||||
toggleDarkMode(isDark) {
|
||||
try {
|
||||
if (isDark) {
|
||||
// 启用暗黑模式
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
uni.setStorageSync('theme', 'dark')
|
||||
} else {
|
||||
// 启用亮色模式
|
||||
document.documentElement.setAttribute('data-theme', 'light')
|
||||
uni.setStorageSync('theme', 'light')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('切换主题失败:', error)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取当前主题模式
|
||||
* @returns {string} 当前主题 ('light' | 'dark')
|
||||
*/
|
||||
getCurrentTheme() {
|
||||
try {
|
||||
// 优先使用用户设置的主题
|
||||
const savedTheme = uni.getStorageSync('theme')
|
||||
if (savedTheme) {
|
||||
return savedTheme
|
||||
}
|
||||
// 检查系统主题
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
if (systemInfo.theme) {
|
||||
return systemInfo.theme
|
||||
}
|
||||
// 默认返回亮色主题
|
||||
return 'light'
|
||||
} catch (error) {
|
||||
console.error('获取主题失败:', error)
|
||||
return 'light'
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 权限检查
|
||||
* @param {string} permission 权限标识
|
||||
* @returns {boolean} 是否拥有权限
|
||||
*/
|
||||
hasPermission(permission) {
|
||||
try {
|
||||
// 获取用户权限列表
|
||||
const permissions = uni.getStorageSync('permissions') || []
|
||||
return permissions.includes(permission)
|
||||
} catch (error) {
|
||||
console.error('权限检查失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 设置用户权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
*/
|
||||
setPermissions(permissions) {
|
||||
try {
|
||||
uni.setStorageSync('permissions', Array.isArray(permissions) ? permissions : [])
|
||||
} catch (error) {
|
||||
console.error('设置权限失败:', error)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取用户所有权限
|
||||
* @returns {Array<string>} 权限列表
|
||||
*/
|
||||
getUserPermissions() {
|
||||
try {
|
||||
return uni.getStorageSync('permissions') || []
|
||||
} catch (error) {
|
||||
console.error('获取权限失败:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查用户是否有任意权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
* @returns {boolean} 是否有任意权限
|
||||
*/
|
||||
hasAnyPermission(permissions) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
return false
|
||||
}
|
||||
const userPermissions = this.getUserPermissions()
|
||||
return permissions.some(permission => userPermissions.includes(permission))
|
||||
}
|
||||
/**
|
||||
* 检查用户是否拥有所有权限
|
||||
* @param {Array<string>} permissions 权限列表
|
||||
* @returns {boolean} 是否拥有所有权限
|
||||
*/
|
||||
hasAllPermissions(permissions) {
|
||||
if (!Array.isArray(permissions)) {
|
||||
return false
|
||||
}
|
||||
const userPermissions = this.getUserPermissions()
|
||||
return permissions.every(permission => userPermissions.includes(permission))
|
||||
}
|
||||
/**
|
||||
* 清除用户权限
|
||||
*/
|
||||
clearPermissions() {
|
||||
try {
|
||||
uni.removeStorageSync('permissions')
|
||||
} catch (error) {
|
||||
console.error('清除权限失败:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例并导出
|
||||
export default new Tool()
|
||||
|
||||
Reference in New Issue
Block a user