uni-events-helper-wx
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

578 lines
20 KiB

3 years ago
  1. import test from './test.js'
  2. /**
  3. * 如果value小于min取min如果value大于max取max
  4. */
  5. function range(min = 0, max = 0, value = 0) {
  6. return Math.max(min, Math.min(max, Number(value)))
  7. }
  8. /**
  9. * 用于获取用户传递值的px值
  10. * 如果用户传递了"xxpx"或者"xxrpx"取出其数值部分如果是"xxxrpx"还需要用过uni.upx2px进行转换
  11. */
  12. function getPx(value, unit = false) {
  13. if (test.number(value)) {
  14. return unit ? `${value}px` : value
  15. }
  16. // 如果带有rpx,先取出其数值部分,再转为px值
  17. if (/(rpx|upx)$/.test(value)) {
  18. return unit ? `${uni.upx2px(parseInt(value))}px` : uni.upx2px(parseInt(value))
  19. }
  20. return unit ? `${parseInt(value)}px` : parseInt(value)
  21. }
  22. /**
  23. * 进行延时以达到可以简写代码的目的比如
  24. * await uni.$u.sleep(20)将会阻塞20ms
  25. */
  26. function sleep(value = 30) {
  27. return new Promise((resolve) => {
  28. setTimeout(() => {
  29. resolve()
  30. }, value)
  31. })
  32. }
  33. function os() {
  34. return uni.getSystemInfoSync().platform.toLowerCase()
  35. }
  36. function sys() {
  37. return uni.getSystemInfoSync()
  38. }
  39. /**
  40. * 取一个区间数
  41. * @param {Number} min 最小值
  42. * @param {Number} max 最大值
  43. */
  44. function random(min, max) {
  45. if (min >= 0 && max > 0 && max >= min) {
  46. const gab = max - min + 1
  47. return Math.floor(Math.random() * gab + min)
  48. }
  49. return 0
  50. }
  51. /**
  52. * 本算法来源于简书开源代码详见https://www.jianshu.com/p/fdbf293d0a85
  53. * 全局唯一标识符uuidGlobally Unique Identifier,也称作 uuid(Universally Unique IDentifier)
  54. * 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题
  55. * 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱
  56. * v-for的时候,推荐使用后端返回的id而不是循环的index
  57. * @param {Number} len uuid的长度
  58. * @param {Boolean} firstU 将返回的首字母置为"u"
  59. * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
  60. */
  61. function guid(len = 32, firstU = true, radix = null) {
  62. const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
  63. const uuid = []
  64. radix = radix || chars.length
  65. if (len) {
  66. // 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
  67. for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
  68. } else {
  69. let r
  70. // rfc4122标准要求返回的uuid中,某些位为固定的字符
  71. uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
  72. uuid[14] = '4'
  73. for (let i = 0; i < 36; i++) {
  74. if (!uuid[i]) {
  75. r = 0 | Math.random() * 16
  76. uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
  77. }
  78. }
  79. }
  80. // 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
  81. if (firstU) {
  82. uuid.shift()
  83. return `u${uuid.join('')}`
  84. }
  85. return uuid.join('')
  86. }
  87. // 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
  88. // this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
  89. // 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
  90. // 值(默认为undefined),就是查找最顶层的$parent
  91. function $parent(name = undefined) {
  92. let parent = this.$parent
  93. // 通过while历遍,这里主要是为了H5需要多层解析的问题
  94. while (parent) {
  95. // 父组件
  96. if (parent.$options && parent.$options.name !== name) {
  97. // 如果组件的name不相等,继续上一级寻找
  98. parent = parent.$parent
  99. } else {
  100. return parent
  101. }
  102. }
  103. return false
  104. }
  105. /**
  106. * 样式转换
  107. * 对象转字符串或者字符串转对象
  108. * @param {Object | String} 需要转换的目标
  109. * @param {String} 转换的目的object-转为对象string-转为字符串
  110. */
  111. function addStyle(customStyle, target = 'object') {
  112. // 字符串转字符串,对象转对象情形,直接返回
  113. if (test.empty(customStyle) || typeof (customStyle) === 'object' && target === 'object' || target === 'string'
  114. && typeof (customStyle) === 'string') {
  115. return customStyle
  116. }
  117. // 字符串转对象
  118. if (target === 'object') {
  119. // 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
  120. customStyle = trim(customStyle)
  121. // 根据";"将字符串转为数组形式
  122. const styleArray = customStyle.split(';')
  123. const style = {}
  124. // 历遍数组,拼接成对象
  125. for (let i = 0; i < styleArray.length; i++) {
  126. // 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
  127. if (styleArray[i]) {
  128. const item = styleArray[i].split(':')
  129. style[trim(item[0])] = trim(item[1])
  130. }
  131. }
  132. return style
  133. }
  134. // 这里为对象转字符串形式
  135. let string = ''
  136. for (const i in customStyle) {
  137. // 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
  138. const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
  139. string += `${key}:${customStyle[i]};`
  140. }
  141. // 去除两端空格
  142. return trim(string)
  143. }
  144. // 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
  145. function addUnit(value = 'auto', unit = 'px') {
  146. value = String(value)
  147. // 用uView内置验证规则中的number判断是否为数值
  148. return test.number(value) ? `${value}${unit}` : value
  149. }
  150. // 深度克隆
  151. function deepClone(obj) {
  152. // 对常见的“非”值,直接返回原来值
  153. if ([null, undefined, NaN, false].includes(obj)) return obj
  154. if (typeof obj !== 'object' && typeof obj !== 'function') {
  155. // 原始类型直接返回
  156. return obj
  157. }
  158. const o = test.array(obj) ? [] : {}
  159. for (const i in obj) {
  160. if (obj.hasOwnProperty(i)) {
  161. o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
  162. }
  163. }
  164. return o
  165. }
  166. // JS对象深度合并
  167. function deepMerge(target = {}, source = {}) {
  168. target = deepClone(target)
  169. if (typeof target !== 'object' || typeof source !== 'object') return false
  170. for (const prop in source) {
  171. if (!source.hasOwnProperty(prop)) continue
  172. if (prop in target) {
  173. if (typeof target[prop] !== 'object') {
  174. target[prop] = source[prop]
  175. } else if (typeof source[prop] !== 'object') {
  176. target[prop] = source[prop]
  177. } else if (target[prop].concat && source[prop].concat) {
  178. target[prop] = target[prop].concat(source[prop])
  179. } else {
  180. target[prop] = deepMerge(target[prop], source[prop])
  181. }
  182. } else {
  183. target[prop] = source[prop]
  184. }
  185. }
  186. return target
  187. }
  188. function error(err) {
  189. // 开发环境才提示,生产环境不会提示
  190. if (process.env.NODE_ENV === 'development') {
  191. console.error(`uView提示:${err}`)
  192. }
  193. }
  194. // 打乱数组
  195. function randomArray(array = []) {
  196. // 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
  197. return array.sort(() => Math.random() - 0.5)
  198. }
  199. // padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
  200. // 所以这里做一个兼容polyfill的兼容处理
  201. if (!String.prototype.padStart) {
  202. // 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
  203. String.prototype.padStart = function (maxLength, fillString = ' ') {
  204. if (Object.prototype.toString.call(fillString) !== '[object String]') {
  205. throw new TypeError(
  206. 'fillString must be String'
  207. )
  208. }
  209. const str = this
  210. // 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
  211. if (str.length >= maxLength) return String(str)
  212. const fillLength = maxLength - str.length
  213. let times = Math.ceil(fillLength / fillString.length)
  214. while (times >>= 1) {
  215. fillString += fillString
  216. if (times === 1) {
  217. fillString += fillString
  218. }
  219. }
  220. return fillString.slice(0, fillLength) + str
  221. }
  222. }
  223. // 其他更多是格式化有如下:
  224. // yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合
  225. function timeFormat(dateTime = null, fmt = 'yyyy-mm-dd') {
  226. // 如果为null,则格式化当前时间
  227. if (!dateTime) dateTime = Number(new Date())
  228. // 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
  229. if (dateTime.toString().length == 10) dateTime *= 1000
  230. const date = new Date(dateTime)
  231. let ret
  232. const opt = {
  233. 'y+': date.getFullYear().toString(), // 年
  234. 'm+': (date.getMonth() + 1).toString(), // 月
  235. 'd+': date.getDate().toString(), // 日
  236. 'h+': date.getHours().toString(), // 时
  237. 'M+': date.getMinutes().toString(), // 分
  238. 's+': date.getSeconds().toString() // 秒
  239. // 有其他格式化字符需求可以继续添加,必须转化成字符串
  240. }
  241. for (const k in opt) {
  242. ret = new RegExp(`(${k})`).exec(fmt)
  243. if (ret) {
  244. fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, '0')))
  245. }
  246. }
  247. return fmt
  248. }
  249. /**
  250. * 时间戳转为多久之前
  251. * @param String timestamp 时间戳
  252. * @param String | Boolean format 如果为时间格式字符串超出一定时间范围返回固定的时间格式
  253. * 如果为布尔值false无论什么时间都返回多久以前的格式
  254. */
  255. function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
  256. if (timestamp == null) timestamp = Number(new Date())
  257. timestamp = parseInt(timestamp)
  258. // 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
  259. if (timestamp.toString().length == 10) timestamp *= 1000
  260. let timer = (new Date()).getTime() - timestamp
  261. timer = parseInt(timer / 1000)
  262. // 如果小于5分钟,则返回"刚刚",其他以此类推
  263. let tips = ''
  264. switch (true) {
  265. case timer < 300:
  266. tips = '刚刚'
  267. break
  268. case timer >= 300 && timer < 3600:
  269. tips = `${parseInt(timer / 60)}分钟前`
  270. break
  271. case timer >= 3600 && timer < 86400:
  272. tips = `${parseInt(timer / 3600)}小时前`
  273. break
  274. case timer >= 86400 && timer < 2592000:
  275. tips = `${parseInt(timer / 86400)}天前`
  276. break
  277. default:
  278. // 如果format为false,则无论什么时间戳,都显示xx之前
  279. if (format === false) {
  280. if (timer >= 2592000 && timer < 365 * 86400) {
  281. tips = `${parseInt(timer / (86400 * 30))}个月前`
  282. } else {
  283. tips = `${parseInt(timer / (86400 * 365))}年前`
  284. }
  285. } else {
  286. tips = timeFormat(timestamp, format)
  287. }
  288. }
  289. return tips
  290. }
  291. /**
  292. * 去除空格
  293. */
  294. function trim(str, pos = 'both') {
  295. str = String(str)
  296. if (pos == 'both') {
  297. return str.replace(/^\s+|\s+$/g, '')
  298. } if (pos == 'left') {
  299. return str.replace(/^\s*/, '')
  300. } if (pos == 'right') {
  301. return str.replace(/(\s*$)/g, '')
  302. } if (pos == 'all') {
  303. return str.replace(/\s+/g, '')
  304. }
  305. return str
  306. }
  307. /**
  308. * 对象转url参数
  309. * @param {*} data,对象
  310. * @param {*} isPrefix,是否自动加上"?"
  311. */
  312. function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
  313. const prefix = isPrefix ? '?' : ''
  314. const _result = []
  315. if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
  316. for (const key in data) {
  317. const value = data[key]
  318. // 去掉为空的参数
  319. if (['', undefined, null].indexOf(value) >= 0) {
  320. continue
  321. }
  322. // 如果值为数组,另行处理
  323. if (value.constructor === Array) {
  324. // e.g. {ids: [1, 2, 3]}
  325. switch (arrayFormat) {
  326. case 'indices':
  327. // 结果: ids[0]=1&ids[1]=2&ids[2]=3
  328. for (let i = 0; i < value.length; i++) {
  329. _result.push(`${key}[${i}]=${value[i]}`)
  330. }
  331. break
  332. case 'brackets':
  333. // 结果: ids[]=1&ids[]=2&ids[]=3
  334. value.forEach((_value) => {
  335. _result.push(`${key}[]=${_value}`)
  336. })
  337. break
  338. case 'repeat':
  339. // 结果: ids=1&ids=2&ids=3
  340. value.forEach((_value) => {
  341. _result.push(`${key}=${_value}`)
  342. })
  343. break
  344. case 'comma':
  345. // 结果: ids=1,2,3
  346. let commaStr = ''
  347. value.forEach((_value) => {
  348. commaStr += (commaStr ? ',' : '') + _value
  349. })
  350. _result.push(`${key}=${commaStr}`)
  351. break
  352. default:
  353. value.forEach((_value) => {
  354. _result.push(`${key}[]=${_value}`)
  355. })
  356. }
  357. } else {
  358. _result.push(`${key}=${value}`)
  359. }
  360. }
  361. return _result.length ? prefix + _result.join('&') : ''
  362. }
  363. function toast(title, duration = 2000) {
  364. uni.showToast({
  365. title: String(title),
  366. icon: 'none',
  367. duration
  368. })
  369. }
  370. /**
  371. * 根据主题type值,获取对应的图标
  372. * @param String type 主题名称,primary|info|error|warning|success
  373. * @param String fill 是否使用fill填充实体的图标
  374. */
  375. function type2icon(type = 'success', fill = false) {
  376. // 如果非预置值,默认为success
  377. if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
  378. let iconName = ''
  379. // 目前(2019-12-12),info和primary使用同一个图标
  380. switch (type) {
  381. case 'primary':
  382. iconName = 'info-circle'
  383. break
  384. case 'info':
  385. iconName = 'info-circle'
  386. break
  387. case 'error':
  388. iconName = 'close-circle'
  389. break
  390. case 'warning':
  391. iconName = 'error-circle'
  392. break
  393. case 'success':
  394. iconName = 'checkmark-circle'
  395. break
  396. default:
  397. iconName = 'checkmark-circle'
  398. }
  399. // 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
  400. if (fill) iconName += '-fill'
  401. return iconName
  402. }
  403. /*
  404. * 参数说明
  405. * number要格式化的数字
  406. * decimals保留几位小数
  407. * decimalPoint小数点符号
  408. * thousandsSeparator千分位符号
  409. * */
  410. function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
  411. number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
  412. const n = !isFinite(+number) ? 0 : +number
  413. const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
  414. const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
  415. const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
  416. let s = ''
  417. const toFixedFix = function (n, prec) {
  418. const k = 10 ** prec
  419. return `${Math.ceil(n * k) / k}`
  420. }
  421. s = (prec ? toFixedFix(n, prec) : `${Math.round(n)}`).split('.')
  422. const re = /(-?\d+)(\d{3})/
  423. while (re.test(s[0])) {
  424. s[0] = s[0].replace(re, `$1${sep}$2`)
  425. }
  426. if ((s[1] || '').length < prec) {
  427. s[1] = s[1] || ''
  428. s[1] += new Array(prec - s[1].length + 1).join('0')
  429. }
  430. return s.join(dec)
  431. }
  432. // 获取duration值,如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
  433. // 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
  434. function getDuration(value, unit = true) {
  435. const valueNum = parseInt(value)
  436. if (unit) {
  437. if (/s$/.test(value)) return value
  438. return value > 30 ? `${value}ms` : `${value}s`
  439. }
  440. if (/ms$/.test(value)) return valueNum
  441. if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
  442. return valueNum
  443. }
  444. // 日期的月或日补零操作
  445. function padZero(value) {
  446. return `00${value}`.slice(-2)
  447. }
  448. // 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法
  449. function formValidate(instance, event) {
  450. const formItem = uni.$u.$parent.call(instance, 'u-form-item')
  451. const form = uni.$u.$parent.call(instance, 'u-form')
  452. // 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-form等,就执行form的validate方法
  453. // 同时将form-item的pros传递给form,让其进行精确对象验证
  454. if (formItem && form) {
  455. form.validateField(formItem.prop, () => { }, event)
  456. }
  457. }
  458. // 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
  459. function getProperty(obj, key) {
  460. if (!obj) {
  461. return
  462. }
  463. if (typeof key !== 'string' || key === '') {
  464. return ''
  465. } if (key.indexOf('.') !== -1) {
  466. const keys = key.split('.')
  467. let firstObj = obj[keys[0]] || {}
  468. for (let i = 1; i < keys.length; i++) {
  469. if (firstObj) {
  470. firstObj = firstObj[keys[i]]
  471. }
  472. }
  473. return firstObj
  474. }
  475. return obj[key]
  476. }
  477. // 设置对象的属性值,如果'a.b.c'的形式进行设置
  478. function setProperty(obj, key, value) {
  479. if (!obj) {
  480. return
  481. }
  482. // 递归赋值
  483. const inFn = function (_obj, keys, v) {
  484. // 最后一个属性key
  485. if (keys.length === 1) {
  486. _obj[keys[0]] = v
  487. return
  488. }
  489. // 0~length-1个key
  490. while (keys.length > 1) {
  491. const k = keys[0]
  492. if (!_obj[k] || (typeof _obj[k] !== 'object')) {
  493. _obj[k] = {}
  494. }
  495. const key = keys.shift()
  496. // 自调用判断是否存在属性,不存在则自动创建对象
  497. inFn(_obj[k], keys, v)
  498. }
  499. }
  500. if (typeof key !== 'string' || key === '') {
  501. } else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
  502. const keys = key.split('.')
  503. inFn(obj, keys, value)
  504. } else {
  505. obj[key] = value
  506. }
  507. }
  508. // 获取当前页面路径
  509. function page() {
  510. const pages = getCurrentPages()
  511. return `/${getCurrentPages()[pages.length - 1].route}`
  512. }
  513. export default {
  514. range,
  515. getPx,
  516. sleep,
  517. os,
  518. sys,
  519. random,
  520. guid,
  521. $parent,
  522. addStyle,
  523. addUnit,
  524. deepClone,
  525. deepMerge,
  526. error,
  527. randomArray,
  528. timeFormat,
  529. timeFrom,
  530. trim,
  531. queryParams,
  532. toast,
  533. type2icon,
  534. priceFormat,
  535. getDuration,
  536. padZero,
  537. formValidate,
  538. getProperty,
  539. setProperty,
  540. page
  541. }