muzhiyu-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +202 -0
- package/components/muzhiyu/mu-action-sheet/mu-action-sheet.vue +335 -0
- package/components/muzhiyu/mu-alert/mu-alert.vue +220 -0
- package/components/muzhiyu/mu-back-top/mu-back-top.vue +161 -0
- package/components/muzhiyu/mu-badge/mu-badge.vue +150 -0
- package/components/muzhiyu/mu-barcode/mu-barcode.vue +100 -0
- package/components/muzhiyu/mu-blank/mu-blank.vue +30 -0
- package/components/muzhiyu/mu-bottom-nav/mu-bottom-nav.vue +331 -0
- package/components/muzhiyu/mu-bubble/mu-bubble.vue +316 -0
- package/components/muzhiyu/mu-button/mu-button.vue +431 -0
- package/components/muzhiyu/mu-calendar/mu-calendar.vue +429 -0
- package/components/muzhiyu/mu-car-input/mu-car-input.vue +209 -0
- package/components/muzhiyu/mu-card/mu-card.vue +317 -0
- package/components/muzhiyu/mu-cascader/mu-cascader.vue +336 -0
- package/components/muzhiyu/mu-cell/mu-cell.vue +368 -0
- package/components/muzhiyu/mu-checkbox/mu-checkbox.vue +116 -0
- package/components/muzhiyu/mu-checkbox-group/mu-checkbox-group.vue +40 -0
- package/components/muzhiyu/mu-code-input/mu-code-input.vue +264 -0
- package/components/muzhiyu/mu-collapse/mu-collapse.vue +316 -0
- package/components/muzhiyu/mu-color-picker/mu-color-picker.vue +466 -0
- package/components/muzhiyu/mu-config/index.js +34 -0
- package/components/muzhiyu/mu-config/mu-config.vue +54 -0
- package/components/muzhiyu/mu-countdown/mu-countdown.vue +338 -0
- package/components/muzhiyu/mu-cropper/mu-cropper.vue +415 -0
- package/components/muzhiyu/mu-date-picker/mu-date-picker.vue +290 -0
- package/components/muzhiyu/mu-datetime/mu-datetime.vue +262 -0
- package/components/muzhiyu/mu-divider/mu-divider.vue +229 -0
- package/components/muzhiyu/mu-drawer/mu-drawer.vue +172 -0
- package/components/muzhiyu/mu-dropdown/mu-dropdown.vue +215 -0
- package/components/muzhiyu/mu-dropdown-top/mu-dropdown-top.vue +243 -0
- package/components/muzhiyu/mu-empty/mu-empty.vue +217 -0
- package/components/muzhiyu/mu-fab/mu-fab.vue +324 -0
- package/components/muzhiyu/mu-footer/mu-footer.vue +21 -0
- package/components/muzhiyu/mu-form/mu-form.vue +170 -0
- package/components/muzhiyu/mu-grid/mu-grid.vue +95 -0
- package/components/muzhiyu/mu-grid-item/mu-grid-item.vue +247 -0
- package/components/muzhiyu/mu-html/mu-html.vue +145 -0
- package/components/muzhiyu/mu-icon/mu-icon.vue +822 -0
- package/components/muzhiyu/mu-image-grid/mu-image-grid.vue +225 -0
- package/components/muzhiyu/mu-image-preview/mu-image-preview.vue +286 -0
- package/components/muzhiyu/mu-index-list/mu-index-list.vue +381 -0
- package/components/muzhiyu/mu-input/mu-input.vue +570 -0
- package/components/muzhiyu/mu-input-number/mu-input-number.vue +193 -0
- package/components/muzhiyu/mu-keyboard/mu-keyboard.vue +415 -0
- package/components/muzhiyu/mu-list/mu-list.vue +89 -0
- package/components/muzhiyu/mu-loading/mu-loading.vue +124 -0
- package/components/muzhiyu/mu-loading-page/mu-loading-page.vue +529 -0
- package/components/muzhiyu/mu-loadmore/mu-loadmore.vue +26 -0
- package/components/muzhiyu/mu-modal/mu-modal.vue +298 -0
- package/components/muzhiyu/mu-navbar/mu-navbar.vue +223 -0
- package/components/muzhiyu/mu-nomore/mu-nomore.vue +107 -0
- package/components/muzhiyu/mu-notice-bar/mu-notice-bar.vue +284 -0
- package/components/muzhiyu/mu-notify/mu-notify.vue +202 -0
- package/components/muzhiyu/mu-overlay/mu-overlay.vue +94 -0
- package/components/muzhiyu/mu-pagination/mu-pagination.vue +330 -0
- package/components/muzhiyu/mu-password-input/mu-password-input.vue +119 -0
- package/components/muzhiyu/mu-picker/mu-picker.vue +297 -0
- package/components/muzhiyu/mu-popup/mu-popup.vue +297 -0
- package/components/muzhiyu/mu-progress/mu-progress.vue +156 -0
- package/components/muzhiyu/mu-pull-refresh/mu-pull-refresh.vue +54 -0
- package/components/muzhiyu/mu-qrcode/mu-qrcode.vue +340 -0
- package/components/muzhiyu/mu-qrcode/qrcode.js +2237 -0
- package/components/muzhiyu/mu-radio/mu-radio.vue +125 -0
- package/components/muzhiyu/mu-radio-group/mu-radio-group.vue +37 -0
- package/components/muzhiyu/mu-rate/mu-rate.vue +103 -0
- package/components/muzhiyu/mu-search/mu-search.vue +320 -0
- package/components/muzhiyu/mu-section/mu-section.vue +206 -0
- package/components/muzhiyu/mu-select/mu-select.vue +440 -0
- package/components/muzhiyu/mu-share-sheet/mu-share-sheet.vue +269 -0
- package/components/muzhiyu/mu-sidebar/mu-sidebar.vue +304 -0
- package/components/muzhiyu/mu-signature/mu-signature.vue +449 -0
- package/components/muzhiyu/mu-skeleton/mu-skeleton.vue +225 -0
- package/components/muzhiyu/mu-slider/mu-slider.vue +228 -0
- package/components/muzhiyu/mu-space/mu-space.vue +84 -0
- package/components/muzhiyu/mu-steps/mu-steps.vue +311 -0
- package/components/muzhiyu/mu-sticky/mu-sticky.vue +57 -0
- package/components/muzhiyu/mu-swipe-cell/mu-swipe-cell.vue +281 -0
- package/components/muzhiyu/mu-swiper/mu-swiper.vue +107 -0
- package/components/muzhiyu/mu-switch/mu-switch.vue +99 -0
- package/components/muzhiyu/mu-tabbar/mu-tabbar.vue +382 -0
- package/components/muzhiyu/mu-table/mu-table.vue +420 -0
- package/components/muzhiyu/mu-tabs/mu-tabs.vue +339 -0
- package/components/muzhiyu/mu-tag/mu-tag.vue +65 -0
- package/components/muzhiyu/mu-text-ellipsis/mu-text-ellipsis.vue +54 -0
- package/components/muzhiyu/mu-timeline/mu-timeline.vue +166 -0
- package/components/muzhiyu/mu-tips/mu-tips.vue +36 -0
- package/components/muzhiyu/mu-toast/mu-toast.vue +151 -0
- package/components/muzhiyu/mu-transfer/mu-transfer.vue +114 -0
- package/components/muzhiyu/mu-tree/mu-tree.vue +263 -0
- package/components/muzhiyu/mu-upload/mu-upload.vue +415 -0
- package/components/muzhiyu/mu-waterfall/mu-waterfall.vue +321 -0
- package/components/muzhiyu/mu-watermark/mu-watermark.vue +60 -0
- package/index.js +83 -0
- package/package.json +38 -0
- package/utils/index.js +600 -0
- package/utils/request.js +265 -0
package/utils/index.js
ADDED
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MuZhiYu UI 全局核心工具函数库 (Unified Utility Collection)
|
|
3
|
+
* 涵盖网络请求、表单正则校验、剪贴板读写、颜色 HEX/RGB 计算、字符串/日期/金额格式化、深拷贝与防抖节流函数
|
|
4
|
+
* @author muzhiyu
|
|
5
|
+
* @version 2.0.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import http from './request.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 常用基础工具集合 (Utils)
|
|
12
|
+
*/
|
|
13
|
+
export const util = {
|
|
14
|
+
/**
|
|
15
|
+
* 去除字符串首尾空格
|
|
16
|
+
* @param {string} value - 待处理的字符串
|
|
17
|
+
* @returns {string} 处理后的字符串
|
|
18
|
+
*/
|
|
19
|
+
trim(value = '') {
|
|
20
|
+
return String(value).replace(/(^\s*)|(\s*$)/g, '')
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 全局替换字符串中的匹配项
|
|
25
|
+
* @param {string} text - 目标文本
|
|
26
|
+
* @param {string} search - 需要被替换的查找内容
|
|
27
|
+
* @param {string} replacement - 替换后的新文本
|
|
28
|
+
* @returns {string} 替换完毕的文本
|
|
29
|
+
*/
|
|
30
|
+
replaceAll(text = '', search, replacement) {
|
|
31
|
+
return String(text).replace(new RegExp(search, 'gm'), replacement)
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 手机号码中间 4 位脱敏显示 (例如: 138****5678)
|
|
36
|
+
* @param {string|number} num - 手机号码
|
|
37
|
+
* @returns {string} 脱敏后的手机号
|
|
38
|
+
*/
|
|
39
|
+
maskPhone(num = '') {
|
|
40
|
+
const str = String(num)
|
|
41
|
+
return str.length === 11 ? str.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2') : str
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 金额千分位格式化并保留两位小数 (例如: 1234567.89 -> 1,234,567.89)
|
|
46
|
+
* @param {number|string} money - 金额数字
|
|
47
|
+
* @returns {string} 格式化后的千分位金额字符串
|
|
48
|
+
*/
|
|
49
|
+
formatMoney(money) {
|
|
50
|
+
const n = parseFloat(money || 0).toFixed(2)
|
|
51
|
+
return n.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 日期时间格式化
|
|
56
|
+
* @param {string} [format='YYYY-MM-DD HH:mm:ss'] - 目标格式 (支持 YYYY, MM, DD, HH, mm, ss)
|
|
57
|
+
* @param {Date|string|number} date - Date 对象、时间戳或日期字符串
|
|
58
|
+
* @returns {string} 格式化后的日期字符串
|
|
59
|
+
*/
|
|
60
|
+
formatDate(format = 'YYYY-MM-DD HH:mm:ss', date) {
|
|
61
|
+
if (!date) return ''
|
|
62
|
+
const d = new Date(date)
|
|
63
|
+
if (isNaN(d.getTime())) return ''
|
|
64
|
+
const pad = (n) => (n < 10 ? '0' + n : '' + n)
|
|
65
|
+
const map = {
|
|
66
|
+
YYYY: d.getFullYear(),
|
|
67
|
+
MM: pad(d.getMonth() + 1),
|
|
68
|
+
DD: pad(d.getDate()),
|
|
69
|
+
HH: pad(d.getHours()),
|
|
70
|
+
mm: pad(d.getMinutes()),
|
|
71
|
+
ss: pad(d.getSeconds())
|
|
72
|
+
}
|
|
73
|
+
let result = format
|
|
74
|
+
for (const [key, val] of Object.entries(map)) {
|
|
75
|
+
result = result.replace(key, val)
|
|
76
|
+
}
|
|
77
|
+
return result
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 防抖函数 (连续触发只在停止触发 N 毫秒后执行一次)
|
|
82
|
+
* @param {Function} fn - 需要防抖的目标函数
|
|
83
|
+
* @param {number} [delay=300] - 延迟时间 (毫秒)
|
|
84
|
+
* @returns {Function} 包裹后的防抖函数
|
|
85
|
+
*/
|
|
86
|
+
debounce(fn, delay = 300) {
|
|
87
|
+
let timer = null
|
|
88
|
+
return function(...args) {
|
|
89
|
+
if (timer) clearTimeout(timer)
|
|
90
|
+
timer = setTimeout(() => {
|
|
91
|
+
fn.apply(this, args)
|
|
92
|
+
}, delay)
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 节流函数 (连续触发在指定时间间隔内只执行一次)
|
|
98
|
+
* @param {Function} fn - 需要节流的目标函数
|
|
99
|
+
* @param {number} [interval=300] - 触发间隔 (毫秒)
|
|
100
|
+
* @returns {Function} 包裹后的节流函数
|
|
101
|
+
*/
|
|
102
|
+
throttle(fn, interval = 300) {
|
|
103
|
+
let last = 0
|
|
104
|
+
return function(...args) {
|
|
105
|
+
const now = Date.now()
|
|
106
|
+
if (now - last >= interval) {
|
|
107
|
+
last = now
|
|
108
|
+
fn.apply(this, args)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 对象与数组深拷贝 (递归防止引用污染)
|
|
115
|
+
* @param {*} obj - 待深拷贝的目标对象或数组
|
|
116
|
+
* @returns {*} 拷贝后的全新对象/数据
|
|
117
|
+
*/
|
|
118
|
+
deepClone(obj) {
|
|
119
|
+
if (obj === null || typeof obj !== 'object') return obj
|
|
120
|
+
if (obj instanceof Date) return new Date(obj)
|
|
121
|
+
if (obj instanceof Array) return obj.map(item => util.deepClone(item))
|
|
122
|
+
const cloned = {}
|
|
123
|
+
for (const key in obj) {
|
|
124
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
125
|
+
cloned[key] = util.deepClone(obj[key])
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return cloned
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 生成带前缀的唯一随机字符串 ID
|
|
133
|
+
* @param {string} [prefix='mu'] - ID 前缀
|
|
134
|
+
* @returns {string} 唯一 ID 字符串
|
|
135
|
+
*/
|
|
136
|
+
uniqueId(prefix = 'mu') {
|
|
137
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 判断当前运行环境是否为 Android (含鸿蒙 HarmonyOS)
|
|
142
|
+
* @returns {boolean} 是否为 Android 或 鸿蒙系统
|
|
143
|
+
*/
|
|
144
|
+
isAndroid() {
|
|
145
|
+
const sys = uni.getDeviceInfo ? uni.getDeviceInfo() : uni.getSystemInfoSync()
|
|
146
|
+
const platform = (sys.platform || '').toLowerCase()
|
|
147
|
+
return platform === 'android' || platform.includes('harmony') || platform === 'ohos'
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 判断当前运行环境是否为 HarmonyOS 鸿蒙系统
|
|
152
|
+
* @returns {boolean} 是否为 鸿蒙系统
|
|
153
|
+
*/
|
|
154
|
+
isHarmonyOS() {
|
|
155
|
+
const sys = uni.getDeviceInfo ? uni.getDeviceInfo() : uni.getSystemInfoSync()
|
|
156
|
+
const platform = (sys.platform || '').toLowerCase()
|
|
157
|
+
return platform.includes('harmony') || platform === 'ohos'
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 判断当前运行环境是否为 iOS
|
|
162
|
+
* @returns {boolean} 是否为 iOS 系统
|
|
163
|
+
*/
|
|
164
|
+
isIOS() {
|
|
165
|
+
const sys = uni.getDeviceInfo ? uni.getDeviceInfo() : uni.getSystemInfoSync()
|
|
166
|
+
const platform = (sys.platform || '').toLowerCase()
|
|
167
|
+
return platform === 'ios' || platform === 'mac'
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* JS 命令式触发自定义 `<mu-toast>` 组件 (支持传参开启或传 false 关闭)
|
|
172
|
+
* @param {string|boolean|Object} options - 提示文案、选项对象或 boolean (false 代表关闭)
|
|
173
|
+
* @param {string} [type='default'] - 图标类型: 'default' | 'success' | 'error' | 'warning' | 'loading'
|
|
174
|
+
* @param {number} [duration=2000] - 持续时间 (毫秒)
|
|
175
|
+
*/
|
|
176
|
+
toast(options, type = 'default', duration = 2000) {
|
|
177
|
+
if (options === false) {
|
|
178
|
+
uni.$emit('mu-toast-hide')
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
let payload = {}
|
|
182
|
+
if (typeof options === 'object' && options !== null) {
|
|
183
|
+
let iconType = options.type || type
|
|
184
|
+
if (options.icon) {
|
|
185
|
+
iconType = (options.icon === 'success') ? 'success' : ((options.icon === 'error' || options.icon === 'fail') ? 'error' : ((options.icon === 'loading') ? 'loading' : 'default'))
|
|
186
|
+
}
|
|
187
|
+
payload = {
|
|
188
|
+
title: options.title || options.text || '',
|
|
189
|
+
type: iconType,
|
|
190
|
+
position: options.position || 'center',
|
|
191
|
+
duration: options.duration || duration
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
payload = {
|
|
195
|
+
title: String(options || '提示信息'),
|
|
196
|
+
type,
|
|
197
|
+
position: 'center',
|
|
198
|
+
duration
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 发射全局事件触发页面挂载的 <mu-toast> 组件
|
|
203
|
+
uni.$emit('mu-toast-show', payload)
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* JS 命令式触发自定义 `<mu-notify>` 消息通知组件 (支持传参开启或传 false 关闭)
|
|
208
|
+
* @param {string|boolean|Object} options - 消息文案、选项对象或 boolean (false 代表关闭)
|
|
209
|
+
* @param {string} [type='primary'] - 通知类型: 'primary' | 'success' | 'warning' | 'error'
|
|
210
|
+
* @param {number} [duration=3000] - 持续时间 (毫秒)
|
|
211
|
+
*/
|
|
212
|
+
notify(options, type = 'primary', duration = 3000) {
|
|
213
|
+
if (options === false) {
|
|
214
|
+
uni.$emit('mu-notify-close')
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
let payload = {}
|
|
218
|
+
if (typeof options === 'object' && options !== null) {
|
|
219
|
+
payload = {
|
|
220
|
+
message: options.message || options.title || '',
|
|
221
|
+
type: options.type || type,
|
|
222
|
+
position: options.position || 'top',
|
|
223
|
+
duration: options.duration !== undefined ? options.duration : duration,
|
|
224
|
+
background: options.background || ''
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
payload = {
|
|
228
|
+
message: String(options || ''),
|
|
229
|
+
type,
|
|
230
|
+
position: 'top',
|
|
231
|
+
duration
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
uni.$emit('mu-notify-show', payload)
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* JS 命令式触发全屏加载页 `<mu-loading-page>` (支持传参开启或传 false 关闭)
|
|
239
|
+
* @param {string|boolean|Object} [options=true] - 加载配置对象、提示文案或 boolean (false 代表关闭)
|
|
240
|
+
*/
|
|
241
|
+
loadingPage(options = true) {
|
|
242
|
+
if (options === false) {
|
|
243
|
+
uni.$emit('mu-loading-page-hide')
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
let payload = {}
|
|
247
|
+
if (typeof options === 'object' && options !== null) {
|
|
248
|
+
payload = {
|
|
249
|
+
text: options.text || options.title || '页面加载中...',
|
|
250
|
+
subText: options.subText || '',
|
|
251
|
+
type: options.type || 'logo',
|
|
252
|
+
layout: options.layout || 'card',
|
|
253
|
+
image: options.image || '',
|
|
254
|
+
imageAnimation: options.imageAnimation || 'pulse',
|
|
255
|
+
color: options.color || '#171717'
|
|
256
|
+
}
|
|
257
|
+
} else if (typeof options === 'string') {
|
|
258
|
+
payload = { text: options, type: 'logo', layout: 'card' }
|
|
259
|
+
} else {
|
|
260
|
+
payload = { text: '页面加载中...', type: 'logo', layout: 'card' }
|
|
261
|
+
}
|
|
262
|
+
uni.$emit('mu-loading-page-show', payload)
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 开启全屏遮罩 Loading
|
|
267
|
+
* @param {string} [title='请稍候...'] - 加载提示语
|
|
268
|
+
*/
|
|
269
|
+
showLoading(title = '请稍候...') {
|
|
270
|
+
uni.showLoading({ title, mask: true })
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 关闭全屏遮罩 Loading
|
|
275
|
+
*/
|
|
276
|
+
hideLoading() {
|
|
277
|
+
uni.hideLoading()
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* 常见正则校验函数对象 (Validator)
|
|
283
|
+
*/
|
|
284
|
+
export const validator = {
|
|
285
|
+
/**
|
|
286
|
+
* 判断值是否为空或未定义 (null / undefined / '')
|
|
287
|
+
* @param {*} value - 待校验值
|
|
288
|
+
* @returns {boolean} 是否为空
|
|
289
|
+
*/
|
|
290
|
+
isNullOrEmpty(value) {
|
|
291
|
+
return value === null || value === '' || value === undefined
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* 校验中国大陆 11 位手机号码格式
|
|
296
|
+
* @param {string|number} value - 手机号码
|
|
297
|
+
* @returns {boolean} 是否合规
|
|
298
|
+
*/
|
|
299
|
+
isMobile(value) {
|
|
300
|
+
return /^(?:1[3-9]\d)\d{5}(\d{3}|\*{3})$/.test(String(value))
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* 校验电子邮箱地址格式
|
|
305
|
+
* @param {string} value - 邮箱地址
|
|
306
|
+
* @returns {boolean} 是否合规
|
|
307
|
+
*/
|
|
308
|
+
isEmail(value) {
|
|
309
|
+
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(String(value))
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* 校验中国车牌号 (支持传统燃油车牌 7 位与新能源车牌 8 位)
|
|
314
|
+
* @param {string} value - 车牌号码
|
|
315
|
+
* @returns {boolean} 是否合规
|
|
316
|
+
*/
|
|
317
|
+
isCarNo(value) {
|
|
318
|
+
const str = String(value)
|
|
319
|
+
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
|
|
320
|
+
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
|
|
321
|
+
return str.length === 7 ? creg.test(str) : str.length === 8 ? xreg.test(str) : false
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 校验中国大陆 18 位身份证号码 (带校验码加权校验算法)
|
|
326
|
+
* @param {string} value - 身份证号
|
|
327
|
+
* @returns {boolean} 是否合规
|
|
328
|
+
*/
|
|
329
|
+
isIdCard(value) {
|
|
330
|
+
const str = String(value)
|
|
331
|
+
if (str.length === 18) {
|
|
332
|
+
const arr = str.split('')
|
|
333
|
+
const W = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1]
|
|
334
|
+
const V = [1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2]
|
|
335
|
+
if (arr[17].toLowerCase() === 'x') arr[17] = 10
|
|
336
|
+
let sum = 0
|
|
337
|
+
for (let i = 0; i < 17; i++) sum += W[i] * arr[i]
|
|
338
|
+
return arr[17] == V[sum % 11]
|
|
339
|
+
}
|
|
340
|
+
return false
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* 校验金额或合法小数格式
|
|
345
|
+
* @param {string|number} value - 数值
|
|
346
|
+
* @returns {boolean} 是否合规
|
|
347
|
+
*/
|
|
348
|
+
isAmount(value) {
|
|
349
|
+
return /^([0-9]*[.]?[0-9])[0-9]{0,1}$/.test(String(value))
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 校验是否全为纯数字
|
|
354
|
+
* @param {string|number} value - 文本
|
|
355
|
+
* @returns {boolean} 是否为纯数字
|
|
356
|
+
*/
|
|
357
|
+
isNum(value) {
|
|
358
|
+
return /^[0-9]+$/.test(String(value))
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 校验 URL 网址格式
|
|
363
|
+
* @param {string} value - URL 网址
|
|
364
|
+
* @returns {boolean} 是否合法 URL
|
|
365
|
+
*/
|
|
366
|
+
isUrl(value) {
|
|
367
|
+
return /^((https?|ftp|file):\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(:[0-9]{1,5})?(\/.*)?$/.test(String(value))
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* 校验表单对象数据的主入口函数
|
|
373
|
+
* @param {Object} formData - 表单数据键值对 { name: '张三' }
|
|
374
|
+
* @param {Array<Object>} rules - 校验规则配置数组 [{ name: 'name', rule: ['required'], msg: ['请输入姓名'] }]
|
|
375
|
+
* @returns {string} 如果返回空字符串 '' 说明校验通过;若不通过则返回首个错误提示语
|
|
376
|
+
*/
|
|
377
|
+
export function validate(formData, rules) {
|
|
378
|
+
for (const item of rules) {
|
|
379
|
+
const { name, rule = [], msg = [] } = item
|
|
380
|
+
if (!name || !rule.length || !msg.length) continue
|
|
381
|
+
const val = formData[name]
|
|
382
|
+
if (!rule.includes('required') && validator.isNullOrEmpty(val)) continue
|
|
383
|
+
|
|
384
|
+
for (let i = 0; i < rule.length; i++) {
|
|
385
|
+
const ruleName = rule[i]
|
|
386
|
+
const errorMsg = msg[i]
|
|
387
|
+
let hasError = false
|
|
388
|
+
switch (ruleName) {
|
|
389
|
+
case 'required': hasError = validator.isNullOrEmpty(val); break
|
|
390
|
+
case 'isMobile': hasError = !validator.isMobile(val); break
|
|
391
|
+
case 'isEmail': hasError = !validator.isEmail(val); break
|
|
392
|
+
case 'isIdCard': hasError = !validator.isIdCard(val); break
|
|
393
|
+
case 'isCarNo': hasError = !validator.isCarNo(val); break
|
|
394
|
+
case 'isAmount': hasError = !validator.isAmount(val); break
|
|
395
|
+
case 'isNum': hasError = !validator.isNum(val); break
|
|
396
|
+
case 'isUrl': hasError = !validator.isUrl(val); break
|
|
397
|
+
}
|
|
398
|
+
if (hasError) return errorMsg
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return ''
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export const validation = { validate, validator }
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* 剪贴板读写工具对象 (Clipboard)
|
|
408
|
+
*/
|
|
409
|
+
export const clipboard = {
|
|
410
|
+
/**
|
|
411
|
+
* 复制指定文本到系统剪贴板 (兼容 H5 / 小程序 / App)
|
|
412
|
+
* @param {string} data - 需要复制的内容字符串
|
|
413
|
+
* @returns {Promise<boolean>} 复制是否成功
|
|
414
|
+
*/
|
|
415
|
+
copy(data) {
|
|
416
|
+
return new Promise((resolve) => {
|
|
417
|
+
// #ifdef APP-PLUS || MP
|
|
418
|
+
uni.setClipboardData({
|
|
419
|
+
data,
|
|
420
|
+
success() { resolve(true) },
|
|
421
|
+
fail() { resolve(false) }
|
|
422
|
+
})
|
|
423
|
+
// #endif
|
|
424
|
+
|
|
425
|
+
// #ifdef H5
|
|
426
|
+
try {
|
|
427
|
+
const textarea = document.createElement('textarea')
|
|
428
|
+
textarea.value = data
|
|
429
|
+
textarea.style.position = 'fixed'
|
|
430
|
+
textarea.style.left = '-9999px'
|
|
431
|
+
document.body.appendChild(textarea)
|
|
432
|
+
textarea.select()
|
|
433
|
+
const ok = document.execCommand('copy')
|
|
434
|
+
document.body.removeChild(textarea)
|
|
435
|
+
resolve(ok)
|
|
436
|
+
} catch (e) {
|
|
437
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
438
|
+
navigator.clipboard.writeText(data).then(() => resolve(true)).catch(() => resolve(false))
|
|
439
|
+
} else {
|
|
440
|
+
resolve(false)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
// #endif
|
|
444
|
+
})
|
|
445
|
+
},
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* 读取系统剪贴板中的文本内容 (兼容 H5 / 小程序 / App)
|
|
449
|
+
* @returns {Promise<string>} 剪贴板中的文本内容
|
|
450
|
+
*/
|
|
451
|
+
read() {
|
|
452
|
+
return new Promise((resolve) => {
|
|
453
|
+
// #ifdef APP-PLUS || MP
|
|
454
|
+
uni.getClipboardData({
|
|
455
|
+
success(res) { resolve(res.data || '') },
|
|
456
|
+
fail() { resolve('') }
|
|
457
|
+
})
|
|
458
|
+
// #endif
|
|
459
|
+
|
|
460
|
+
// #ifdef H5
|
|
461
|
+
if (navigator.clipboard && navigator.clipboard.readText) {
|
|
462
|
+
navigator.clipboard.readText().then(text => resolve(text)).catch(() => resolve(''))
|
|
463
|
+
} else {
|
|
464
|
+
resolve('')
|
|
465
|
+
}
|
|
466
|
+
// #endif
|
|
467
|
+
})
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* 颜色进制转换与混合计算工具对象 (Color)
|
|
473
|
+
*/
|
|
474
|
+
export const color = {
|
|
475
|
+
/**
|
|
476
|
+
* RGB 颜色分量转 Hex 十六进制颜色值 (例如: 99, 102, 241 -> #6366F1)
|
|
477
|
+
* @param {number} r - 红色分量 (0~255)
|
|
478
|
+
* @param {number} g - 绿色分量 (0~255)
|
|
479
|
+
* @param {number} b - 蓝色分量 (0~255)
|
|
480
|
+
* @returns {string} Hex 格式颜色字符串
|
|
481
|
+
*/
|
|
482
|
+
rgbToHex(r, g, b) {
|
|
483
|
+
const toHex = (n) => {
|
|
484
|
+
n = Math.max(0, Math.min(255, parseInt(n, 10)))
|
|
485
|
+
return '0123456789ABCDEF'.charAt((n - n % 16) / 16) + '0123456789ABCDEF'.charAt(n % 16)
|
|
486
|
+
}
|
|
487
|
+
return '#' + toHex(r) + toHex(g) + toHex(b)
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Hex 十六进制颜色转 RGB 颜色对象 (例如: #6366F1 -> { r: 99, g: 102, b: 241 })
|
|
492
|
+
* @param {string} hex - Hex 十六进制颜色值
|
|
493
|
+
* @returns {Object|null} { r, g, b } 分量对象
|
|
494
|
+
*/
|
|
495
|
+
hexToRgb(hex) {
|
|
496
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
497
|
+
return result ? {
|
|
498
|
+
r: parseInt(result[1], 16),
|
|
499
|
+
g: parseInt(result[2], 16),
|
|
500
|
+
b: parseInt(result[3], 16)
|
|
501
|
+
} : null
|
|
502
|
+
},
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* 两种颜色按权重比例按混合计算 (常用于渐变色或按压颜色生成)
|
|
506
|
+
* @param {string} color1 - 第一种颜色 (#Hex)
|
|
507
|
+
* @param {string} color2 - 第二种颜色 (#Hex)
|
|
508
|
+
* @param {number} [weight=0.5] - 第一种颜色的占比权重 (0~1)
|
|
509
|
+
* @returns {string} 混合后的 rgb(r, g, b) 颜色值
|
|
510
|
+
*/
|
|
511
|
+
mix(color1, color2, weight = 0.5) {
|
|
512
|
+
const c1 = color.hexToRgb(color1)
|
|
513
|
+
const c2 = color.hexToRgb(color2)
|
|
514
|
+
if (!c1 || !c2) return color1
|
|
515
|
+
const r = Math.round(c1.r * weight + c2.r * (1 - weight))
|
|
516
|
+
const g = Math.round(c1.g * weight + c2.g * (1 - weight))
|
|
517
|
+
const b = Math.round(c1.b * weight + c2.b * (1 - weight))
|
|
518
|
+
return `rgb(${r},${g},${b})`
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// 挂载命令式关闭子函数
|
|
523
|
+
util.toast.hide = () => uni.$emit('mu-toast-hide')
|
|
524
|
+
util.toast.close = () => uni.$emit('mu-toast-hide')
|
|
525
|
+
|
|
526
|
+
util.notify.hide = () => uni.$emit('mu-notify-close')
|
|
527
|
+
util.notify.close = () => uni.$emit('mu-notify-close')
|
|
528
|
+
|
|
529
|
+
util.loadingPage.hide = () => uni.$emit('mu-loading-page-hide')
|
|
530
|
+
util.loadingPage.close = () => uni.$emit('mu-loading-page-hide')
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* 全局统一导出核心入口对象 (mu)
|
|
534
|
+
*/
|
|
535
|
+
export const mu = {
|
|
536
|
+
http,
|
|
537
|
+
util,
|
|
538
|
+
validation,
|
|
539
|
+
clipboard,
|
|
540
|
+
color,
|
|
541
|
+
|
|
542
|
+
// 常用高频函数快捷别名解构
|
|
543
|
+
toast: util.toast,
|
|
544
|
+
notify: util.notify,
|
|
545
|
+
loadingPage: util.loadingPage,
|
|
546
|
+
showLoading: util.showLoading,
|
|
547
|
+
hideLoading: util.hideLoading,
|
|
548
|
+
formatDate: util.formatDate,
|
|
549
|
+
formatMoney: util.formatMoney,
|
|
550
|
+
maskPhone: util.maskPhone,
|
|
551
|
+
debounce: util.debounce,
|
|
552
|
+
throttle: util.throttle,
|
|
553
|
+
deepClone: util.deepClone,
|
|
554
|
+
copy: clipboard.copy,
|
|
555
|
+
read: clipboard.read,
|
|
556
|
+
validate,
|
|
557
|
+
validator,
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Vue 3 插件式安装主入口 (支持 app.use(MuZhiYuUI, options))
|
|
561
|
+
* @param {Object} app - Vue app 实例
|
|
562
|
+
* @param {Object} [options={}] - 插件选项 (含 baseURL, requestInterceptor, responseInterceptor)
|
|
563
|
+
*/
|
|
564
|
+
install(app, options = {}) {
|
|
565
|
+
if (options.baseURL) {
|
|
566
|
+
http.setBaseURL(options.baseURL)
|
|
567
|
+
}
|
|
568
|
+
if (typeof options.requestInterceptor === 'function') {
|
|
569
|
+
http.interceptors.request.use(options.requestInterceptor)
|
|
570
|
+
}
|
|
571
|
+
if (typeof options.responseInterceptor === 'function') {
|
|
572
|
+
http.interceptors.response.use(options.responseInterceptor)
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
app.config.globalProperties.$mu = mu
|
|
576
|
+
if (typeof uni !== 'undefined') {
|
|
577
|
+
uni.$mu = mu
|
|
578
|
+
uni.$toast = mu.toast
|
|
579
|
+
uni.showToast = function(opts) {
|
|
580
|
+
mu.toast(opts)
|
|
581
|
+
}
|
|
582
|
+
uni.hideToast = function() {
|
|
583
|
+
uni.$emit('mu-toast-hide')
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
if (typeof uni !== 'undefined') {
|
|
590
|
+
uni.$mu = mu
|
|
591
|
+
uni.$toast = mu.toast
|
|
592
|
+
uni.showToast = function(opts) {
|
|
593
|
+
mu.toast(opts)
|
|
594
|
+
}
|
|
595
|
+
uni.hideToast = function() {
|
|
596
|
+
uni.$emit('mu-toast-hide')
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export default mu
|