70 changed files with 8064 additions and 935 deletions
|
Before Width: | Height: | Size: 790 B After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,13 @@ |
|||
## 1.0.4(2023-07-17) |
|||
1. 优化文档 |
|||
2. 优化其他 |
|||
## 1.0.3(2023-06-19) |
|||
1. 修复nvue模式下不显示的BUG |
|||
## 1.0.2(2023-06-02) |
|||
1. 修复可能存在的BUG |
|||
2. 优化 |
|||
## 1.0.1(2023-05-16) |
|||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用 |
|||
2. 优化部分功能 |
|||
## 1.0.0(2023-05-10) |
|||
uv-parse 富文本解析器 |
|||
@ -0,0 +1,576 @@ |
|||
<template> |
|||
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style"> |
|||
<block v-for="(n, i) in childs" v-bind:key="i"> |
|||
<!-- 图片 --> |
|||
<!-- 占位图 --> |
|||
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" /> |
|||
<!-- 显示图片 --> |
|||
<!-- #ifdef H5 || (APP-PLUS && VUE2) --> |
|||
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 || (APP-PLUS && VUE2) --> |
|||
<!-- 表格中的图片,使用 rich-text 防止大小不正确 --> |
|||
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" /> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 || APP-PLUS --> |
|||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
|||
<!-- #endif --> |
|||
<!-- #ifdef APP-PLUS && VUE3 --> |
|||
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" /> |
|||
<!-- #endif --> |
|||
<!-- 文本 --> |
|||
<!-- #ifdef MP-WEIXIN --> |
|||
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text> |
|||
<!-- #endif --> |
|||
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO --> |
|||
<text v-else-if="n.text" decode>{{n.text}}</text> |
|||
<!-- #endif --> |
|||
<text v-else-if="n.name==='br'">\n</text> |
|||
<!-- 链接 --> |
|||
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap"> |
|||
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" /> |
|||
</view> |
|||
<!-- 视频 --> |
|||
<!-- #ifdef APP-PLUS --> |
|||
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" /> |
|||
<!-- #endif --> |
|||
<!-- #ifndef APP-PLUS --> |
|||
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" /> |
|||
<!-- #endif --> |
|||
<!-- #ifdef H5 || APP-PLUS --> |
|||
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" /> |
|||
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" /> |
|||
<!-- #endif --> |
|||
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) --> |
|||
<!-- 音频 --> |
|||
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" /> |
|||
<!-- #endif --> |
|||
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style"> |
|||
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" /> |
|||
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style"> |
|||
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" /> |
|||
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y"> |
|||
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style"> |
|||
<node :childs="tr.children" :opts="opts" /> |
|||
</view> |
|||
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style"> |
|||
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style"> |
|||
<node :childs="td.children" :opts="opts" /> |
|||
</view> |
|||
</view> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 富文本 --> |
|||
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) --> |
|||
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" /> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) --> |
|||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" /> |
|||
<!-- #endif --> |
|||
<!-- 继续递归 --> |
|||
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style"> |
|||
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" /> |
|||
</view> |
|||
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" /> |
|||
</block> |
|||
</view> |
|||
</template> |
|||
<script module="handler" lang="wxs"> |
|||
// 行内标签列表 |
|||
var inlineTags = { |
|||
abbr: true, |
|||
b: true, |
|||
big: true, |
|||
code: true, |
|||
del: true, |
|||
em: true, |
|||
i: true, |
|||
ins: true, |
|||
label: true, |
|||
q: true, |
|||
small: true, |
|||
span: true, |
|||
strong: true, |
|||
sub: true, |
|||
sup: true |
|||
} |
|||
/** |
|||
* @description 判断是否为行内标签 |
|||
*/ |
|||
module.exports = { |
|||
isInline: function (tagName, style) { |
|||
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1 |
|||
} |
|||
} |
|||
</script> |
|||
<script> |
|||
|
|||
import node from './node' |
|||
export default { |
|||
name: 'node', |
|||
options: { |
|||
// #ifdef MP-WEIXIN |
|||
virtualHost: true, |
|||
// #endif |
|||
// #ifdef MP-TOUTIAO |
|||
addGlobalClass: false |
|||
// #endif |
|||
}, |
|||
data () { |
|||
return { |
|||
ctrl: {}, |
|||
// #ifdef MP-WEIXIN |
|||
isiOS: uni.getSystemInfoSync().system.includes('iOS') |
|||
// #endif |
|||
} |
|||
}, |
|||
props: { |
|||
name: String, |
|||
attrs: { |
|||
type: Object, |
|||
default () { |
|||
return {} |
|||
} |
|||
}, |
|||
childs: Array, |
|||
opts: Array |
|||
}, |
|||
components: { |
|||
|
|||
// #ifndef (H5 || APP-PLUS) && VUE3 |
|||
node |
|||
// #endif |
|||
}, |
|||
mounted () { |
|||
this.$nextTick(() => { |
|||
for (this.root = this.$parent; this.root.$options.name !== 'uv-parse'; this.root = this.root.$parent); |
|||
}) |
|||
// #ifdef H5 || APP-PLUS |
|||
if (this.opts[0]) { |
|||
let i |
|||
for (i = this.childs.length; i--;) { |
|||
if (this.childs[i].name === 'img') break |
|||
} |
|||
if (i !== -1) { |
|||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({ |
|||
top: 500, |
|||
bottom: 500 |
|||
}) |
|||
this.observer.observe('._img', res => { |
|||
if (res.intersectionRatio) { |
|||
this.$set(this.ctrl, 'load', 1) |
|||
this.observer.disconnect() |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
// #endif |
|||
}, |
|||
beforeDestroy () { |
|||
// #ifdef H5 || APP-PLUS |
|||
if (this.observer) { |
|||
this.observer.disconnect() |
|||
} |
|||
// #endif |
|||
}, |
|||
methods:{ |
|||
// #ifdef MP-WEIXIN |
|||
toJSON () { return this }, |
|||
// #endif |
|||
/** |
|||
* @description 播放视频事件 |
|||
* @param {Event} e |
|||
*/ |
|||
play (e) { |
|||
this.root.$emit('play') |
|||
// #ifndef APP-PLUS |
|||
if (this.root.pauseVideo) { |
|||
let flag = false |
|||
const id = e.target.id |
|||
for (let i = this.root._videos.length; i--;) { |
|||
if (this.root._videos[i].id === id) { |
|||
flag = true |
|||
} else { |
|||
this.root._videos[i].pause() // 自动暂停其他视频 |
|||
} |
|||
} |
|||
// 将自己加入列表 |
|||
if (!flag) { |
|||
const ctx = uni.createVideoContext(id |
|||
// #ifndef MP-BAIDU |
|||
, this |
|||
// #endif |
|||
) |
|||
ctx.id = id |
|||
if (this.root.playbackRate) { |
|||
ctx.playbackRate(this.root.playbackRate) |
|||
} |
|||
this.root._videos.push(ctx) |
|||
} |
|||
} |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 图片点击事件 |
|||
* @param {Event} e |
|||
*/ |
|||
imgTap (e) { |
|||
const node = this.childs[e.currentTarget.dataset.i] |
|||
if (node.a) { |
|||
this.linkTap(node.a) |
|||
return |
|||
} |
|||
if (node.attrs.ignore) return |
|||
// #ifdef H5 || APP-PLUS |
|||
node.attrs.src = node.attrs.src || node.attrs['data-src'] |
|||
// #endif |
|||
this.root.$emit('imgtap', node.attrs) |
|||
// 自动预览图片 |
|||
if (this.root.previewImg) { |
|||
uni.previewImage({ |
|||
// #ifdef MP-WEIXIN |
|||
showmenu: this.root.showImgMenu, |
|||
// #endif |
|||
// #ifdef MP-ALIPAY |
|||
enablesavephoto: this.root.showImgMenu, |
|||
enableShowPhotoDownload: this.root.showImgMenu, |
|||
// #endif |
|||
current: parseInt(node.attrs.i), |
|||
urls: this.root.imgList |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* @description 图片长按 |
|||
*/ |
|||
imgLongTap (e) { |
|||
// #ifdef APP-PLUS |
|||
const attrs = this.childs[e.currentTarget.dataset.i].attrs |
|||
if (this.opts[3] && !attrs.ignore) { |
|||
uni.showActionSheet({ |
|||
itemList: ['保存图片'], |
|||
success: () => { |
|||
const save = path => { |
|||
uni.saveImageToPhotosAlbum({ |
|||
filePath: path, |
|||
success () { |
|||
uni.showToast({ |
|||
title: '保存成功' |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
if (this.root.imgList[attrs.i].startsWith('http')) { |
|||
uni.downloadFile({ |
|||
url: this.root.imgList[attrs.i], |
|||
success: res => save(res.tempFilePath) |
|||
}) |
|||
} else { |
|||
save(this.root.imgList[attrs.i]) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 图片加载完成事件 |
|||
* @param {Event} e |
|||
*/ |
|||
imgLoad (e) { |
|||
const i = e.currentTarget.dataset.i |
|||
/* #ifndef H5 || (APP-PLUS && VUE2) */ |
|||
if (!this.childs[i].w) { |
|||
// 设置原宽度 |
|||
this.$set(this.ctrl, i, e.detail.width) |
|||
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) { |
|||
// 加载完毕,取消加载中占位图 |
|||
this.$set(this.ctrl, i, 1) |
|||
} |
|||
this.checkReady() |
|||
}, |
|||
|
|||
/** |
|||
* @description 检查是否所有图片加载完毕 |
|||
*/ |
|||
checkReady () { |
|||
if (this.root && !this.root.lazyLoad) { |
|||
this.root._unloadimgs -= 1 |
|||
if (!this.root._unloadimgs) { |
|||
setTimeout(() => { |
|||
this.root.getRect().then(rect => { |
|||
this.root.$emit('ready', rect) |
|||
}).catch(() => { |
|||
this.root.$emit('ready', {}) |
|||
}) |
|||
}, 350) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* @description 链接点击事件 |
|||
* @param {Event} e |
|||
*/ |
|||
linkTap (e) { |
|||
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {} |
|||
const attrs = node.attrs || e |
|||
const href = attrs.href |
|||
this.root.$emit('linktap', Object.assign({ |
|||
innerText: this.root.getText(node.children || []) // 链接内的文本内容 |
|||
}, attrs)) |
|||
if (href) { |
|||
if (href[0] === '#') { |
|||
// 跳转锚点 |
|||
this.root.navigateTo(href.substring(1)).catch(() => { }) |
|||
} else if (href.split('?')[0].includes('://')) { |
|||
// 复制外部链接 |
|||
if (this.root.copyLink) { |
|||
// #ifdef H5 |
|||
window.open(href) |
|||
// #endif |
|||
// #ifdef MP |
|||
uni.setClipboardData({ |
|||
data: href, |
|||
success: () => |
|||
uni.showToast({ |
|||
title: '链接已复制' |
|||
}) |
|||
}) |
|||
// #endif |
|||
// #ifdef APP-PLUS |
|||
plus.runtime.openWeb(href) |
|||
// #endif |
|||
} |
|||
} else { |
|||
// 跳转页面 |
|||
uni.navigateTo({ |
|||
url: href, |
|||
fail () { |
|||
uni.switchTab({ |
|||
url: href, |
|||
fail () { } |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* @description 错误事件 |
|||
* @param {Event} e |
|||
*/ |
|||
mediaError (e) { |
|||
const i = e.currentTarget.dataset.i |
|||
const node = this.childs[i] |
|||
// 加载其他源 |
|||
if (node.name === 'video' || node.name === 'audio') { |
|||
let index = (this.ctrl[i] || 0) + 1 |
|||
if (index > node.src.length) { |
|||
index = 0 |
|||
} |
|||
if (index < node.src.length) { |
|||
this.$set(this.ctrl, i, index) |
|||
return |
|||
} |
|||
} else if (node.name === 'img') { |
|||
// #ifdef H5 && VUE3 |
|||
if (this.opts[0] && !this.ctrl.load) return |
|||
// #endif |
|||
// 显示错误占位图 |
|||
if (this.opts[2]) { |
|||
this.$set(this.ctrl, i, -1) |
|||
} |
|||
this.checkReady() |
|||
} |
|||
if (this.root) { |
|||
this.root.$emit('error', { |
|||
source: node.name, |
|||
attrs: node.attrs, |
|||
// #ifndef H5 && VUE3 |
|||
errMsg: e.detail.errMsg |
|||
// #endif |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
<style> |
|||
/* a 标签默认效果 */ |
|||
._a { |
|||
padding: 1.5px 0 1.5px 0; |
|||
color: #366092; |
|||
word-break: break-all; |
|||
} |
|||
|
|||
/* a 标签点击态效果 */ |
|||
._hover { |
|||
text-decoration: underline; |
|||
opacity: 0.7; |
|||
} |
|||
|
|||
/* 图片默认效果 */ |
|||
._img { |
|||
max-width: 100%; |
|||
-webkit-touch-callout: none; |
|||
} |
|||
|
|||
/* 内部样式 */ |
|||
|
|||
._block { |
|||
display: block; |
|||
} |
|||
|
|||
._b, |
|||
._strong { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
._code { |
|||
font-family: monospace; |
|||
} |
|||
|
|||
._del { |
|||
text-decoration: line-through; |
|||
} |
|||
|
|||
._em, |
|||
._i { |
|||
font-style: italic; |
|||
} |
|||
|
|||
._h1 { |
|||
font-size: 2em; |
|||
} |
|||
|
|||
._h2 { |
|||
font-size: 1.5em; |
|||
} |
|||
|
|||
._h3 { |
|||
font-size: 1.17em; |
|||
} |
|||
|
|||
._h5 { |
|||
font-size: 0.83em; |
|||
} |
|||
|
|||
._h6 { |
|||
font-size: 0.67em; |
|||
} |
|||
|
|||
._h1, |
|||
._h2, |
|||
._h3, |
|||
._h4, |
|||
._h5, |
|||
._h6 { |
|||
display: block; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
._image { |
|||
height: 1px; |
|||
} |
|||
|
|||
._ins { |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
._li { |
|||
display: list-item; |
|||
} |
|||
|
|||
._ol { |
|||
list-style-type: decimal; |
|||
} |
|||
|
|||
._ol, |
|||
._ul { |
|||
display: block; |
|||
padding-left: 40px; |
|||
margin: 1em 0; |
|||
} |
|||
|
|||
._q::before { |
|||
content: '"'; |
|||
} |
|||
|
|||
._q::after { |
|||
content: '"'; |
|||
} |
|||
|
|||
._sub { |
|||
font-size: smaller; |
|||
vertical-align: sub; |
|||
} |
|||
|
|||
._sup { |
|||
font-size: smaller; |
|||
vertical-align: super; |
|||
} |
|||
|
|||
._thead, |
|||
._tbody, |
|||
._tfoot { |
|||
display: table-row-group; |
|||
} |
|||
|
|||
._tr { |
|||
display: table-row; |
|||
} |
|||
|
|||
._td, |
|||
._th { |
|||
display: table-cell; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
._th { |
|||
font-weight: bold; |
|||
text-align: center; |
|||
} |
|||
|
|||
._ul { |
|||
list-style-type: disc; |
|||
} |
|||
|
|||
._ul ._ul { |
|||
margin: 0; |
|||
list-style-type: circle; |
|||
} |
|||
|
|||
._ul ._ul ._ul { |
|||
list-style-type: square; |
|||
} |
|||
|
|||
._abbr, |
|||
._b, |
|||
._code, |
|||
._del, |
|||
._em, |
|||
._i, |
|||
._ins, |
|||
._label, |
|||
._q, |
|||
._span, |
|||
._strong, |
|||
._sub, |
|||
._sup { |
|||
display: inline; |
|||
} |
|||
|
|||
/* #ifdef APP-PLUS */ |
|||
._video { |
|||
width: 300px; |
|||
height: 225px; |
|||
} |
|||
/* #endif */ |
|||
</style> |
|||
File diff suppressed because it is too large
@ -0,0 +1,498 @@ |
|||
<template> |
|||
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle"> |
|||
<slot v-if="!nodes[0]" /> |
|||
<!-- #ifndef APP-PLUS-NVUE --> |
|||
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" /> |
|||
<!-- #endif --> |
|||
<!-- #ifdef APP-PLUS-NVUE --> |
|||
<web-view ref="web" src="/uni_modules/uv-parse/static/app-plus/uv-parse/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" /> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* uv-parse v1.0.3 |
|||
* @description 富文本组件 |
|||
* @tutorial https://www.uvui.cn/components/parse.html |
|||
* @property {String} container-style 容器的样式 |
|||
* @property {String} content 用于渲染的 html 字符串 |
|||
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制 |
|||
* @property {String} domain 主域名,用于拼接链接 |
|||
* @property {String} error-img 图片出错时的占位图链接 |
|||
* @property {Boolean} lazy-load 是否开启图片懒加载 |
|||
* @property {string} loading-img 图片加载过程中的占位图链接 |
|||
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频 |
|||
* @property {Boolean} preview-img 是否允许图片被点击时自动预览 |
|||
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动 |
|||
* @property {Boolean | String} selectable 是否开启长按复制 |
|||
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题 |
|||
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单 |
|||
* @property {Object} tag-style 标签的默认样式 |
|||
* @property {Boolean | Number} use-anchor 是否使用锚点链接 |
|||
* @event {Function} load dom 结构加载完毕时触发 |
|||
* @event {Function} ready 所有图片加载完毕时触发 |
|||
* @event {Function} imgtap 图片被点击时触发 |
|||
* @event {Function} linktap 链接被点击时触发 |
|||
* @event {Function} play 音视频播放时触发 |
|||
* @event {Function} error 媒体加载出错时触发 |
|||
*/ |
|||
// #ifndef APP-PLUS-NVUE |
|||
import node from './node/node' |
|||
// #endif |
|||
import Parser from './parser' |
|||
const plugins=[] |
|||
// #ifdef APP-PLUS-NVUE |
|||
const dom = weex.requireModule('dom') |
|||
// #endif |
|||
export default { |
|||
name: 'uv-parse', |
|||
data () { |
|||
return { |
|||
nodes: [], |
|||
// #ifdef APP-PLUS-NVUE |
|||
height: 3 |
|||
// #endif |
|||
} |
|||
}, |
|||
props: { |
|||
containerStyle: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
content: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
copyLink: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
domain: String, |
|||
errorImg: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
lazyLoad: { |
|||
type: [Boolean, String], |
|||
default: false |
|||
}, |
|||
loadingImg: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
pauseVideo: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
previewImg: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
scrollTable: [Boolean, String], |
|||
selectable: [Boolean, String], |
|||
setTitle: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
showImgMenu: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
tagStyle: Object, |
|||
useAnchor: [Boolean, Number] |
|||
}, |
|||
// #ifdef VUE3 |
|||
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'], |
|||
// #endif |
|||
// #ifndef APP-PLUS-NVUE |
|||
components: { |
|||
node |
|||
}, |
|||
// #endif |
|||
watch: { |
|||
content (content) { |
|||
this.setContent(content) |
|||
} |
|||
}, |
|||
created () { |
|||
this.plugins = [] |
|||
for (let i = plugins.length; i--;) { |
|||
this.plugins.push(new plugins[i](this)) |
|||
} |
|||
}, |
|||
mounted () { |
|||
if (this.content && !this.nodes.length) { |
|||
this.setContent(this.content) |
|||
} |
|||
}, |
|||
beforeDestroy () { |
|||
this._hook('onDetached') |
|||
}, |
|||
methods: { |
|||
/** |
|||
* @description 将锚点跳转的范围限定在一个 scroll-view 内 |
|||
* @param {Object} page scroll-view 所在页面的示例 |
|||
* @param {String} selector scroll-view 的选择器 |
|||
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名 |
|||
*/ |
|||
in (page, selector, scrollTop) { |
|||
// #ifndef APP-PLUS-NVUE |
|||
if (page && selector && scrollTop) { |
|||
this._in = { |
|||
page, |
|||
selector, |
|||
scrollTop |
|||
} |
|||
} |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 锚点跳转 |
|||
* @param {String} id 要跳转的锚点 id |
|||
* @param {Number} offset 跳转位置的偏移量 |
|||
* @returns {Promise} |
|||
*/ |
|||
navigateTo (id, offset) { |
|||
return new Promise((resolve, reject) => { |
|||
if (!this.useAnchor) { |
|||
reject(Error('Anchor is disabled')) |
|||
return |
|||
} |
|||
offset = offset || parseInt(this.useAnchor) || 0 |
|||
// #ifdef APP-PLUS-NVUE |
|||
if (!id) { |
|||
dom.scrollToElement(this.$refs.web, { |
|||
offset |
|||
}) |
|||
resolve() |
|||
} else { |
|||
this._navigateTo = { |
|||
resolve, |
|||
reject, |
|||
offset |
|||
} |
|||
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})') |
|||
} |
|||
// #endif |
|||
// #ifndef APP-PLUS-NVUE |
|||
let deep = ' ' |
|||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO |
|||
deep = '>>>' |
|||
// #endif |
|||
const selector = uni.createSelectorQuery() |
|||
// #ifndef MP-ALIPAY |
|||
.in(this._in ? this._in.page : this) |
|||
// #endif |
|||
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect() |
|||
if (this._in) { |
|||
selector.select(this._in.selector).scrollOffset() |
|||
.select(this._in.selector).boundingClientRect() |
|||
} else { |
|||
// 获取 scroll-view 的位置和滚动距离 |
|||
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离 |
|||
} |
|||
selector.exec(res => { |
|||
if (!res[0]) { |
|||
reject(Error('Label not found')) |
|||
return |
|||
} |
|||
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset |
|||
if (this._in) { |
|||
// scroll-view 跳转 |
|||
this._in.page[this._in.scrollTop] = scrollTop |
|||
} else { |
|||
// 页面跳转 |
|||
uni.pageScrollTo({ |
|||
scrollTop, |
|||
duration: 300 |
|||
}) |
|||
} |
|||
resolve() |
|||
}) |
|||
// #endif |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* @description 获取文本内容 |
|||
* @return {String} |
|||
*/ |
|||
getText (nodes) { |
|||
let text = ''; |
|||
(function traversal (nodes) { |
|||
for (let i = 0; i < nodes.length; i++) { |
|||
const node = nodes[i] |
|||
if (node.type === 'text') { |
|||
text += node.text.replace(/&/g, '&') |
|||
} else if (node.name === 'br') { |
|||
text += '\n' |
|||
} else { |
|||
// 块级标签前后加换行 |
|||
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7') |
|||
if (isBlock && text && text[text.length - 1] !== '\n') { |
|||
text += '\n' |
|||
} |
|||
// 递归获取子节点的文本 |
|||
if (node.children) { |
|||
traversal(node.children) |
|||
} |
|||
if (isBlock && text[text.length - 1] !== '\n') { |
|||
text += '\n' |
|||
} else if (node.name === 'td' || node.name === 'th') { |
|||
text += '\t' |
|||
} |
|||
} |
|||
} |
|||
})(nodes || this.nodes) |
|||
return text |
|||
}, |
|||
|
|||
/** |
|||
* @description 获取内容大小和位置 |
|||
* @return {Promise} |
|||
*/ |
|||
getRect () { |
|||
return new Promise((resolve, reject) => { |
|||
uni.createSelectorQuery() |
|||
// #ifndef MP-ALIPAY |
|||
.in(this) |
|||
// #endif |
|||
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found'))) |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* @description 暂停播放媒体 |
|||
*/ |
|||
pauseMedia () { |
|||
for (let i = (this._videos || []).length; i--;) { |
|||
this._videos[i].pause() |
|||
} |
|||
// #ifdef APP-PLUS |
|||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()' |
|||
// #ifndef APP-PLUS-NVUE |
|||
let page = this.$parent |
|||
while (!page.$scope) page = page.$parent |
|||
page.$scope.$getAppWebview().evalJS(command) |
|||
// #endif |
|||
// #ifdef APP-PLUS-NVUE |
|||
this.$refs.web.evalJs(command) |
|||
// #endif |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 设置媒体播放速率 |
|||
* @param {Number} rate 播放速率 |
|||
*/ |
|||
setPlaybackRate (rate) { |
|||
this.playbackRate = rate |
|||
for (let i = (this._videos || []).length; i--;) { |
|||
this._videos[i].playbackRate(rate) |
|||
} |
|||
// #ifdef APP-PLUS |
|||
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate |
|||
// #ifndef APP-PLUS-NVUE |
|||
let page = this.$parent |
|||
while (!page.$scope) page = page.$parent |
|||
page.$scope.$getAppWebview().evalJS(command) |
|||
// #endif |
|||
// #ifdef APP-PLUS-NVUE |
|||
this.$refs.web.evalJs(command) |
|||
// #endif |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 设置内容 |
|||
* @param {String} content html 内容 |
|||
* @param {Boolean} append 是否在尾部追加 |
|||
*/ |
|||
setContent (content, append) { |
|||
if (!append || !this.imgList) { |
|||
this.imgList = [] |
|||
} |
|||
const nodes = new Parser(this).parse(content) |
|||
// #ifdef APP-PLUS-NVUE |
|||
if (this._ready) { |
|||
this._set(nodes, append) |
|||
} |
|||
// #endif |
|||
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes) |
|||
|
|||
// #ifndef APP-PLUS-NVUE |
|||
this._videos = [] |
|||
this.$nextTick(() => { |
|||
this._hook('onLoad') |
|||
this.$emit('load') |
|||
}) |
|||
|
|||
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) { |
|||
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕 |
|||
let height = 0 |
|||
const callback = rect => { |
|||
if (!rect || !rect.height) rect = {} |
|||
// 350ms 总高度无变化就触发 ready 事件 |
|||
if (rect.height === height) { |
|||
this.$emit('ready', rect) |
|||
} else { |
|||
height = rect.height |
|||
setTimeout(() => { |
|||
this.getRect().then(callback).catch(callback) |
|||
}, 350) |
|||
} |
|||
} |
|||
this.getRect().then(callback).catch(callback) |
|||
} else { |
|||
// 未设置懒加载,等待所有图片加载完毕 |
|||
if (!this.imgList._unloadimgs) { |
|||
this.getRect().then(rect => { |
|||
this.$emit('ready', rect) |
|||
}).catch(() => { |
|||
this.$emit('ready', {}) |
|||
}) |
|||
} |
|||
} |
|||
// #endif |
|||
}, |
|||
|
|||
/** |
|||
* @description 调用插件钩子函数 |
|||
*/ |
|||
_hook (name) { |
|||
for (let i = plugins.length; i--;) { |
|||
if (this.plugins[i][name]) { |
|||
this.plugins[i][name]() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// #ifdef APP-PLUS-NVUE |
|||
/** |
|||
* @description 设置内容 |
|||
*/ |
|||
_set (nodes, append) { |
|||
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')') |
|||
}, |
|||
|
|||
/** |
|||
* @description 接收到 web-view 消息 |
|||
*/ |
|||
_onMessage (e) { |
|||
const message = e.detail.data[0] |
|||
switch (message.action) { |
|||
// web-view 初始化完毕 |
|||
case 'onJSBridgeReady': |
|||
this._ready = true |
|||
if (this.nodes) { |
|||
this._set(this.nodes) |
|||
} |
|||
break |
|||
// 内容 dom 加载完毕 |
|||
case 'onLoad': |
|||
this.height = message.height |
|||
this._hook('onLoad') |
|||
this.$emit('load') |
|||
break |
|||
// 所有图片加载完毕 |
|||
case 'onReady': |
|||
this.getRect().then(res => { |
|||
this.$emit('ready', res) |
|||
}).catch(() => { |
|||
this.$emit('ready', {}) |
|||
}) |
|||
break |
|||
// 总高度发生变化 |
|||
case 'onHeightChange': |
|||
this.height = message.height |
|||
break |
|||
// 图片点击 |
|||
case 'onImgTap': |
|||
this.$emit('imgtap', message.attrs) |
|||
if (this.previewImg) { |
|||
uni.previewImage({ |
|||
current: parseInt(message.attrs.i), |
|||
urls: this.imgList |
|||
}) |
|||
} |
|||
break |
|||
// 链接点击 |
|||
case 'onLinkTap': { |
|||
const href = message.attrs.href |
|||
this.$emit('linktap', message.attrs) |
|||
if (href) { |
|||
// 锚点跳转 |
|||
if (href[0] === '#') { |
|||
if (this.useAnchor) { |
|||
dom.scrollToElement(this.$refs.web, { |
|||
offset: message.offset |
|||
}) |
|||
} |
|||
} else if (href.includes('://')) { |
|||
// 打开外链 |
|||
if (this.copyLink) { |
|||
plus.runtime.openWeb(href) |
|||
} |
|||
} else { |
|||
uni.navigateTo({ |
|||
url: href, |
|||
fail () { |
|||
uni.switchTab({ |
|||
url: href |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
break |
|||
} |
|||
case 'onPlay': |
|||
this.$emit('play') |
|||
break |
|||
// 获取到锚点的偏移量 |
|||
case 'getOffset': |
|||
if (typeof message.offset === 'number') { |
|||
dom.scrollToElement(this.$refs.web, { |
|||
offset: message.offset + this._navigateTo.offset |
|||
}) |
|||
this._navigateTo.resolve() |
|||
} else { |
|||
this._navigateTo.reject(Error('Label not found')) |
|||
} |
|||
break |
|||
// 点击 |
|||
case 'onClick': |
|||
this.$emit('tap') |
|||
this.$emit('click') |
|||
break |
|||
// 出错 |
|||
case 'onError': |
|||
this.$emit('error', { |
|||
source: message.source, |
|||
attrs: message.attrs |
|||
}) |
|||
} |
|||
} |
|||
// #endif |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
/* #ifndef APP-PLUS-NVUE */ |
|||
/* 根节点样式 */ |
|||
._root { |
|||
padding: 1px 0; |
|||
overflow-x: auto; |
|||
overflow-y: hidden; |
|||
-webkit-overflow-scrolling: touch; |
|||
} |
|||
|
|||
/* 长按复制 */ |
|||
._select { |
|||
user-select: text; |
|||
} |
|||
/* #endif */ |
|||
</style> |
|||
@ -0,0 +1,87 @@ |
|||
{ |
|||
"id": "uv-parse", |
|||
"displayName": "uv-parse 富文本解析器 全面兼容vue3+2、app、h5、小程序等多端", |
|||
"version": "1.0.4", |
|||
"description": "uv-parse 该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生HTML标签的各类字符串等,此组件和uni-app官方的rich-text组件功能有重合之处,但是也有不同的地方。", |
|||
"keywords": [ |
|||
"uv-parse", |
|||
"uvui", |
|||
"uv-ui", |
|||
"parse", |
|||
"富文本" |
|||
], |
|||
"repository": "", |
|||
"engines": { |
|||
"HBuilderX": "^3.1.0" |
|||
}, |
|||
"dcloudext": { |
|||
"type": "component-vue", |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "插件不采集任何数据", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [ |
|||
"uv-ui-tools" |
|||
], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y" |
|||
}, |
|||
"client": { |
|||
"Vue": { |
|||
"vue2": "y", |
|||
"vue3": "y" |
|||
}, |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "y" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "y", |
|||
"百度": "y", |
|||
"字节跳动": "y", |
|||
"QQ": "y", |
|||
"钉钉": "u", |
|||
"快手": "u", |
|||
"飞书": "u", |
|||
"京东": "u" |
|||
}, |
|||
"快应用": { |
|||
"华为": "u", |
|||
"联盟": "u" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
## Parse 富文本解析器 |
|||
|
|||
> **组件名:uv-parse** |
|||
|
|||
该组件一般用于富文本解析场景,比如解析文章内容,商品详情,带原生`HTML`标签的各类字符串等,此组件和`uni-app`官方的`rich-text`组件功能有重合之处,但是也有不同的地方。 |
|||
|
|||
该插件只提供富文本的解析,该功能已经足够丰富。如果需要富文本的编辑,可使用`uniapp`官方提供的组件。 |
|||
|
|||
# <a href="https://www.uvui.cn/components/parse.html" target="_blank">查看文档</a> |
|||
|
|||
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) |
|||
|
|||
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui) |
|||
|
|||
<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank"> |
|||
|
|||
 |
|||
|
|||
</a> |
|||
|
|||
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a> |
|||
@ -0,0 +1,224 @@ |
|||
'use strict' |
|||
|
|||
// 等待初始化完毕
|
|||
document.addEventListener('UniAppJSBridgeReady', () => { |
|||
document.body.onclick = function () { |
|||
return uni.postMessage({ |
|||
data: { |
|||
action: 'onClick' |
|||
} |
|||
}) |
|||
} |
|||
|
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onJSBridgeReady' |
|||
} |
|||
}) |
|||
}) |
|||
let options |
|||
let medias = [] |
|||
/** |
|||
* @description 获取标签的所有属性 |
|||
* @param {Element} ele |
|||
*/ |
|||
|
|||
function getAttrs(ele) { |
|||
const attrs = Object.create(null) |
|||
|
|||
for (let i = ele.attributes.length; i--;) { |
|||
attrs[ele.attributes[i].name] = ele.attributes[i].value |
|||
} |
|||
|
|||
return attrs |
|||
} |
|||
/** |
|||
* @description 图片加载出错 |
|||
*/ |
|||
|
|||
function onImgError() { |
|||
if (options[1]) { |
|||
this.src = options[1] |
|||
this.onerror = null |
|||
} // 取消监听点击
|
|||
|
|||
this.onclick = null |
|||
this.ontouchstart = null |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onError', |
|||
source: 'img', |
|||
attrs: getAttrs(this) |
|||
} |
|||
}) |
|||
} |
|||
/** |
|||
* @description 创建 dom 结构 |
|||
* @param {object[]} nodes 节点数组 |
|||
* @param {Element} parent 父节点 |
|||
* @param {string} namespace 命名空间 |
|||
*/ |
|||
|
|||
function createDom(nodes, parent, namespace) { |
|||
const _loop = function _loop(i) { |
|||
const node = nodes[i] |
|||
let ele = void 0 |
|||
|
|||
if (!node.type || node.type == 'node') { |
|||
let { name } = node // svg 需要设置 namespace
|
|||
|
|||
if (name == 'svg') namespace = 'http://www.w3.org/2000/svg' |
|||
if (name == 'html' || name == 'body') name = 'div' // 创建标签
|
|||
|
|||
if (!namespace) ele = document.createElement(name); else ele = document.createElementNS(namespace, name) // 设置属性
|
|||
|
|||
for (const item in node.attrs) { |
|||
ele.setAttribute(item, node.attrs[item]) |
|||
} // 递归创建子节点
|
|||
|
|||
if (node.children) createDom(node.children, ele, namespace) // 处理图片
|
|||
|
|||
if (name == 'img') { |
|||
if (!ele.src && ele.getAttribute('data-src')) ele.src = ele.getAttribute('data-src') |
|||
|
|||
if (!node.attrs.ignore) { |
|||
// 监听图片点击事件
|
|||
ele.onclick = function (e) { |
|||
e.stopPropagation() |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onImgTap', |
|||
attrs: getAttrs(this) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
if (options[2]) { |
|||
image = new Image() |
|||
image.src = ele.src |
|||
ele.src = options[2] |
|||
|
|||
image.onload = function () { |
|||
ele.src = this.src |
|||
} |
|||
|
|||
image.onerror = function () { |
|||
ele.onerror() |
|||
} |
|||
} |
|||
|
|||
ele.onerror = onImgError |
|||
} // 处理链接
|
|||
else if (name == 'a') { |
|||
ele.addEventListener('click', function (e) { |
|||
e.stopPropagation() |
|||
e.preventDefault() // 阻止默认跳转
|
|||
|
|||
const href = this.getAttribute('href') |
|||
let offset |
|||
if (href && href[0] == '#') offset = (document.getElementById(href.substr(1)) || {}).offsetTop |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onLinkTap', |
|||
attrs: getAttrs(this), |
|||
offset |
|||
} |
|||
}) |
|||
}, true) |
|||
} // 处理音视频
|
|||
else if (name == 'video' || name == 'audio') { |
|||
medias.push(ele) |
|||
|
|||
if (!node.attrs.autoplay) { |
|||
if (!node.attrs.controls) ele.setAttribute('controls', 'true') // 空白图占位
|
|||
|
|||
if (!node.attrs.poster) ele.setAttribute('poster', "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>") |
|||
} |
|||
|
|||
if (options[3]) { |
|||
ele.onplay = function () { |
|||
for (let _i = 0; _i < medias.length; _i++) { |
|||
if (medias[_i] != this) medias[_i].pause() |
|||
} |
|||
} |
|||
} |
|||
|
|||
ele.onerror = function () { |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onError', |
|||
source: name, |
|||
attrs: getAttrs(this) |
|||
} |
|||
}) |
|||
} |
|||
} // 处理表格
|
|||
else if (name == 'table' && options[4] && !ele.style.cssText.includes('inline')) { |
|||
const div = document.createElement('div') |
|||
div.style.overflow = 'auto' |
|||
div.appendChild(ele) |
|||
ele = div |
|||
} else if (name == 'svg') namespace = void 0 |
|||
} else ele = document.createTextNode(node.text.replace(/&/g, '&')) |
|||
|
|||
parent.appendChild(ele) |
|||
} |
|||
|
|||
for (let i = 0; i < nodes.length; i++) { |
|||
var image |
|||
|
|||
_loop(i) |
|||
} |
|||
} // 设置 html 内容
|
|||
|
|||
window.setContent = function (nodes, opts, append) { |
|||
const ele = document.getElementById('content') // 背景颜色
|
|||
|
|||
if (opts[0]) document.body.bgColor = opts[0] // 长按复制
|
|||
|
|||
if (!opts[5]) ele.style.userSelect = 'none' |
|||
|
|||
if (!append) { |
|||
ele.innerHTML = '' // 不追加则先清空
|
|||
|
|||
medias = [] |
|||
} |
|||
|
|||
options = opts |
|||
const fragment = document.createDocumentFragment() |
|||
createDom(nodes, fragment) |
|||
ele.appendChild(fragment) // 触发事件
|
|||
|
|||
let height = ele.scrollHeight |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onLoad', |
|||
height |
|||
} |
|||
}) |
|||
clearInterval(window.timer) |
|||
let ready = false |
|||
window.timer = setInterval(() => { |
|||
if (ele.scrollHeight != height) { |
|||
height = ele.scrollHeight |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onHeightChange', |
|||
height |
|||
} |
|||
}) |
|||
} else if (!ready) { |
|||
ready = true |
|||
uni.postMessage({ |
|||
data: { |
|||
action: 'onReady' |
|||
} |
|||
}) |
|||
} |
|||
}, 350) |
|||
} // 回收计时器
|
|||
|
|||
window.onunload = function () { |
|||
clearInterval(window.timer) |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
!(function (e, n) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = n() : typeof define === 'function' && define.amd ? define(n) : (e = e || self).uni = n() }(this, (() => { |
|||
'use strict' |
|||
|
|||
try { const e = {}; Object.defineProperty(e, 'passive', { get() { !0 } }), window.addEventListener('test-passive', null, e) } catch (e) {} const n = Object.prototype.hasOwnProperty; function t(e, t) { return n.call(e, t) } const i = []; const a = function (e, n) { const t = { options: { timestamp: +new Date() }, name: e, arg: n }; if (window.__dcloud_weex_postMessage || window.__dcloud_weex_) { if (e === 'postMessage') { const a = { data: [n] }; return window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessage(a) : window.__dcloud_weex_.postMessage(JSON.stringify(a)) } const o = { type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }; window.__dcloud_weex_postMessage ? window.__dcloud_weex_postMessageToService(o) : window.__dcloud_weex_.postMessageToService(JSON.stringify(o)) } if (!window.plus) return window.parent.postMessage({ type: 'WEB_INVOKE_APPSERVICE', data: t, pageId: '' }, '*'); if (i.length === 0) { const r = plus.webview.currentWebview(); if (!r) throw new Error('plus.webview.currentWebview() is undefined'); const d = r.parent(); let s = ''; s = d ? d.id : r.id, i.push(s) } if (plus.webview.getWebviewById('__uniapp__service'))plus.webview.postMessageToUniNView({ type: 'WEB_INVOKE_APPSERVICE', args: { data: t, webviewIds: i } }, '__uniapp__service'); else { const w = JSON.stringify(t); plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat('WEB_INVOKE_APPSERVICE', '",').concat(w, ',').concat(JSON.stringify(i), ');')) } }; const o = { |
|||
navigateTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('navigateTo', { url: encodeURI(n) }) }, navigateBack() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.delta; a('navigateBack', { delta: parseInt(n) || 1 }) }, switchTab() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('switchTab', { url: encodeURI(n) }) }, reLaunch() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('reLaunch', { url: encodeURI(n) }) }, redirectTo() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; const n = e.url; a('redirectTo', { url: encodeURI(n) }) }, getEnv(e) { window.plus ? e({ plus: !0 }) : e({ h5: !0 }) }, postMessage() { const e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; a('postMessage', e.data || {}) } |
|||
}; const r = /uni-app/i.test(navigator.userAgent); const d = /Html5Plus/i.test(navigator.userAgent); const s = /complete|loaded|interactive/; const w = window.my && navigator.userAgent.indexOf('AlipayClient') > -1; const u = window.swan && window.swan.webView && /swan/i.test(navigator.userAgent); const c = window.qq && window.qq.miniProgram && /QQ/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const g = window.tt && window.tt.miniProgram && /toutiaomicroapp/i.test(navigator.userAgent); const v = window.wx && window.wx.miniProgram && /micromessenger/i.test(navigator.userAgent) && /miniProgram/i.test(navigator.userAgent); const p = window.qa && /quickapp/i.test(navigator.userAgent); for (var l, _ = function () { window.UniAppJSBridge = !0, document.dispatchEvent(new CustomEvent('UniAppJSBridgeReady', { bubbles: !0, cancelable: !0 })) }, f = [function (e) { if (r || d) return window.__dcloud_weex_postMessage || window.__dcloud_weex_ ? document.addEventListener('DOMContentLoaded', e) : window.plus && s.test(document.readyState) ? setTimeout(e, 0) : document.addEventListener('plusready', e), o }, function (e) { if (v) return window.WeixinJSBridge && window.WeixinJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('WeixinJSBridgeReady', e), window.wx.miniProgram }, function (e) { if (c) return window.QQJSBridge && window.QQJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QQJSBridgeReady', e), window.qq.miniProgram }, function (e) { |
|||
if (w) { |
|||
document.addEventListener('DOMContentLoaded', e); const n = window.my; return { |
|||
navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv |
|||
} |
|||
} |
|||
}, function (e) { if (u) return document.addEventListener('DOMContentLoaded', e), window.swan.webView }, function (e) { if (g) return document.addEventListener('DOMContentLoaded', e), window.tt.miniProgram }, function (e) { |
|||
if (p) { |
|||
window.QaJSBridge && window.QaJSBridge.invoke ? setTimeout(e, 0) : document.addEventListener('QaJSBridgeReady', e); const n = window.qa; return { |
|||
navigateTo: n.navigateTo, navigateBack: n.navigateBack, switchTab: n.switchTab, reLaunch: n.reLaunch, redirectTo: n.redirectTo, postMessage: n.postMessage, getEnv: n.getEnv |
|||
} |
|||
} |
|||
}, function (e) { return document.addEventListener('DOMContentLoaded', e), o }], m = 0; m < f.length && !(l = f[m](_)); m++);l || (l = {}); const E = typeof uni !== 'undefined' ? uni : {}; if (!E.navigateTo) for (const b in l)t(l, b) && (E[b] = l[b]); return E.webView = l, E |
|||
}))) |
|||
@ -0,0 +1 @@ |
|||
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id="content"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body> |
|||
@ -0,0 +1,76 @@ |
|||
## 1.1.25(2024-01-20) |
|||
1.1.20版本更新 |
|||
## 1.1.24(2023-12-21) |
|||
1. luch-request更新 |
|||
## 1.1.23(2023-12-12) |
|||
1. 1.1.19版本 |
|||
## 1.1.22(2023-11-28) |
|||
1. 优化 |
|||
## 1.1.21(2023-11-10) |
|||
1. 1.1.17版本 |
|||
## 1.1.20(2023-10-30) |
|||
1. 1.1.16版本 |
|||
## 1.1.19(2023-10-13) |
|||
1. 兼容vue3 |
|||
## 1.1.18(2023-10-12) |
|||
1. 1.1.15版本 |
|||
## 1.1.17(2023-09-27) |
|||
1. 1.1.14版本发布 |
|||
## 1.1.16(2023-09-15) |
|||
1. 1.1.13版本发布 |
|||
## 1.1.15(2023-09-15) |
|||
1. 更新button.js相关按钮支持open-type="agreePrivacyAuthorization" |
|||
## 1.1.14(2023-09-14) |
|||
1. 优化dayjs |
|||
## 1.1.13(2023-09-13) |
|||
1. 优化,$uv中增加unit参数,方便组件中使用 |
|||
## 1.1.12(2023-09-10) |
|||
1. 升级版本 |
|||
## 1.1.11(2023-09-04) |
|||
1. 1.1.11版本 |
|||
## 1.1.10(2023-08-31) |
|||
1. 修复customStyle和customClass存在冲突的问题 |
|||
## 1.1.9(2023-08-27) |
|||
1. 版本升级 |
|||
2. 优化 |
|||
## 1.1.8(2023-08-24) |
|||
1. 版本升级 |
|||
## 1.1.7(2023-08-22) |
|||
1. 版本升级 |
|||
## 1.1.6(2023-08-18) |
|||
uvui版本:1.1.6 |
|||
## 1.0.15(2023-08-14) |
|||
1. 更新uvui版本号 |
|||
## 1.0.13(2023-08-06) |
|||
1. 优化 |
|||
## 1.0.12(2023-08-06) |
|||
1. 修改版本号 |
|||
## 1.0.11(2023-08-06) |
|||
1. 路由增加events参数 |
|||
2. 路由拦截修复 |
|||
## 1.0.10(2023-08-01) |
|||
1. 优化 |
|||
## 1.0.9(2023-06-28) |
|||
优化openType.js |
|||
## 1.0.8(2023-06-15) |
|||
1. 修改支付宝报错的BUG |
|||
## 1.0.7(2023-06-07) |
|||
1. 解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors |
|||
2. 解决上述提示,需要在uni.scss配置$uvui-nvue-style: false; 然后在APP.vue下面引入uvui内置的基础样式:@import '@/uni_modules/uv-ui-tools/index.scss'; |
|||
## 1.0.6(2023-06-04) |
|||
1. uv-ui-tools 优化工具组件,兼容更多功能 |
|||
2. 小程序分享功能优化等 |
|||
## 1.0.5(2023-06-02) |
|||
1. 修改扩展使用mixin中方法的问题 |
|||
## 1.0.4(2023-05-23) |
|||
1. 兼容百度小程序修改bem函数 |
|||
## 1.0.3(2023-05-16) |
|||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用 |
|||
2. 优化部分功能 |
|||
## 1.0.2(2023-05-10) |
|||
1. 增加Http请求封装 |
|||
2. 优化 |
|||
## 1.0.1(2023-05-04) |
|||
1. 修改名称及备注 |
|||
## 1.0.0(2023-05-04) |
|||
1. uv-ui工具集首次发布 |
|||
@ -0,0 +1,6 @@ |
|||
<template> |
|||
</template> |
|||
<script> |
|||
</script> |
|||
<style> |
|||
</style> |
|||
@ -0,0 +1,79 @@ |
|||
// 全局挂载引入http相关请求拦截插件
|
|||
import Request from './libs/luch-request' |
|||
|
|||
// 引入全局mixin
|
|||
import mixin from './libs/mixin/mixin.js' |
|||
// 小程序特有的mixin
|
|||
import mpMixin from './libs/mixin/mpMixin.js' |
|||
// #ifdef MP
|
|||
import mpShare from './libs/mixin/mpShare.js' |
|||
// #endif
|
|||
|
|||
// 路由封装
|
|||
import route from './libs/util/route.js' |
|||
// 公共工具函数
|
|||
import * as index from './libs/function/index.js' |
|||
// 防抖方法
|
|||
import debounce from './libs/function/debounce.js' |
|||
// 节流方法
|
|||
import throttle from './libs/function/throttle.js' |
|||
// 规则检验
|
|||
import * as test from './libs/function/test.js' |
|||
|
|||
// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
|
|||
import * as colorGradient from './libs/function/colorGradient.js' |
|||
|
|||
// 配置信息
|
|||
import config from './libs/config/config.js' |
|||
// 平台
|
|||
import platform from './libs/function/platform' |
|||
|
|||
const $uv = { |
|||
route, |
|||
config, |
|||
test, |
|||
date: index.timeFormat, // 另名date
|
|||
...index, |
|||
colorGradient: colorGradient.colorGradient, |
|||
hexToRgb: colorGradient.hexToRgb, |
|||
rgbToHex: colorGradient.rgbToHex, |
|||
colorToRgba: colorGradient.colorToRgba, |
|||
http: new Request(), |
|||
debounce, |
|||
throttle, |
|||
platform, |
|||
mixin, |
|||
mpMixin |
|||
} |
|||
uni.$uv = $uv; |
|||
const install = (Vue,options={}) => { |
|||
// #ifndef APP-NVUE
|
|||
const cloneMixin = index.deepClone(mixin); |
|||
delete cloneMixin?.props?.customClass; |
|||
delete cloneMixin?.props?.customStyle; |
|||
Vue.mixin(cloneMixin); |
|||
// #ifdef MP
|
|||
if(options.mpShare){ |
|||
Vue.mixin(mpShare); |
|||
} |
|||
// #endif
|
|||
// #endif
|
|||
// #ifdef VUE2
|
|||
// 时间格式化,同时两个名称,date和timeFormat
|
|||
Vue.filter('timeFormat', (timestamp, format) => uni.$uv.timeFormat(timestamp, format)); |
|||
Vue.filter('date', (timestamp, format) => uni.$uv.timeFormat(timestamp, format)); |
|||
// 将多久以前的方法,注入到全局过滤器
|
|||
Vue.filter('timeFrom', (timestamp, format) => uni.$uv.timeFrom(timestamp, format)); |
|||
// 同时挂载到uni和Vue.prototype中
|
|||
// #ifndef APP-NVUE
|
|||
// 只有vue,挂载到Vue.prototype才有意义,因为nvue中全局Vue.prototype和Vue.mixin是无效的
|
|||
Vue.prototype.$uv = $uv; |
|||
// #endif
|
|||
// #endif
|
|||
// #ifdef VUE3
|
|||
Vue.config.globalProperties.$uv = $uv; |
|||
// #endif
|
|||
} |
|||
export default { |
|||
install |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
// 引入公共基础类 |
|||
@import "./libs/css/common.scss"; |
|||
|
|||
// 非nvue的样式 |
|||
/* #ifndef APP-NVUE */ |
|||
@import "./libs/css/vue.scss"; |
|||
/* #endif */ |
|||
@ -0,0 +1,34 @@ |
|||
// 此版本发布于2024-01-20
|
|||
const version = '1.1.20' |
|||
|
|||
// 开发环境才提示,生产环境不会提示
|
|||
if (process.env.NODE_ENV === 'development') { |
|||
console.log(`\n %c uvui V${version} https://www.uvui.cn/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;'); |
|||
} |
|||
|
|||
export default { |
|||
v: version, |
|||
version, |
|||
// 主题名称
|
|||
type: [ |
|||
'primary', |
|||
'success', |
|||
'info', |
|||
'error', |
|||
'warning' |
|||
], |
|||
// 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持
|
|||
color: { |
|||
'uv-primary': '#2979ff', |
|||
'uv-warning': '#ff9900', |
|||
'uv-success': '#19be6b', |
|||
'uv-error': '#fa3534', |
|||
'uv-info': '#909399', |
|||
'uv-main-color': '#303133', |
|||
'uv-content-color': '#606266', |
|||
'uv-tips-color': '#909399', |
|||
'uv-light-color': '#c0c4cc' |
|||
}, |
|||
// 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx
|
|||
unit: 'px' |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
$uv-main-color: #303133 !default; |
|||
$uv-content-color: #606266 !default; |
|||
$uv-tips-color: #909193 !default; |
|||
$uv-light-color: #c0c4cc !default; |
|||
$uv-border-color: #dadbde !default; |
|||
$uv-bg-color: #f3f4f6 !default; |
|||
$uv-disabled-color: #c8c9cc !default; |
|||
|
|||
$uv-primary: #3c9cff !default; |
|||
$uv-primary-dark: #398ade !default; |
|||
$uv-primary-disabled: #9acafc !default; |
|||
$uv-primary-light: #ecf5ff !default; |
|||
|
|||
$uv-warning: #f9ae3d !default; |
|||
$uv-warning-dark: #f1a532 !default; |
|||
$uv-warning-disabled: #f9d39b !default; |
|||
$uv-warning-light: #fdf6ec !default; |
|||
|
|||
$uv-success: #5ac725 !default; |
|||
$uv-success-dark: #53c21d !default; |
|||
$uv-success-disabled: #a9e08f !default; |
|||
$uv-success-light: #f5fff0; |
|||
|
|||
$uv-error: #f56c6c !default; |
|||
$uv-error-dark: #e45656 !default; |
|||
$uv-error-disabled: #f7b2b2 !default; |
|||
$uv-error-light: #fef0f0 !default; |
|||
|
|||
$uv-info: #909399 !default; |
|||
$uv-info-dark: #767a82 !default; |
|||
$uv-info-disabled: #c4c6c9 !default; |
|||
$uv-info-light: #f4f4f5 !default; |
|||
@ -0,0 +1,100 @@ |
|||
// 超出行数,自动显示行尾省略号,最多5行 |
|||
// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】 |
|||
@for $i from 1 through 5 { |
|||
.uv-line-#{$i} { |
|||
/* #ifdef APP-NVUE */ |
|||
// nvue下,可以直接使用lines属性,这是weex特有样式 |
|||
lines: $i; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
flex: 1; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
// vue下,单行和多行显示省略号需要单独处理 |
|||
@if $i == '1' { |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} @else { |
|||
display: -webkit-box!important; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
word-break: break-all; |
|||
-webkit-line-clamp: $i; |
|||
-webkit-box-orient: vertical!important; |
|||
} |
|||
/* #endif */ |
|||
} |
|||
} |
|||
$uv-bordercolor: #dadbde; |
|||
@if variable-exists(uv-border-color) { |
|||
$uv-bordercolor: $uv-border-color; |
|||
} |
|||
|
|||
// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时, |
|||
// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效 |
|||
// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important |
|||
// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现 |
|||
.uv-border { |
|||
border-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-style: solid; |
|||
} |
|||
|
|||
.uv-border-top { |
|||
border-top-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-top-style: solid; |
|||
} |
|||
|
|||
.uv-border-left { |
|||
border-left-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-left-style: solid; |
|||
} |
|||
|
|||
.uv-border-right { |
|||
border-right-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-right-style: solid; |
|||
} |
|||
|
|||
.uv-border-bottom { |
|||
border-bottom-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-bottom-style: solid; |
|||
} |
|||
|
|||
.uv-border-top-bottom { |
|||
border-top-width: 0.5px!important; |
|||
border-bottom-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-top-style: solid; |
|||
border-bottom-style: solid; |
|||
} |
|||
|
|||
// 去除button的所有默认样式,让其表现跟普通的view、text元素一样 |
|||
.uv-reset-button { |
|||
padding: 0; |
|||
background-color: transparent; |
|||
/* #ifndef APP-PLUS */ |
|||
font-size: inherit; |
|||
line-height: inherit; |
|||
color: inherit; |
|||
/* #endif */ |
|||
/* #ifdef APP-NVUE */ |
|||
border-width: 0; |
|||
/* #endif */ |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.uv-reset-button::after { |
|||
border: none; |
|||
} |
|||
/* #endif */ |
|||
|
|||
.uv-hover-class { |
|||
opacity: 0.7; |
|||
} |
|||
|
|||
@ -0,0 +1,23 @@ |
|||
@mixin flex($direction: row) { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: $direction; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
// 由于uvui是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column; |
|||
// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常 |
|||
$uvui-nvue-style: true !default; |
|||
@if $uvui-nvue-style == true { |
|||
view, scroll-view, swiper-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex-shrink: 0; |
|||
flex-grow: 0; |
|||
flex-basis: auto; |
|||
align-items: stretch; |
|||
align-content: flex-start; |
|||
} |
|||
} |
|||
/* #endif */ |
|||
@ -0,0 +1,111 @@ |
|||
// 超出行数,自动显示行尾省略号,最多5行 |
|||
// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】 |
|||
@if variable-exists(show-lines) { |
|||
@for $i from 1 through 5 { |
|||
.uv-line-#{$i} { |
|||
/* #ifdef APP-NVUE */ |
|||
// nvue下,可以直接使用lines属性,这是weex特有样式 |
|||
lines: $i; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
flex: 1; |
|||
/* #endif */ |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
// vue下,单行和多行显示省略号需要单独处理 |
|||
@if $i == '1' { |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
} @else { |
|||
display: -webkit-box!important; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
word-break: break-all; |
|||
-webkit-line-clamp: $i; |
|||
-webkit-box-orient: vertical!important; |
|||
} |
|||
/* #endif */ |
|||
} |
|||
} |
|||
} |
|||
@if variable-exists(show-border) { |
|||
$uv-bordercolor: #dadbde; |
|||
@if variable-exists(uv-border-color) { |
|||
$uv-bordercolor: $uv-border-color; |
|||
} |
|||
// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时, |
|||
// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效 |
|||
// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important |
|||
// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现 |
|||
@if variable-exists(show-border-surround) { |
|||
.uv-border { |
|||
border-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-style: solid; |
|||
} |
|||
} |
|||
@if variable-exists(show-border-top) { |
|||
.uv-border-top { |
|||
border-top-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-top-style: solid; |
|||
} |
|||
} |
|||
@if variable-exists(show-border-left) { |
|||
.uv-border-left { |
|||
border-left-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-left-style: solid; |
|||
} |
|||
} |
|||
@if variable-exists(show-border-right) { |
|||
.uv-border-right { |
|||
border-right-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-right-style: solid; |
|||
} |
|||
} |
|||
@if variable-exists(show-border-bottom) { |
|||
.uv-border-bottom { |
|||
border-bottom-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-bottom-style: solid; |
|||
} |
|||
} |
|||
@if variable-exists(show-border-top-bottom) { |
|||
.uv-border-top-bottom { |
|||
border-top-width: 0.5px!important; |
|||
border-bottom-width: 0.5px!important; |
|||
border-color: $uv-bordercolor!important; |
|||
border-top-style: solid; |
|||
border-bottom-style: solid; |
|||
} |
|||
} |
|||
} |
|||
@if variable-exists(show-reset-button) { |
|||
// 去除button的所有默认样式,让其表现跟普通的view、text元素一样 |
|||
.uv-reset-button { |
|||
padding: 0; |
|||
background-color: transparent; |
|||
/* #ifndef APP-PLUS */ |
|||
font-size: inherit; |
|||
line-height: inherit; |
|||
color: inherit; |
|||
/* #endif */ |
|||
/* #ifdef APP-NVUE */ |
|||
border-width: 0; |
|||
/* #endif */ |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.uv-reset-button::after { |
|||
border: none; |
|||
} |
|||
/* #endif */ |
|||
} |
|||
@if variable-exists(show-hover) { |
|||
.uv-hover-class { |
|||
opacity: 0.7; |
|||
} |
|||
} |
|||
@ -0,0 +1,40 @@ |
|||
// 历遍生成4个方向的底部安全区 |
|||
@each $d in top, right, bottom, left { |
|||
.uv-safe-area-inset-#{$d} { |
|||
padding-#{$d}: 0; |
|||
padding-#{$d}: constant(safe-area-inset-#{$d}); |
|||
padding-#{$d}: env(safe-area-inset-#{$d}); |
|||
} |
|||
} |
|||
|
|||
//提升H5端uni.toast()的层级,避免被uvui的modal等遮盖 |
|||
/* #ifdef H5 */ |
|||
uni-toast { |
|||
z-index: 10090; |
|||
} |
|||
uni-toast .uni-toast { |
|||
z-index: 10090; |
|||
} |
|||
/* #endif */ |
|||
|
|||
// 隐藏scroll-view的滚动条 |
|||
::-webkit-scrollbar { |
|||
display: none; |
|||
width: 0 !important; |
|||
height: 0 !important; |
|||
-webkit-appearance: none; |
|||
background: transparent; |
|||
} |
|||
|
|||
$uvui-nvue-style: true !default; |
|||
@if $uvui-nvue-style == false { |
|||
view, scroll-view, swiper-item { |
|||
display: flex; |
|||
flex-direction: column; |
|||
flex-shrink: 0; |
|||
flex-grow: 0; |
|||
flex-basis: auto; |
|||
align-items: stretch; |
|||
align-content: flex-start; |
|||
} |
|||
} |
|||
@ -0,0 +1,134 @@ |
|||
/** |
|||
* 求两个颜色之间的渐变值 |
|||
* @param {string} startColor 开始的颜色 |
|||
* @param {string} endColor 结束的颜色 |
|||
* @param {number} step 颜色等分的份额 |
|||
* */ |
|||
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) { |
|||
const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式
|
|||
const startR = startRGB[0] |
|||
const startG = startRGB[1] |
|||
const startB = startRGB[2] |
|||
|
|||
const endRGB = hexToRgb(endColor, false) |
|||
const endR = endRGB[0] |
|||
const endG = endRGB[1] |
|||
const endB = endRGB[2] |
|||
|
|||
const sR = (endR - startR) / step // 总差值
|
|||
const sG = (endG - startG) / step |
|||
const sB = (endB - startB) / step |
|||
const colorArr = [] |
|||
for (let i = 0; i < step; i++) { |
|||
// 计算每一步的hex值
|
|||
let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB |
|||
* i + startB))})`)
|
|||
// 确保第一个颜色值为startColor的值
|
|||
if (i === 0) hex = rgbToHex(startColor) |
|||
// 确保最后一个颜色值为endColor的值
|
|||
if (i === step - 1) hex = rgbToHex(endColor) |
|||
colorArr.push(hex) |
|||
} |
|||
return colorArr |
|||
} |
|||
|
|||
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
|
|||
function hexToRgb(sColor, str = true) { |
|||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ |
|||
sColor = String(sColor).toLowerCase() |
|||
if (sColor && reg.test(sColor)) { |
|||
if (sColor.length === 4) { |
|||
let sColorNew = '#' |
|||
for (let i = 1; i < 4; i += 1) { |
|||
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)) |
|||
} |
|||
sColor = sColorNew |
|||
} |
|||
// 处理六位的颜色值
|
|||
const sColorChange = [] |
|||
for (let i = 1; i < 7; i += 2) { |
|||
sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`)) |
|||
} |
|||
if (!str) { |
|||
return sColorChange |
|||
} |
|||
return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})` |
|||
} if (/^(rgb|RGB)/.test(sColor)) { |
|||
const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',') |
|||
return arr.map((val) => Number(val)) |
|||
} |
|||
return sColor |
|||
} |
|||
|
|||
// 将rgb表示方式转换为hex表示方式
|
|||
function rgbToHex(rgb) { |
|||
const _this = rgb |
|||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ |
|||
if (/^(rgb|RGB)/.test(_this)) { |
|||
const aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',') |
|||
let strHex = '#' |
|||
for (let i = 0; i < aColor.length; i++) { |
|||
let hex = Number(aColor[i]).toString(16) |
|||
hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位
|
|||
if (hex === '0') { |
|||
hex += hex |
|||
} |
|||
strHex += hex |
|||
} |
|||
if (strHex.length !== 7) { |
|||
strHex = _this |
|||
} |
|||
return strHex |
|||
} if (reg.test(_this)) { |
|||
const aNum = _this.replace(/#/, '').split('') |
|||
if (aNum.length === 6) { |
|||
return _this |
|||
} if (aNum.length === 3) { |
|||
let numHex = '#' |
|||
for (let i = 0; i < aNum.length; i += 1) { |
|||
numHex += (aNum[i] + aNum[i]) |
|||
} |
|||
return numHex |
|||
} |
|||
} else { |
|||
return _this |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串 |
|||
* sHex为传入的十六进制的色值 |
|||
* alpha为rgba的透明度 |
|||
*/ |
|||
function colorToRgba(color, alpha) { |
|||
color = rgbToHex(color) |
|||
// 十六进制颜色值的正则表达式
|
|||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/ |
|||
/* 16进制颜色转为RGB格式 */ |
|||
let sColor = String(color).toLowerCase() |
|||
if (sColor && reg.test(sColor)) { |
|||
if (sColor.length === 4) { |
|||
let sColorNew = '#' |
|||
for (let i = 1; i < 4; i += 1) { |
|||
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1)) |
|||
} |
|||
sColor = sColorNew |
|||
} |
|||
// 处理六位的颜色值
|
|||
const sColorChange = [] |
|||
for (let i = 1; i < 7; i += 2) { |
|||
sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`)) |
|||
} |
|||
// return sColorChange.join(',')
|
|||
return `rgba(${sColorChange.join(',')},${alpha})` |
|||
} |
|||
|
|||
return sColor |
|||
} |
|||
|
|||
export { |
|||
colorGradient, |
|||
hexToRgb, |
|||
rgbToHex, |
|||
colorToRgba |
|||
} |
|||
@ -0,0 +1,29 @@ |
|||
let timeout = null |
|||
|
|||
/** |
|||
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数 |
|||
* |
|||
* @param {Function} func 要执行的回调函数 |
|||
* @param {Number} wait 延时的时间 |
|||
* @param {Boolean} immediate 是否立即执行 |
|||
* @return null |
|||
*/ |
|||
function debounce(func, wait = 500, immediate = false) { |
|||
// 清除定时器
|
|||
if (timeout !== null) clearTimeout(timeout) |
|||
// 立即执行,此类情况一般用不到
|
|||
if (immediate) { |
|||
const callNow = !timeout |
|||
timeout = setTimeout(() => { |
|||
timeout = null |
|||
}, wait) |
|||
if (callNow) typeof func === 'function' && func() |
|||
} else { |
|||
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
|
|||
timeout = setTimeout(() => { |
|||
typeof func === 'function' && func() |
|||
}, wait) |
|||
} |
|||
} |
|||
|
|||
export default debounce |
|||
@ -0,0 +1,167 @@ |
|||
let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
|
|||
|
|||
/** |
|||
* 把错误的数据转正 |
|||
* @private |
|||
* @example strip(0.09999999999999998)=0.1 |
|||
*/ |
|||
function strip(num, precision = 15) { |
|||
return +parseFloat(Number(num).toPrecision(precision)); |
|||
} |
|||
|
|||
/** |
|||
* Return digits length of a number |
|||
* @private |
|||
* @param {*number} num Input number |
|||
*/ |
|||
function digitLength(num) { |
|||
// Get digit length of e
|
|||
const eSplit = num.toString().split(/[eE]/); |
|||
const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0); |
|||
return len > 0 ? len : 0; |
|||
} |
|||
|
|||
/** |
|||
* 把小数转成整数,如果是小数则放大成整数 |
|||
* @private |
|||
* @param {*number} num 输入数 |
|||
*/ |
|||
function float2Fixed(num) { |
|||
if (num.toString().indexOf('e') === -1) { |
|||
return Number(num.toString().replace('.', '')); |
|||
} |
|||
const dLen = digitLength(num); |
|||
return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num); |
|||
} |
|||
|
|||
/** |
|||
* 检测数字是否越界,如果越界给出提示 |
|||
* @private |
|||
* @param {*number} num 输入数 |
|||
*/ |
|||
function checkBoundary(num) { |
|||
if (_boundaryCheckingState) { |
|||
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { |
|||
console.warn(`${num} 超出了精度限制,结果可能不正确`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 把递归操作扁平迭代化 |
|||
* @param {number[]} arr 要操作的数字数组 |
|||
* @param {function} operation 迭代操作 |
|||
* @private |
|||
*/ |
|||
function iteratorOperation(arr, operation) { |
|||
const [num1, num2, ...others] = arr; |
|||
let res = operation(num1, num2); |
|||
|
|||
others.forEach((num) => { |
|||
res = operation(res, num); |
|||
}); |
|||
|
|||
return res; |
|||
} |
|||
|
|||
/** |
|||
* 高精度乘法 |
|||
* @export |
|||
*/ |
|||
export function times(...nums) { |
|||
if (nums.length > 2) { |
|||
return iteratorOperation(nums, times); |
|||
} |
|||
|
|||
const [num1, num2] = nums; |
|||
const num1Changed = float2Fixed(num1); |
|||
const num2Changed = float2Fixed(num2); |
|||
const baseNum = digitLength(num1) + digitLength(num2); |
|||
const leftValue = num1Changed * num2Changed; |
|||
|
|||
checkBoundary(leftValue); |
|||
|
|||
return leftValue / Math.pow(10, baseNum); |
|||
} |
|||
|
|||
/** |
|||
* 高精度加法 |
|||
* @export |
|||
*/ |
|||
export function plus(...nums) { |
|||
if (nums.length > 2) { |
|||
return iteratorOperation(nums, plus); |
|||
} |
|||
|
|||
const [num1, num2] = nums; |
|||
// 取最大的小数位
|
|||
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); |
|||
// 把小数都转为整数然后再计算
|
|||
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; |
|||
} |
|||
|
|||
/** |
|||
* 高精度减法 |
|||
* @export |
|||
*/ |
|||
export function minus(...nums) { |
|||
if (nums.length > 2) { |
|||
return iteratorOperation(nums, minus); |
|||
} |
|||
|
|||
const [num1, num2] = nums; |
|||
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2))); |
|||
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; |
|||
} |
|||
|
|||
/** |
|||
* 高精度除法 |
|||
* @export |
|||
*/ |
|||
export function divide(...nums) { |
|||
if (nums.length > 2) { |
|||
return iteratorOperation(nums, divide); |
|||
} |
|||
|
|||
const [num1, num2] = nums; |
|||
const num1Changed = float2Fixed(num1); |
|||
const num2Changed = float2Fixed(num2); |
|||
checkBoundary(num1Changed); |
|||
checkBoundary(num2Changed); |
|||
// 重要,这里必须用strip进行修正
|
|||
return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1)))); |
|||
} |
|||
|
|||
/** |
|||
* 四舍五入 |
|||
* @export |
|||
*/ |
|||
export function round(num, ratio) { |
|||
const base = Math.pow(10, ratio); |
|||
let result = divide(Math.round(Math.abs(times(num, base))), base); |
|||
if (num < 0 && result !== 0) { |
|||
result = times(result, -1); |
|||
} |
|||
// 位数不足则补0
|
|||
return result; |
|||
} |
|||
|
|||
/** |
|||
* 是否进行边界检查,默认开启 |
|||
* @param flag 标记开关,true 为开启,false 为关闭,默认为 true |
|||
* @export |
|||
*/ |
|||
export function enableBoundaryChecking(flag = true) { |
|||
_boundaryCheckingState = flag; |
|||
} |
|||
|
|||
|
|||
export default { |
|||
times, |
|||
plus, |
|||
minus, |
|||
divide, |
|||
round, |
|||
enableBoundaryChecking, |
|||
}; |
|||
|
|||
@ -0,0 +1,734 @@ |
|||
import { number, empty } from './test.js' |
|||
import { round } from './digit.js' |
|||
/** |
|||
* @description 如果value小于min,取min;如果value大于max,取max |
|||
* @param {number} min |
|||
* @param {number} max |
|||
* @param {number} value |
|||
*/ |
|||
function range(min = 0, max = 0, value = 0) { |
|||
return Math.max(min, Math.min(max, Number(value))) |
|||
} |
|||
|
|||
/** |
|||
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换 |
|||
* @param {number|string} value 用户传递值的px值 |
|||
* @param {boolean} unit |
|||
* @returns {number|string} |
|||
*/ |
|||
function getPx(value, unit = false) { |
|||
if (number(value)) { |
|||
return unit ? `${value}px` : Number(value) |
|||
} |
|||
// 如果带有rpx,先取出其数值部分,再转为px值
|
|||
if (/(rpx|upx)$/.test(value)) { |
|||
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value))) |
|||
} |
|||
return unit ? `${parseInt(value)}px` : parseInt(value) |
|||
} |
|||
|
|||
/** |
|||
* @description 进行延时,以达到可以简写代码的目的 比如: await uni.$uv.sleep(20)将会阻塞20ms |
|||
* @param {number} value 堵塞时间 单位ms 毫秒 |
|||
* @returns {Promise} 返回promise |
|||
*/ |
|||
function sleep(value = 30) { |
|||
return new Promise((resolve) => { |
|||
setTimeout(() => { |
|||
resolve() |
|||
}, value) |
|||
}) |
|||
} |
|||
/** |
|||
* @description 运行期判断平台 |
|||
* @returns {string} 返回所在平台(小写) |
|||
* @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
|
|||
*/ |
|||
function os() { |
|||
return uni.getSystemInfoSync().platform.toLowerCase() |
|||
} |
|||
/** |
|||
* @description 获取系统信息同步接口 |
|||
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
|
|||
*/ |
|||
function sys() { |
|||
return uni.getSystemInfoSync() |
|||
} |
|||
|
|||
/** |
|||
* @description 取一个区间数 |
|||
* @param {Number} min 最小值 |
|||
* @param {Number} max 最大值 |
|||
*/ |
|||
function random(min, max) { |
|||
if (min >= 0 && max > 0 && max >= min) { |
|||
const gab = max - min + 1 |
|||
return Math.floor(Math.random() * gab + min) |
|||
} |
|||
return 0 |
|||
} |
|||
|
|||
/** |
|||
* @param {Number} len uuid的长度 |
|||
* @param {Boolean} firstU 将返回的首字母置为"u" |
|||
* @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制 |
|||
*/ |
|||
function guid(len = 32, firstU = true, radix = null) { |
|||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('') |
|||
const uuid = [] |
|||
radix = radix || chars.length |
|||
|
|||
if (len) { |
|||
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
|
|||
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix] |
|||
} else { |
|||
let r |
|||
// rfc4122标准要求返回的uuid中,某些位为固定的字符
|
|||
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-' |
|||
uuid[14] = '4' |
|||
|
|||
for (let i = 0; i < 36; i++) { |
|||
if (!uuid[i]) { |
|||
r = 0 | Math.random() * 16 |
|||
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r] |
|||
} |
|||
} |
|||
} |
|||
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
|
|||
if (firstU) { |
|||
uuid.shift() |
|||
return `u${uuid.join('')}` |
|||
} |
|||
return uuid.join('') |
|||
} |
|||
|
|||
/** |
|||
* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法 |
|||
this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx |
|||
这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name |
|||
值(默认为undefined),就是查找最顶层的$parent |
|||
* @param {string|undefined} name 父组件的参数名 |
|||
*/ |
|||
function $parent(name = undefined) { |
|||
let parent = this.$parent |
|||
// 通过while历遍,这里主要是为了H5需要多层解析的问题
|
|||
while (parent) { |
|||
// 父组件
|
|||
if (parent.$options && parent.$options.name !== name) { |
|||
// 如果组件的name不相等,继续上一级寻找
|
|||
parent = parent.$parent |
|||
} else { |
|||
return parent |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* @description 样式转换 |
|||
* 对象转字符串,或者字符串转对象 |
|||
* @param {object | string} customStyle 需要转换的目标 |
|||
* @param {String} target 转换的目的,object-转为对象,string-转为字符串 |
|||
* @returns {object|string} |
|||
*/ |
|||
function addStyle(customStyle, target = 'object') { |
|||
// 字符串转字符串,对象转对象情形,直接返回
|
|||
if (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' && |
|||
typeof(customStyle) === 'string') { |
|||
return customStyle |
|||
} |
|||
// 字符串转对象
|
|||
if (target === 'object') { |
|||
// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
|
|||
customStyle = trim(customStyle) |
|||
// 根据";"将字符串转为数组形式
|
|||
const styleArray = customStyle.split(';') |
|||
const style = {} |
|||
// 历遍数组,拼接成对象
|
|||
for (let i = 0; i < styleArray.length; i++) { |
|||
// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
|
|||
if (styleArray[i]) { |
|||
const item = styleArray[i].split(':') |
|||
style[trim(item[0])] = trim(item[1]) |
|||
} |
|||
} |
|||
return style |
|||
} |
|||
// 这里为对象转字符串形式
|
|||
let string = '' |
|||
for (const i in customStyle) { |
|||
// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
|
|||
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase() |
|||
string += `${key}:${customStyle[i]};` |
|||
} |
|||
// 去除两端空格
|
|||
return trim(string) |
|||
} |
|||
|
|||
/** |
|||
* @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾 |
|||
* @param {string|number} value 需要添加单位的值 |
|||
* @param {string} unit 添加的单位名 比如px |
|||
*/ |
|||
function addUnit(value = 'auto', unit = uni?.$uv?.config?.unit ? uni?.$uv?.config?.unit : 'px') { |
|||
value = String(value) |
|||
// 用uvui内置验证规则中的number判断是否为数值
|
|||
return number(value) ? `${value}${unit}` : value |
|||
} |
|||
|
|||
/** |
|||
* @description 深度克隆 |
|||
* @param {object} obj 需要深度克隆的对象 |
|||
* @param cache 缓存 |
|||
* @returns {*} 克隆后的对象或者原值(不是对象) |
|||
*/ |
|||
function deepClone(obj, cache = new WeakMap()) { |
|||
if (obj === null || typeof obj !== 'object') return obj; |
|||
if (cache.has(obj)) return cache.get(obj); |
|||
let clone; |
|||
if (obj instanceof Date) { |
|||
clone = new Date(obj.getTime()); |
|||
} else if (obj instanceof RegExp) { |
|||
clone = new RegExp(obj); |
|||
} else if (obj instanceof Map) { |
|||
clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)])); |
|||
} else if (obj instanceof Set) { |
|||
clone = new Set(Array.from(obj, value => deepClone(value, cache))); |
|||
} else if (Array.isArray(obj)) { |
|||
clone = obj.map(value => deepClone(value, cache)); |
|||
} else if (Object.prototype.toString.call(obj) === '[object Object]') { |
|||
clone = Object.create(Object.getPrototypeOf(obj)); |
|||
cache.set(obj, clone); |
|||
for (const [key, value] of Object.entries(obj)) { |
|||
clone[key] = deepClone(value, cache); |
|||
} |
|||
} else { |
|||
clone = Object.assign({}, obj); |
|||
} |
|||
cache.set(obj, clone); |
|||
return clone; |
|||
} |
|||
|
|||
/** |
|||
* @description JS对象深度合并 |
|||
* @param {object} target 需要拷贝的对象 |
|||
* @param {object} source 拷贝的来源对象 |
|||
* @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象) |
|||
*/ |
|||
function deepMerge(target = {}, source = {}) { |
|||
target = deepClone(target) |
|||
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target; |
|||
const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target); |
|||
for (const prop in source) { |
|||
if (!source.hasOwnProperty(prop)) continue; |
|||
const sourceValue = source[prop]; |
|||
const targetValue = merged[prop]; |
|||
if (sourceValue instanceof Date) { |
|||
merged[prop] = new Date(sourceValue); |
|||
} else if (sourceValue instanceof RegExp) { |
|||
merged[prop] = new RegExp(sourceValue); |
|||
} else if (sourceValue instanceof Map) { |
|||
merged[prop] = new Map(sourceValue); |
|||
} else if (sourceValue instanceof Set) { |
|||
merged[prop] = new Set(sourceValue); |
|||
} else if (typeof sourceValue === 'object' && sourceValue !== null) { |
|||
merged[prop] = deepMerge(targetValue, sourceValue); |
|||
} else { |
|||
merged[prop] = sourceValue; |
|||
} |
|||
} |
|||
return merged; |
|||
} |
|||
|
|||
/** |
|||
* @description error提示 |
|||
* @param {*} err 错误内容 |
|||
*/ |
|||
function error(err) { |
|||
// 开发环境才提示,生产环境不会提示
|
|||
if (process.env.NODE_ENV === 'development') { |
|||
console.error(`uvui提示:${err}`) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description 打乱数组 |
|||
* @param {array} array 需要打乱的数组 |
|||
* @returns {array} 打乱后的数组 |
|||
*/ |
|||
function randomArray(array = []) { |
|||
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
|
|||
return array.sort(() => Math.random() - 0.5) |
|||
} |
|||
|
|||
// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
|
|||
// 所以这里做一个兼容polyfill的兼容处理
|
|||
if (!String.prototype.padStart) { |
|||
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
|
|||
String.prototype.padStart = function(maxLength, fillString = ' ') { |
|||
if (Object.prototype.toString.call(fillString) !== '[object String]') { |
|||
throw new TypeError( |
|||
'fillString must be String' |
|||
) |
|||
} |
|||
const str = this |
|||
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
|
|||
if (str.length >= maxLength) return String(str) |
|||
|
|||
const fillLength = maxLength - str.length |
|||
let times = Math.ceil(fillLength / fillString.length) |
|||
while (times >>= 1) { |
|||
fillString += fillString |
|||
if (times === 1) { |
|||
fillString += fillString |
|||
} |
|||
} |
|||
return fillString.slice(0, fillLength) + str |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description 格式化时间 |
|||
* @param {String|Number} dateTime 需要格式化的时间戳 |
|||
* @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd |
|||
* @returns {string} 返回格式化后的字符串 |
|||
*/ |
|||
function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') { |
|||
let date |
|||
// 若传入时间为假值,则取当前时间
|
|||
if (!dateTime) { |
|||
date = new Date() |
|||
} |
|||
// 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
|
|||
else if (/^\d{10}$/.test(dateTime?.toString().trim())) { |
|||
date = new Date(dateTime * 1000) |
|||
} |
|||
// 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
|
|||
else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) { |
|||
date = new Date(Number(dateTime)) |
|||
} |
|||
// 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
|
|||
// 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
|
|||
else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) { |
|||
date = new Date(dateTime.replace(/-/g, '/')) |
|||
} |
|||
// 其他都认为符合 RFC 2822 规范
|
|||
else { |
|||
date = new Date(dateTime) |
|||
} |
|||
|
|||
const timeSource = { |
|||
'y': date.getFullYear().toString(), // 年
|
|||
'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
|
|||
'd': date.getDate().toString().padStart(2, '0'), // 日
|
|||
'h': date.getHours().toString().padStart(2, '0'), // 时
|
|||
'M': date.getMinutes().toString().padStart(2, '0'), // 分
|
|||
's': date.getSeconds().toString().padStart(2, '0') // 秒
|
|||
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|||
} |
|||
|
|||
for (const key in timeSource) { |
|||
const [ret] = new RegExp(`${key}+`).exec(formatStr) || [] |
|||
if (ret) { |
|||
// 年可能只需展示两位
|
|||
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0 |
|||
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex)) |
|||
} |
|||
} |
|||
|
|||
return formatStr |
|||
} |
|||
|
|||
/** |
|||
* @description 时间戳转为多久之前 |
|||
* @param {String|Number} timestamp 时间戳 |
|||
* @param {String|Boolean} format |
|||
* 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式; |
|||
* 如果为布尔值false,无论什么时间,都返回多久以前的格式 |
|||
* @returns {string} 转化后的内容 |
|||
*/ |
|||
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') { |
|||
if (timestamp == null) timestamp = Number(new Date()) |
|||
timestamp = parseInt(timestamp) |
|||
// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
|
|||
if (timestamp.toString().length == 10) timestamp *= 1000 |
|||
let timer = (new Date()).getTime() - timestamp |
|||
timer = parseInt(timer / 1000) |
|||
// 如果小于5分钟,则返回"刚刚",其他以此类推
|
|||
let tips = '' |
|||
switch (true) { |
|||
case timer < 300: |
|||
tips = '刚刚' |
|||
break |
|||
case timer >= 300 && timer < 3600: |
|||
tips = `${parseInt(timer / 60)}分钟前` |
|||
break |
|||
case timer >= 3600 && timer < 86400: |
|||
tips = `${parseInt(timer / 3600)}小时前` |
|||
break |
|||
case timer >= 86400 && timer < 2592000: |
|||
tips = `${parseInt(timer / 86400)}天前` |
|||
break |
|||
default: |
|||
// 如果format为false,则无论什么时间戳,都显示xx之前
|
|||
if (format === false) { |
|||
if (timer >= 2592000 && timer < 365 * 86400) { |
|||
tips = `${parseInt(timer / (86400 * 30))}个月前` |
|||
} else { |
|||
tips = `${parseInt(timer / (86400 * 365))}年前` |
|||
} |
|||
} else { |
|||
tips = timeFormat(timestamp, format) |
|||
} |
|||
} |
|||
return tips |
|||
} |
|||
|
|||
/** |
|||
* @description 去除空格 |
|||
* @param String str 需要去除空格的字符串 |
|||
* @param String pos both(左右)|left|right|all 默认both |
|||
*/ |
|||
function trim(str, pos = 'both') { |
|||
str = String(str) |
|||
if (pos == 'both') { |
|||
return str.replace(/^\s+|\s+$/g, '') |
|||
} |
|||
if (pos == 'left') { |
|||
return str.replace(/^\s*/, '') |
|||
} |
|||
if (pos == 'right') { |
|||
return str.replace(/(\s*$)/g, '') |
|||
} |
|||
if (pos == 'all') { |
|||
return str.replace(/\s+/g, '') |
|||
} |
|||
return str |
|||
} |
|||
|
|||
/** |
|||
* @description 对象转url参数 |
|||
* @param {object} data,对象 |
|||
* @param {Boolean} isPrefix,是否自动加上"?" |
|||
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma |
|||
*/ |
|||
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') { |
|||
const prefix = isPrefix ? '?' : '' |
|||
const _result = [] |
|||
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets' |
|||
for (const key in data) { |
|||
const value = data[key] |
|||
// 去掉为空的参数
|
|||
if (['', undefined, null].indexOf(value) >= 0) { |
|||
continue |
|||
} |
|||
// 如果值为数组,另行处理
|
|||
if (value.constructor === Array) { |
|||
// e.g. {ids: [1, 2, 3]}
|
|||
switch (arrayFormat) { |
|||
case 'indices': |
|||
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
|
|||
for (let i = 0; i < value.length; i++) { |
|||
_result.push(`${key}[${i}]=${value[i]}`) |
|||
} |
|||
break |
|||
case 'brackets': |
|||
// 结果: ids[]=1&ids[]=2&ids[]=3
|
|||
value.forEach((_value) => { |
|||
_result.push(`${key}[]=${_value}`) |
|||
}) |
|||
break |
|||
case 'repeat': |
|||
// 结果: ids=1&ids=2&ids=3
|
|||
value.forEach((_value) => { |
|||
_result.push(`${key}=${_value}`) |
|||
}) |
|||
break |
|||
case 'comma': |
|||
// 结果: ids=1,2,3
|
|||
let commaStr = '' |
|||
value.forEach((_value) => { |
|||
commaStr += (commaStr ? ',' : '') + _value |
|||
}) |
|||
_result.push(`${key}=${commaStr}`) |
|||
break |
|||
default: |
|||
value.forEach((_value) => { |
|||
_result.push(`${key}[]=${_value}`) |
|||
}) |
|||
} |
|||
} else { |
|||
_result.push(`${key}=${value}`) |
|||
} |
|||
} |
|||
return _result.length ? prefix + _result.join('&') : '' |
|||
} |
|||
|
|||
/** |
|||
* 显示消息提示框 |
|||
* @param {String} title 提示的内容,长度与 icon 取值有关。 |
|||
* @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000 |
|||
*/ |
|||
function toast(title, duration = 2000) { |
|||
uni.showToast({ |
|||
title: String(title), |
|||
icon: 'none', |
|||
duration |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* @description 根据主题type值,获取对应的图标 |
|||
* @param {String} type 主题名称,primary|info|error|warning|success |
|||
* @param {boolean} fill 是否使用fill填充实体的图标 |
|||
*/ |
|||
function type2icon(type = 'success', fill = false) { |
|||
// 如果非预置值,默认为success
|
|||
if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success' |
|||
let iconName = '' |
|||
// 目前(2019-12-12),info和primary使用同一个图标
|
|||
switch (type) { |
|||
case 'primary': |
|||
iconName = 'info-circle' |
|||
break |
|||
case 'info': |
|||
iconName = 'info-circle' |
|||
break |
|||
case 'error': |
|||
iconName = 'close-circle' |
|||
break |
|||
case 'warning': |
|||
iconName = 'error-circle' |
|||
break |
|||
case 'success': |
|||
iconName = 'checkmark-circle' |
|||
break |
|||
default: |
|||
iconName = 'checkmark-circle' |
|||
} |
|||
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
|
|||
if (fill) iconName += '-fill' |
|||
return iconName |
|||
} |
|||
|
|||
/** |
|||
* @description 数字格式化 |
|||
* @param {number|string} number 要格式化的数字 |
|||
* @param {number} decimals 保留几位小数 |
|||
* @param {string} decimalPoint 小数点符号 |
|||
* @param {string} thousandsSeparator 千分位符号 |
|||
* @returns {string} 格式化后的数字 |
|||
*/ |
|||
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') { |
|||
number = (`${number}`).replace(/[^0-9+-Ee.]/g, '') |
|||
const n = !isFinite(+number) ? 0 : +number |
|||
const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals) |
|||
const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator |
|||
const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint |
|||
let s = '' |
|||
|
|||
s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.') |
|||
const re = /(-?\d+)(\d{3})/ |
|||
while (re.test(s[0])) { |
|||
s[0] = s[0].replace(re, `$1${sep}$2`) |
|||
} |
|||
|
|||
if ((s[1] || '').length < prec) { |
|||
s[1] = s[1] || '' |
|||
s[1] += new Array(prec - s[1].length + 1).join('0') |
|||
} |
|||
return s.join(dec) |
|||
} |
|||
|
|||
/** |
|||
* @description 获取duration值 |
|||
* 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位 |
|||
* 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画 |
|||
* @param {String|number} value 比如: "1s"|"100ms"|1|100 |
|||
* @param {boolean} unit 提示: 如果是false 默认返回number |
|||
* @return {string|number} |
|||
*/ |
|||
function getDuration(value, unit = true) { |
|||
const valueNum = parseInt(value) |
|||
if (unit) { |
|||
if (/s$/.test(value)) return value |
|||
return value > 30 ? `${value}ms` : `${value}s` |
|||
} |
|||
if (/ms$/.test(value)) return valueNum |
|||
if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000 |
|||
return valueNum |
|||
} |
|||
|
|||
/** |
|||
* @description 日期的月或日补零操作 |
|||
* @param {String} value 需要补零的值 |
|||
*/ |
|||
function padZero(value) { |
|||
return `00${value}`.slice(-2) |
|||
} |
|||
|
|||
/** |
|||
* @description 在uv-form的子组件内容发生变化,或者失去焦点时,尝试通知uv-form执行校验方法 |
|||
* @param {*} instance |
|||
* @param {*} event |
|||
*/ |
|||
function formValidate(instance, event) { |
|||
const formItem = $parent.call(instance, 'uv-form-item') |
|||
const form = $parent.call(instance, 'uv-form') |
|||
// 如果发生变化的input或者textarea等,其父组件中有uv-form-item或者uv-form等,就执行form的validate方法
|
|||
// 同时将form-item的pros传递给form,让其进行精确对象验证
|
|||
if (formItem && form) { |
|||
form.validateField(formItem.prop, () => {}, event) |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式 |
|||
* @param {object} obj 对象 |
|||
* @param {string} key 需要获取的属性字段 |
|||
* @returns {*} |
|||
*/ |
|||
function getProperty(obj, key) { |
|||
if (!obj) { |
|||
return |
|||
} |
|||
if (typeof key !== 'string' || key === '') { |
|||
return '' |
|||
} |
|||
if (key.indexOf('.') !== -1) { |
|||
const keys = key.split('.') |
|||
let firstObj = obj[keys[0]] || {} |
|||
|
|||
for (let i = 1; i < keys.length; i++) { |
|||
if (firstObj) { |
|||
firstObj = firstObj[keys[i]] |
|||
} |
|||
} |
|||
return firstObj |
|||
} |
|||
return obj[key] |
|||
} |
|||
|
|||
/** |
|||
* @description 设置对象的属性值,如果'a.b.c'的形式进行设置 |
|||
* @param {object} obj 对象 |
|||
* @param {string} key 需要设置的属性 |
|||
* @param {string} value 设置的值 |
|||
*/ |
|||
function setProperty(obj, key, value) { |
|||
if (!obj) { |
|||
return |
|||
} |
|||
// 递归赋值
|
|||
const inFn = function(_obj, keys, v) { |
|||
// 最后一个属性key
|
|||
if (keys.length === 1) { |
|||
_obj[keys[0]] = v |
|||
return |
|||
} |
|||
// 0~length-1个key
|
|||
while (keys.length > 1) { |
|||
const k = keys[0] |
|||
if (!_obj[k] || (typeof _obj[k] !== 'object')) { |
|||
_obj[k] = {} |
|||
} |
|||
const key = keys.shift() |
|||
// 自调用判断是否存在属性,不存在则自动创建对象
|
|||
inFn(_obj[k], keys, v) |
|||
} |
|||
} |
|||
|
|||
if (typeof key !== 'string' || key === '') { |
|||
|
|||
} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
|
|||
const keys = key.split('.') |
|||
inFn(obj, keys, value) |
|||
} else { |
|||
obj[key] = value |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @description 获取当前页面路径 |
|||
*/ |
|||
function page() { |
|||
const pages = getCurrentPages(); |
|||
const route = pages[pages.length - 1]?.route; |
|||
// 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
|
|||
return `/${route ? route : ''}` |
|||
} |
|||
|
|||
/** |
|||
* @description 获取当前路由栈实例数组 |
|||
*/ |
|||
function pages() { |
|||
const pages = getCurrentPages() |
|||
return pages |
|||
} |
|||
|
|||
/** |
|||
* 获取页面历史栈指定层实例 |
|||
* @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。 |
|||
*/ |
|||
function getHistoryPage(back = 0) { |
|||
const pages = getCurrentPages() |
|||
const len = pages.length |
|||
return pages[len - 1 + back] |
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* @description 修改uvui内置属性值 |
|||
* @param {object} props 修改内置props属性 |
|||
* @param {object} config 修改内置config属性 |
|||
* @param {object} color 修改内置color属性 |
|||
* @param {object} zIndex 修改内置zIndex属性 |
|||
*/ |
|||
function setConfig({ |
|||
props = {}, |
|||
config = {}, |
|||
color = {}, |
|||
zIndex = {} |
|||
}) { |
|||
const { |
|||
deepMerge, |
|||
} = uni.$uv |
|||
uni.$uv.config = deepMerge(uni.$uv.config, config) |
|||
uni.$uv.props = deepMerge(uni.$uv.props, props) |
|||
uni.$uv.color = deepMerge(uni.$uv.color, color) |
|||
uni.$uv.zIndex = deepMerge(uni.$uv.zIndex, zIndex) |
|||
} |
|||
|
|||
export { |
|||
range, |
|||
getPx, |
|||
sleep, |
|||
os, |
|||
sys, |
|||
random, |
|||
guid, |
|||
$parent, |
|||
addStyle, |
|||
addUnit, |
|||
deepClone, |
|||
deepMerge, |
|||
error, |
|||
randomArray, |
|||
timeFormat, |
|||
timeFrom, |
|||
trim, |
|||
queryParams, |
|||
toast, |
|||
type2icon, |
|||
priceFormat, |
|||
getDuration, |
|||
padZero, |
|||
formValidate, |
|||
getProperty, |
|||
setProperty, |
|||
page, |
|||
pages, |
|||
getHistoryPage, |
|||
setConfig |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
/** |
|||
* 注意: |
|||
* 此部分内容,在vue-cli模式下,需要在vue.config.js加入如下内容才有效: |
|||
* module.exports = { |
|||
* transpileDependencies: ['uview-v2'] |
|||
* } |
|||
*/ |
|||
|
|||
let platform = 'none' |
|||
|
|||
// #ifdef VUE3
|
|||
platform = 'vue3' |
|||
// #endif
|
|||
|
|||
// #ifdef VUE2
|
|||
platform = 'vue2' |
|||
// #endif
|
|||
|
|||
// #ifdef APP-PLUS
|
|||
platform = 'plus' |
|||
// #endif
|
|||
|
|||
// #ifdef APP-NVUE
|
|||
platform = 'nvue' |
|||
// #endif
|
|||
|
|||
// #ifdef H5
|
|||
platform = 'h5' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-WEIXIN
|
|||
platform = 'weixin' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-ALIPAY
|
|||
platform = 'alipay' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-BAIDU
|
|||
platform = 'baidu' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-TOUTIAO
|
|||
platform = 'toutiao' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-QQ
|
|||
platform = 'qq' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-KUAISHOU
|
|||
platform = 'kuaishou' |
|||
// #endif
|
|||
|
|||
// #ifdef MP-360
|
|||
platform = '360' |
|||
// #endif
|
|||
|
|||
// #ifdef MP
|
|||
platform = 'mp' |
|||
// #endif
|
|||
|
|||
// #ifdef QUICKAPP-WEBVIEW
|
|||
platform = 'quickapp-webview' |
|||
// #endif
|
|||
|
|||
// #ifdef QUICKAPP-WEBVIEW-HUAWEI
|
|||
platform = 'quickapp-webview-huawei' |
|||
// #endif
|
|||
|
|||
// #ifdef QUICKAPP-WEBVIEW-UNION
|
|||
platform = 'quckapp-webview-union' |
|||
// #endif
|
|||
|
|||
export default platform |
|||
@ -0,0 +1,287 @@ |
|||
/** |
|||
* 验证电子邮箱格式 |
|||
*/ |
|||
function email(value) { |
|||
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证手机格式 |
|||
*/ |
|||
function mobile(value) { |
|||
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证URL格式 |
|||
*/ |
|||
function url(value) { |
|||
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/ |
|||
.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证日期格式 |
|||
*/ |
|||
function date(value) { |
|||
if (!value) return false |
|||
// 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
|
|||
if (number(value)) value = +value |
|||
return !/Invalid|NaN/.test(new Date(value).toString()) |
|||
} |
|||
|
|||
/** |
|||
* 验证ISO类型的日期格式 |
|||
*/ |
|||
function dateISO(value) { |
|||
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证十进制数字 |
|||
*/ |
|||
function number(value) { |
|||
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证字符串 |
|||
*/ |
|||
function string(value) { |
|||
return typeof value === 'string' |
|||
} |
|||
|
|||
/** |
|||
* 验证整数 |
|||
*/ |
|||
function digits(value) { |
|||
return /^\d+$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证身份证号码 |
|||
*/ |
|||
function idCard(value) { |
|||
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test( |
|||
value |
|||
) |
|||
} |
|||
|
|||
/** |
|||
* 是否车牌号 |
|||
*/ |
|||
function carNo(value) { |
|||
// 新能源车牌
|
|||
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/ |
|||
// 旧车牌
|
|||
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/ |
|||
if (value.length === 7) { |
|||
return creg.test(value) |
|||
} if (value.length === 8) { |
|||
return xreg.test(value) |
|||
} |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 金额,只允许2位小数 |
|||
*/ |
|||
function amount(value) { |
|||
// 金额,只允许保留两位小数
|
|||
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 中文 |
|||
*/ |
|||
function chinese(value) { |
|||
const reg = /^[\u4e00-\u9fa5]+$/gi |
|||
return reg.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 只能输入字母 |
|||
*/ |
|||
function letter(value) { |
|||
return /^[a-zA-Z]*$/.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 只能是字母或者数字 |
|||
*/ |
|||
function enOrNum(value) { |
|||
// 英文或者数字
|
|||
const reg = /^[0-9a-zA-Z]*$/g |
|||
return reg.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 验证是否包含某个值 |
|||
*/ |
|||
function contains(value, param) { |
|||
return value.indexOf(param) >= 0 |
|||
} |
|||
|
|||
/** |
|||
* 验证一个值范围[min, max] |
|||
*/ |
|||
function range(value, param) { |
|||
return value >= param[0] && value <= param[1] |
|||
} |
|||
|
|||
/** |
|||
* 验证一个长度范围[min, max] |
|||
*/ |
|||
function rangeLength(value, param) { |
|||
return value.length >= param[0] && value.length <= param[1] |
|||
} |
|||
|
|||
/** |
|||
* 是否固定电话 |
|||
*/ |
|||
function landline(value) { |
|||
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/ |
|||
return reg.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 判断是否为空 |
|||
*/ |
|||
function empty(value) { |
|||
switch (typeof value) { |
|||
case 'undefined': |
|||
return true |
|||
case 'string': |
|||
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true |
|||
break |
|||
case 'boolean': |
|||
if (!value) return true |
|||
break |
|||
case 'number': |
|||
if (value === 0 || isNaN(value)) return true |
|||
break |
|||
case 'object': |
|||
if (value === null || value.length === 0) return true |
|||
for (const i in value) { |
|||
return false |
|||
} |
|||
return true |
|||
} |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 是否json字符串 |
|||
*/ |
|||
function jsonString(value) { |
|||
if (typeof value === 'string') { |
|||
try { |
|||
const obj = JSON.parse(value) |
|||
if (typeof obj === 'object' && obj) { |
|||
return true |
|||
} |
|||
return false |
|||
} catch (e) { |
|||
return false |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
/** |
|||
* 是否数组 |
|||
*/ |
|||
function array(value) { |
|||
if (typeof Array.isArray === 'function') { |
|||
return Array.isArray(value) |
|||
} |
|||
return Object.prototype.toString.call(value) === '[object Array]' |
|||
} |
|||
|
|||
/** |
|||
* 是否对象 |
|||
*/ |
|||
function object(value) { |
|||
return Object.prototype.toString.call(value) === '[object Object]' |
|||
} |
|||
|
|||
/** |
|||
* 是否短信验证码 |
|||
*/ |
|||
function code(value, len = 6) { |
|||
return new RegExp(`^\\d{${len}}$`).test(value) |
|||
} |
|||
|
|||
/** |
|||
* 是否函数方法 |
|||
* @param {Object} value |
|||
*/ |
|||
function func(value) { |
|||
return typeof value === 'function' |
|||
} |
|||
|
|||
/** |
|||
* 是否promise对象 |
|||
* @param {Object} value |
|||
*/ |
|||
function promise(value) { |
|||
return object(value) && func(value.then) && func(value.catch) |
|||
} |
|||
|
|||
/** 是否图片格式 |
|||
* @param {Object} value |
|||
*/ |
|||
function image(value) { |
|||
const newValue = value.split('?')[0] |
|||
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i |
|||
return IMAGE_REGEXP.test(newValue) |
|||
} |
|||
|
|||
/** |
|||
* 是否视频格式 |
|||
* @param {Object} value |
|||
*/ |
|||
function video(value) { |
|||
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i |
|||
return VIDEO_REGEXP.test(value) |
|||
} |
|||
|
|||
/** |
|||
* 是否为正则对象 |
|||
* @param {Object} |
|||
* @return {Boolean} |
|||
*/ |
|||
function regExp(o) { |
|||
return o && Object.prototype.toString.call(o) === '[object RegExp]' |
|||
} |
|||
|
|||
export { |
|||
email, |
|||
mobile, |
|||
url, |
|||
date, |
|||
dateISO, |
|||
number, |
|||
digits, |
|||
idCard, |
|||
carNo, |
|||
amount, |
|||
chinese, |
|||
letter, |
|||
enOrNum, |
|||
contains, |
|||
range, |
|||
rangeLength, |
|||
empty, |
|||
jsonString, |
|||
landline, |
|||
object, |
|||
array, |
|||
code, |
|||
func, |
|||
promise, |
|||
video, |
|||
image, |
|||
regExp, |
|||
string |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
let timer; let |
|||
flag |
|||
/** |
|||
* 节流原理:在一定时间内,只能触发一次 |
|||
* |
|||
* @param {Function} func 要执行的回调函数 |
|||
* @param {Number} wait 延时的时间 |
|||
* @param {Boolean} immediate 是否立即执行 |
|||
* @return null |
|||
*/ |
|||
function throttle(func, wait = 500, immediate = true) { |
|||
if (immediate) { |
|||
if (!flag) { |
|||
flag = true |
|||
// 如果是立即执行,则在wait毫秒内开始时执行
|
|||
typeof func === 'function' && func() |
|||
timer = setTimeout(() => { |
|||
flag = false |
|||
}, wait) |
|||
} |
|||
} else if (!flag) { |
|||
flag = true |
|||
// 如果是非立即执行,则在wait毫秒内的结束处执行
|
|||
timer = setTimeout(() => { |
|||
flag = false |
|||
typeof func === 'function' && func() |
|||
}, wait) |
|||
} |
|||
} |
|||
export default throttle |
|||
@ -0,0 +1,132 @@ |
|||
import buildURL from '../helpers/buildURL' |
|||
import buildFullPath from '../core/buildFullPath' |
|||
import settle from '../core/settle' |
|||
import {isUndefined} from "../utils" |
|||
|
|||
/** |
|||
* 返回可选值存在的配置 |
|||
* @param {Array} keys - 可选值数组 |
|||
* @param {Object} config2 - 配置 |
|||
* @return {{}} - 存在的配置项 |
|||
*/ |
|||
const mergeKeys = (keys, config2) => { |
|||
let config = {} |
|||
keys.forEach(prop => { |
|||
if (!isUndefined(config2[prop])) { |
|||
config[prop] = config2[prop] |
|||
} |
|||
}) |
|||
return config |
|||
} |
|||
export default (config) => { |
|||
return new Promise((resolve, reject) => { |
|||
let fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params, config.paramsSerializer) |
|||
const _config = { |
|||
url: fullPath, |
|||
header: config.header, |
|||
complete: (response) => { |
|||
config.fullPath = fullPath |
|||
response.config = config |
|||
response.rawData = response.data |
|||
try { |
|||
let jsonParseHandle = false |
|||
const forcedJSONParsingType = typeof config.forcedJSONParsing |
|||
if (forcedJSONParsingType === 'boolean') { |
|||
jsonParseHandle = config.forcedJSONParsing |
|||
} else if (forcedJSONParsingType === 'object') { |
|||
const includesMethod = config.forcedJSONParsing.include || [] |
|||
jsonParseHandle = includesMethod.includes(config.method) |
|||
} |
|||
|
|||
// 对可能字符串不是json 的情况容错
|
|||
if (jsonParseHandle && typeof response.data === 'string') { |
|||
response.data = JSON.parse(response.data) |
|||
} |
|||
// eslint-disable-next-line no-empty
|
|||
} catch (e) { |
|||
} |
|||
settle(resolve, reject, response) |
|||
} |
|||
} |
|||
let requestTask |
|||
if (config.method === 'UPLOAD') { |
|||
delete _config.header['content-type'] |
|||
delete _config.header['Content-Type'] |
|||
let otherConfig = { |
|||
// #ifdef MP-ALIPAY
|
|||
fileType: config.fileType, |
|||
// #endif
|
|||
filePath: config.filePath, |
|||
name: config.name |
|||
} |
|||
const optionalKeys = [ |
|||
// #ifdef APP-PLUS || H5
|
|||
'files', |
|||
// #endif
|
|||
// #ifdef H5
|
|||
'file', |
|||
// #endif
|
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
'timeout', |
|||
// #endif
|
|||
'formData' |
|||
] |
|||
requestTask = uni.uploadFile({..._config, ...otherConfig, ...mergeKeys(optionalKeys, config)}) |
|||
} else if (config.method === 'DOWNLOAD') { |
|||
const optionalKeys = [ |
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
'timeout', |
|||
// #endif
|
|||
// #ifdef MP
|
|||
'filePath', |
|||
// #endif
|
|||
] |
|||
requestTask = uni.downloadFile({..._config, ...mergeKeys(optionalKeys, config)}) |
|||
} else { |
|||
const optionalKeys = [ |
|||
'data', |
|||
'method', |
|||
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
|
|||
'timeout', |
|||
// #endif
|
|||
'dataType', |
|||
// #ifndef MP-ALIPAY
|
|||
'responseType', |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
'sslVerify', |
|||
// #endif
|
|||
// #ifdef H5
|
|||
'withCredentials', |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
'firstIpv4', |
|||
// #endif
|
|||
// #ifdef MP-WEIXIN
|
|||
'enableHttp2', |
|||
'enableQuic', |
|||
// #endif
|
|||
// #ifdef MP-TOUTIAO || MP-WEIXIN
|
|||
'enableCache', |
|||
// #endif
|
|||
// #ifdef MP-WEIXIN
|
|||
'enableHttpDNS', |
|||
'httpDNSServiceId', |
|||
'enableChunked', |
|||
'forceCellularNetwork', |
|||
// #endif
|
|||
// #ifdef MP-ALIPAY
|
|||
'enableCookie', |
|||
// #endif
|
|||
// #ifdef MP-BAIDU
|
|||
'cloudCache', |
|||
'defer' |
|||
// #endif
|
|||
] |
|||
requestTask = uni.request({..._config, ...mergeKeys(optionalKeys, config)}) |
|||
} |
|||
if (config.getTask) { |
|||
config.getTask(requestTask, config) |
|||
} |
|||
}) |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
'use strict' |
|||
|
|||
|
|||
function InterceptorManager() { |
|||
this.handlers = [] |
|||
} |
|||
|
|||
/** |
|||
* Add a new interceptor to the stack |
|||
* |
|||
* @param {Function} fulfilled The function to handle `then` for a `Promise` |
|||
* @param {Function} rejected The function to handle `reject` for a `Promise` |
|||
* |
|||
* @return {Number} An ID used to remove interceptor later |
|||
*/ |
|||
InterceptorManager.prototype.use = function use(fulfilled, rejected) { |
|||
this.handlers.push({ |
|||
fulfilled: fulfilled, |
|||
rejected: rejected |
|||
}) |
|||
return this.handlers.length - 1 |
|||
} |
|||
|
|||
/** |
|||
* Remove an interceptor from the stack |
|||
* |
|||
* @param {Number} id The ID that was returned by `use` |
|||
*/ |
|||
InterceptorManager.prototype.eject = function eject(id) { |
|||
if (this.handlers[id]) { |
|||
this.handlers[id] = null |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Iterate over all the registered interceptors |
|||
* |
|||
* This method is particularly useful for skipping over any |
|||
* interceptors that may have become `null` calling `eject`. |
|||
* |
|||
* @param {Function} fn The function to call for each interceptor |
|||
*/ |
|||
InterceptorManager.prototype.forEach = function forEach(fn) { |
|||
this.handlers.forEach(h => { |
|||
if (h !== null) { |
|||
fn(h) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export default InterceptorManager |
|||
@ -0,0 +1,201 @@ |
|||
/** |
|||
* @Class Request |
|||
* @description luch-request http请求插件 |
|||
* @Author lu-ch |
|||
* @Email webwork.s@qq.com |
|||
* 文档: https://www.quanzhan.co/luch-request/
|
|||
* github: https://github.com/lei-mu/luch-request
|
|||
* DCloud: http://ext.dcloud.net.cn/plugin?id=392
|
|||
*/ |
|||
|
|||
|
|||
import dispatchRequest from './dispatchRequest' |
|||
import InterceptorManager from './InterceptorManager' |
|||
import mergeConfig from './mergeConfig' |
|||
import defaults from './defaults' |
|||
import { isPlainObject } from '../utils' |
|||
import clone from '../utils/clone' |
|||
|
|||
export default class Request { |
|||
/** |
|||
* @param {Object} arg - 全局配置 |
|||
* @param {String} arg.baseURL - 全局根路径 |
|||
* @param {Object} arg.header - 全局header |
|||
* @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式 |
|||
* @param {String} arg.dataType = [json] - 全局默认的dataType |
|||
* @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持 |
|||
* @param {Object} arg.custom - 全局默认的自定义参数 |
|||
* @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序 |
|||
* @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+) |
|||
* @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+) |
|||
* @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+) |
|||
* @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300 |
|||
*/ |
|||
constructor(arg = {}) { |
|||
if (!isPlainObject(arg)) { |
|||
arg = {} |
|||
console.warn('设置全局参数必须接收一个Object') |
|||
} |
|||
this.config = clone({...defaults, ...arg}) |
|||
this.interceptors = { |
|||
request: new InterceptorManager(), |
|||
response: new InterceptorManager() |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @Function |
|||
* @param {Request~setConfigCallback} f - 设置全局默认配置 |
|||
*/ |
|||
setConfig(f) { |
|||
this.config = f(this.config) |
|||
} |
|||
|
|||
middleware(config) { |
|||
config = mergeConfig(this.config, config) |
|||
let chain = [dispatchRequest, undefined] |
|||
let promise = Promise.resolve(config) |
|||
|
|||
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { |
|||
chain.unshift(interceptor.fulfilled, interceptor.rejected) |
|||
}) |
|||
|
|||
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { |
|||
chain.push(interceptor.fulfilled, interceptor.rejected) |
|||
}) |
|||
|
|||
while (chain.length) { |
|||
promise = promise.then(chain.shift(), chain.shift()) |
|||
} |
|||
|
|||
return promise |
|||
} |
|||
|
|||
/** |
|||
* @Function |
|||
* @param {Object} config - 请求配置项 |
|||
* @prop {String} options.url - 请求路径 |
|||
* @prop {Object} options.data - 请求参数 |
|||
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型 |
|||
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse |
|||
* @prop {Object} [options.header = config.header] - 请求header |
|||
* @prop {Object} [options.method = config.method] - 请求方法 |
|||
* @returns {Promise<unknown>} |
|||
*/ |
|||
request(config = {}) { |
|||
return this.middleware(config) |
|||
} |
|||
|
|||
get(url, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
method: 'GET', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
post(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'POST', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #ifndef MP-ALIPAY || MP-KUAISHOU || MP-JD
|
|||
put(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'PUT', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
|
|||
delete(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'DELETE', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
// #ifdef H5 || MP-WEIXIN
|
|||
connect(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'CONNECT', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
// #ifdef H5 || MP-WEIXIN || MP-BAIDU
|
|||
head(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'HEAD', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
|
|||
options(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'OPTIONS', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
// #ifdef H5 || MP-WEIXIN
|
|||
trace(url, data, options = {}) { |
|||
return this.middleware({ |
|||
url, |
|||
data, |
|||
method: 'TRACE', |
|||
...options |
|||
}) |
|||
} |
|||
|
|||
// #endif
|
|||
|
|||
upload(url, config = {}) { |
|||
config.url = url |
|||
config.method = 'UPLOAD' |
|||
return this.middleware(config) |
|||
} |
|||
|
|||
download(url, config = {}) { |
|||
config.url = url |
|||
config.method = 'DOWNLOAD' |
|||
return this.middleware(config) |
|||
} |
|||
|
|||
get version () { |
|||
return '3.1.0' |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* setConfig回调 |
|||
* @return {Object} - 返回操作后的config |
|||
* @callback Request~setConfigCallback |
|||
* @param {Object} config - 全局默认config |
|||
*/ |
|||
@ -0,0 +1,20 @@ |
|||
'use strict' |
|||
|
|||
import isAbsoluteURL from '../helpers/isAbsoluteURL' |
|||
import combineURLs from '../helpers/combineURLs' |
|||
|
|||
/** |
|||
* Creates a new URL by combining the baseURL with the requestedURL, |
|||
* only when the requestedURL is not already an absolute URL. |
|||
* If the requestURL is absolute, this function returns the requestedURL untouched. |
|||
* |
|||
* @param {string} baseURL The base URL |
|||
* @param {string} requestedURL Absolute or relative URL to combine |
|||
* @returns {string} The combined full path |
|||
*/ |
|||
export default function buildFullPath(baseURL, requestedURL) { |
|||
if (baseURL && !isAbsoluteURL(requestedURL)) { |
|||
return combineURLs(baseURL, requestedURL) |
|||
} |
|||
return requestedURL |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
/** |
|||
* 默认的全局配置 |
|||
*/ |
|||
|
|||
|
|||
export default { |
|||
baseURL: '', |
|||
header: {}, |
|||
method: 'GET', |
|||
dataType: 'json', |
|||
paramsSerializer: null, |
|||
// #ifndef MP-ALIPAY
|
|||
responseType: 'text', |
|||
// #endif
|
|||
custom: {}, |
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
timeout: 60000, |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
sslVerify: true, |
|||
// #endif
|
|||
// #ifdef H5
|
|||
withCredentials: false, |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
firstIpv4: false, |
|||
// #endif
|
|||
validateStatus: function validateStatus(status) { |
|||
return status >= 200 && status < 300 |
|||
}, |
|||
// 是否尝试将响应数据json化
|
|||
forcedJSONParsing: true |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
import adapter from '../adapters/index' |
|||
|
|||
|
|||
export default (config) => { |
|||
return adapter(config) |
|||
} |
|||
@ -0,0 +1,126 @@ |
|||
import {deepMerge, isUndefined} from '../utils' |
|||
|
|||
/** |
|||
* 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局 |
|||
* @param {Array} keys - 配置项 |
|||
* @param {Object} globalsConfig - 当前的全局配置 |
|||
* @param {Object} config2 - 局部配置 |
|||
* @return {{}} |
|||
*/ |
|||
const mergeKeys = (keys, globalsConfig, config2) => { |
|||
let config = {} |
|||
keys.forEach(prop => { |
|||
if (!isUndefined(config2[prop])) { |
|||
config[prop] = config2[prop] |
|||
} else if (!isUndefined(globalsConfig[prop])) { |
|||
config[prop] = globalsConfig[prop] |
|||
} |
|||
}) |
|||
return config |
|||
} |
|||
/** |
|||
* |
|||
* @param globalsConfig - 当前实例的全局配置 |
|||
* @param config2 - 当前的局部配置 |
|||
* @return - 合并后的配置 |
|||
*/ |
|||
export default (globalsConfig, config2 = {}) => { |
|||
const method = config2.method || globalsConfig.method || 'GET' |
|||
let config = { |
|||
baseURL: config2.baseURL || globalsConfig.baseURL || '', |
|||
method: method, |
|||
url: config2.url || '', |
|||
params: config2.params || {}, |
|||
custom: {...(globalsConfig.custom || {}), ...(config2.custom || {})}, |
|||
header: deepMerge(globalsConfig.header || {}, config2.header || {}) |
|||
} |
|||
const defaultToConfig2Keys = ['getTask', 'validateStatus', 'paramsSerializer', 'forcedJSONParsing'] |
|||
config = {...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2)} |
|||
|
|||
// eslint-disable-next-line no-empty
|
|||
if (method === 'DOWNLOAD') { |
|||
const downloadKeys = [ |
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
'timeout', |
|||
// #endif
|
|||
// #ifdef MP
|
|||
'filePath', |
|||
// #endif
|
|||
] |
|||
config = {...config, ...mergeKeys(downloadKeys, globalsConfig, config2)} |
|||
} else if (method === 'UPLOAD') { |
|||
delete config.header['content-type'] |
|||
delete config.header['Content-Type'] |
|||
const uploadKeys = [ |
|||
// #ifdef APP-PLUS || H5
|
|||
'files', |
|||
// #endif
|
|||
// #ifdef MP-ALIPAY
|
|||
'fileType', |
|||
// #endif
|
|||
// #ifdef H5
|
|||
'file', |
|||
// #endif
|
|||
'filePath', |
|||
'name', |
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
'timeout', |
|||
// #endif
|
|||
'formData', |
|||
] |
|||
uploadKeys.forEach(prop => { |
|||
if (!isUndefined(config2[prop])) { |
|||
config[prop] = config2[prop] |
|||
} |
|||
}) |
|||
// #ifdef H5 || APP-PLUS || MP-WEIXIN || MP-ALIPAY || MP-TOUTIAO || MP-KUAISHOU
|
|||
if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) { |
|||
config['timeout'] = globalsConfig['timeout'] |
|||
} |
|||
// #endif
|
|||
} else { |
|||
const defaultsKeys = [ |
|||
'data', |
|||
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
|
|||
'timeout', |
|||
// #endif
|
|||
'dataType', |
|||
// #ifndef MP-ALIPAY
|
|||
'responseType', |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
'sslVerify', |
|||
// #endif
|
|||
// #ifdef H5
|
|||
'withCredentials', |
|||
// #endif
|
|||
// #ifdef APP-PLUS
|
|||
'firstIpv4', |
|||
// #endif
|
|||
// #ifdef MP-WEIXIN
|
|||
'enableHttp2', |
|||
'enableQuic', |
|||
// #endif
|
|||
// #ifdef MP-TOUTIAO || MP-WEIXIN
|
|||
'enableCache', |
|||
// #endif
|
|||
// #ifdef MP-WEIXIN
|
|||
'enableHttpDNS', |
|||
'httpDNSServiceId', |
|||
'enableChunked', |
|||
'forceCellularNetwork', |
|||
// #endif
|
|||
// #ifdef MP-ALIPAY
|
|||
'enableCookie', |
|||
// #endif
|
|||
// #ifdef MP-BAIDU
|
|||
'cloudCache', |
|||
'defer' |
|||
// #endif
|
|||
|
|||
] |
|||
config = {...config, ...mergeKeys(defaultsKeys, globalsConfig, config2)} |
|||
} |
|||
|
|||
return config |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
/** |
|||
* Resolve or reject a Promise based on response status. |
|||
* |
|||
* @param {Function} resolve A function that resolves the promise. |
|||
* @param {Function} reject A function that rejects the promise. |
|||
* @param {object} response The response. |
|||
*/ |
|||
export default function settle(resolve, reject, response) { |
|||
const validateStatus = response.config.validateStatus |
|||
const status = response.statusCode |
|||
if (status && (!validateStatus || validateStatus(status))) { |
|||
resolve(response) |
|||
} else { |
|||
reject(response) |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
'use strict' |
|||
|
|||
import * as utils from './../utils' |
|||
|
|||
function encode(val) { |
|||
return encodeURIComponent(val).replace(/%40/gi, '@').replace(/%3A/gi, ':').replace(/%24/g, '$').replace(/%2C/gi, ',').replace(/%20/g, '+').replace(/%5B/gi, '[').replace(/%5D/gi, ']') |
|||
} |
|||
|
|||
/** |
|||
* Build a URL by appending params to the end |
|||
* |
|||
* @param {string} url The base of the url (e.g., http://www.google.com)
|
|||
* @param {object} [params] The params to be appended |
|||
* @returns {string} The formatted url |
|||
*/ |
|||
export default function buildURL(url, params, paramsSerializer) { |
|||
/*eslint no-param-reassign:0*/ |
|||
if (!params) { |
|||
return url |
|||
} |
|||
|
|||
var serializedParams |
|||
if (paramsSerializer) { |
|||
serializedParams = paramsSerializer(params) |
|||
} else if (utils.isURLSearchParams(params)) { |
|||
serializedParams = params.toString() |
|||
} else { |
|||
var parts = [] |
|||
|
|||
utils.forEach(params, function serialize(val, key) { |
|||
if (val === null || typeof val === 'undefined') { |
|||
return |
|||
} |
|||
|
|||
if (utils.isArray(val)) { |
|||
key = key + '[]' |
|||
} else { |
|||
val = [val] |
|||
} |
|||
|
|||
utils.forEach(val, function parseValue(v) { |
|||
if (utils.isDate(v)) { |
|||
v = v.toISOString() |
|||
} else if (utils.isObject(v)) { |
|||
v = JSON.stringify(v) |
|||
} |
|||
parts.push(encode(key) + '=' + encode(v)) |
|||
}) |
|||
}) |
|||
|
|||
serializedParams = parts.join('&') |
|||
} |
|||
|
|||
if (serializedParams) { |
|||
var hashmarkIndex = url.indexOf('#') |
|||
if (hashmarkIndex !== -1) { |
|||
url = url.slice(0, hashmarkIndex) |
|||
} |
|||
|
|||
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams |
|||
} |
|||
|
|||
return url |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
'use strict' |
|||
|
|||
/** |
|||
* Creates a new URL by combining the specified URLs |
|||
* |
|||
* @param {string} baseURL The base URL |
|||
* @param {string} relativeURL The relative URL |
|||
* @returns {string} The combined URL |
|||
*/ |
|||
export default function combineURLs(baseURL, relativeURL) { |
|||
return relativeURL |
|||
? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') |
|||
: baseURL |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
'use strict' |
|||
|
|||
/** |
|||
* Determines whether the specified URL is absolute |
|||
* |
|||
* @param {string} url The URL to test |
|||
* @returns {boolean} True if the specified URL is absolute, otherwise false |
|||
*/ |
|||
export default function isAbsoluteURL(url) { |
|||
// A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
|
|||
// RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
|
|||
// by any combination of letters, digits, plus, period, or hyphen.
|
|||
return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url) |
|||
} |
|||
@ -0,0 +1,197 @@ |
|||
export type HttpTask = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask; |
|||
|
|||
export type HttpRequestTask = UniApp.RequestTask; |
|||
|
|||
export type HttpUploadTask = UniApp.UploadTask; |
|||
|
|||
export type HttpDownloadTask = UniApp.DownloadTask; |
|||
|
|||
export type HttpMethod = |
|||
"GET" |
|||
| "POST" |
|||
| "PUT" |
|||
| "DELETE" |
|||
| "CONNECT" |
|||
| "HEAD" |
|||
| "OPTIONS" |
|||
| "TRACE" |
|||
| "UPLOAD" |
|||
| "DOWNLOAD"; |
|||
|
|||
export type HttpRequestHeader = Record<string, string>; |
|||
|
|||
export type HttpParams = Record<string, any>; |
|||
|
|||
export type HttpData = Record<string, any>; |
|||
|
|||
export type HttpResponseType = 'arraybuffer' | 'text'; |
|||
|
|||
export type HttpCustom = Record<string, any>; |
|||
|
|||
export type HttpFileType = 'image' | 'video' | 'audio'; |
|||
|
|||
export type HttpFormData = Record<string, any>; |
|||
|
|||
export type HttpResponseHeader = Record<string, string> & { |
|||
"set-cookie"?: string[] |
|||
}; |
|||
|
|||
export interface HttpRequestConfig<T = HttpTask> { |
|||
/** @desc 请求服务器接口地址 */ |
|||
url?: string; |
|||
/** @desc 请求方式,默认为 GET */ |
|||
method?: HttpMethod; |
|||
/** @desc 请求基地址 */ |
|||
baseURL?: string; |
|||
/** @desc 请求头信息,不能设置 Referer,App、H5 端会自动带上 cookie,且 H5 端不可手动修改 */ |
|||
header?: HttpRequestHeader; |
|||
/** @desc 请求查询参数,自动拼接为查询字符串 */ |
|||
params?: HttpParams; |
|||
/** @desc 请求体参数 */ |
|||
data?: HttpData; |
|||
/** @desc 超时时间,单位 ms,默认为 60000,仅 H5 (HBuilderX 2.9.9+)、APP (HBuilderX 2.9.9+)、微信小程序 (2.10.0)、支付宝小程序支持 */ |
|||
timeout?: number; |
|||
/** @desc 跨域请求时是否携带凭证 (cookies),默认为 false,仅 H5 (HBuilderX 2.6.15+) 支持 */ |
|||
withCredentials?: boolean; |
|||
/** @desc 设置响应的数据类型,支付宝小程序不支持 */ |
|||
responseType?: HttpResponseType; |
|||
/** @desc 全局自定义验证器 */ |
|||
validateStatus?: ((statusCode: number) => boolean) | null; |
|||
|
|||
|
|||
/** params 参数自定义处理 */ |
|||
paramsSerializer?: (params: AnyObject) => string | void; |
|||
|
|||
/** @desc 默认为 json,如果设为 json,会尝试对返回的数据做一次 JSON.parse */ |
|||
dataType?: string; |
|||
/** @desc DNS 解析时是否优先使用 ipv4,默认为 false,仅 App-Android (HBuilderX 2.8.0+) 支持 */ |
|||
firstIpv4?: boolean; |
|||
/** @desc 是否验证 SSL 证书,默认为 true,仅 App-Android (HBuilderX 2.3.3+) 支持 */ |
|||
sslVerify?: boolean; |
|||
|
|||
/** @desc 开启 http2;微信小程序 */ |
|||
enableHttp2?: boolean; |
|||
|
|||
/** @desc 开启 quic;微信小程序 */ |
|||
enableQuic?: boolean; |
|||
/** @desc 开启 cache;微信小程序、字节跳动小程序 2.31.0+ */ |
|||
enableCache?: boolean; |
|||
/** @desc 开启 httpDNS;微信小程序 */ |
|||
enableHttpDNS?: boolean; |
|||
/** @desc httpDNS 服务商;微信小程序 */ |
|||
httpDNSServiceId?: string; |
|||
/** @desc 开启 transfer-encoding chunked;微信小程序 */ |
|||
enableChunked?: boolean; |
|||
/** @desc wifi下使用移动网络发送请求;微信小程序 */ |
|||
forceCellularNetwork?: boolean; |
|||
/** @desc 开启后可在headers中编辑cookie;支付宝小程序 10.2.33+ */ |
|||
enableCookie?: boolean; |
|||
/** @desc 是否开启云加速;百度小程序 3.310.11+ */ |
|||
cloudCache?: boolean | object; |
|||
/** @desc 控制当前请求是否延时至首屏内容渲染后发送;百度小程序 3.310.11+ */ |
|||
defer?: boolean; |
|||
|
|||
/** @desc 自定义参数 */ |
|||
custom?: HttpCustom; |
|||
|
|||
/** @desc 返回当前请求的 task 和 options,不要在这里修改 options */ |
|||
getTask?: (task: T, options: HttpRequestConfig<T>) => void; |
|||
|
|||
/** @desc 需要上传的文件列表,使用 files 时,filePath 和 name 不生效,仅支持 App、H5 (2.6.15+) */ |
|||
files?: { name?: string; file?: File; uri: string; }[]; |
|||
/** @desc 文件类型,仅支付宝小程序支持且为必填项 */ |
|||
fileType?: HttpFileType; |
|||
/** @desc 要上传的文件对象,仅 H5 (2.6.15+) 支持 */ |
|||
file?: File; |
|||
/** @desc 要上传文件资源的路径,使用 files 时,filePath 和 name 不生效 */ |
|||
filePath?: string; |
|||
/** @desc 文件对应的 key,开发者在服务器端通过这个 key 可以获取到文件二进制内容,使用 files 时,filePath 和 name 不生效 */ |
|||
name?: string; |
|||
/** @desc 请求中其他额外的 form data */ |
|||
formData?: HttpFormData; |
|||
} |
|||
|
|||
export interface HttpResponse<T = any, D = HttpTask> { |
|||
data: T; |
|||
statusCode: number; |
|||
header: HttpResponseHeader; |
|||
config: HttpRequestConfig<D>; |
|||
cookies: string[]; |
|||
errMsg: string; |
|||
rawData: any; |
|||
} |
|||
|
|||
export interface HttpUploadResponse<T = any, D = HttpTask> { |
|||
data: T; |
|||
statusCode: number; |
|||
config: HttpRequestConfig<D>; |
|||
errMsg: string; |
|||
rawData: any; |
|||
} |
|||
|
|||
export interface HttpDownloadResponse extends HttpResponse { |
|||
tempFilePath: string; |
|||
apFilePath?: string; |
|||
filePath?: string; |
|||
fileContent?: string; |
|||
} |
|||
|
|||
export interface HttpError<T = any, D = HttpTask> { |
|||
data?: T; |
|||
statusCode?: number; |
|||
header?: HttpResponseHeader; |
|||
config: HttpRequestConfig<D>; |
|||
cookies?: string[]; |
|||
errMsg: string; |
|||
} |
|||
|
|||
export interface HttpPromise<T = any> extends Promise<HttpResponse<T>> { |
|||
} |
|||
|
|||
export interface HttpInterceptorManager<V, E = V> { |
|||
use(onFulfilled?: (value: V) => V | Promise<V>, onRejected?: (error: E) => T | Promise<E>): void; |
|||
|
|||
eject(id: number): void; |
|||
} |
|||
|
|||
export abstract class HttpRequestAbstract { |
|||
constructor(config?: HttpRequestConfig); |
|||
|
|||
interceptors: { |
|||
request: HttpInterceptorManager<HttpRequestConfig>; |
|||
response: HttpInterceptorManager<HttpResponse, HttpError>; |
|||
} |
|||
|
|||
request<T = any, R = HttpResponse<T>, D = HttpRequestTask>(config: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
get<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
delete<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
head<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
options<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
post<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
put<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
config: HttpRequestConfig; |
|||
|
|||
setConfig<D = HttpTask>(onSend: (config: HttpRequestConfig<D>) => HttpRequestConfig<D>): void; |
|||
|
|||
connect<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
trace<T = any, R = HttpResponse<T>, D = HttpRequestTask>(url: string, data?: HttpData, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
upload<T = any, R = HttpUploadResponse<T>, D = HttpUploadTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
download<T = any, R = HttpDownloadResponse<T>, D = HttpDownloadTask>(url: string, config?: HttpRequestConfig<D>): Promise<R>; |
|||
|
|||
middleware<T = any, R = HttpResponse<T>, D = HttpTask>(config: HttpRequestConfig<D>): Promise<R>; |
|||
} |
|||
|
|||
declare class HttpRequest extends HttpRequestAbstract { |
|||
} |
|||
|
|||
export default HttpRequest; |
|||
@ -0,0 +1,2 @@ |
|||
import Request from './core/Request' |
|||
export default Request |
|||
@ -0,0 +1,135 @@ |
|||
'use strict' |
|||
|
|||
// utils is a library of generic helper functions non-specific to axios
|
|||
|
|||
var toString = Object.prototype.toString |
|||
|
|||
/** |
|||
* Determine if a value is an Array |
|||
* |
|||
* @param {Object} val The value to test |
|||
* @returns {boolean} True if value is an Array, otherwise false |
|||
*/ |
|||
export function isArray (val) { |
|||
return toString.call(val) === '[object Array]' |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Determine if a value is an Object |
|||
* |
|||
* @param {Object} val The value to test |
|||
* @returns {boolean} True if value is an Object, otherwise false |
|||
*/ |
|||
export function isObject (val) { |
|||
return val !== null && typeof val === 'object' |
|||
} |
|||
|
|||
/** |
|||
* Determine if a value is a Date |
|||
* |
|||
* @param {Object} val The value to test |
|||
* @returns {boolean} True if value is a Date, otherwise false |
|||
*/ |
|||
export function isDate (val) { |
|||
return toString.call(val) === '[object Date]' |
|||
} |
|||
|
|||
/** |
|||
* Determine if a value is a URLSearchParams object |
|||
* |
|||
* @param {Object} val The value to test |
|||
* @returns {boolean} True if value is a URLSearchParams object, otherwise false |
|||
*/ |
|||
export function isURLSearchParams (val) { |
|||
return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Iterate over an Array or an Object invoking a function for each item. |
|||
* |
|||
* If `obj` is an Array callback will be called passing |
|||
* the value, index, and complete array for each item. |
|||
* |
|||
* If 'obj' is an Object callback will be called passing |
|||
* the value, key, and complete object for each property. |
|||
* |
|||
* @param {Object|Array} obj The object to iterate |
|||
* @param {Function} fn The callback to invoke for each item |
|||
*/ |
|||
export function forEach (obj, fn) { |
|||
// Don't bother if no value provided
|
|||
if (obj === null || typeof obj === 'undefined') { |
|||
return |
|||
} |
|||
|
|||
// Force an array if not already something iterable
|
|||
if (typeof obj !== 'object') { |
|||
/*eslint no-param-reassign:0*/ |
|||
obj = [obj] |
|||
} |
|||
|
|||
if (isArray(obj)) { |
|||
// Iterate over array values
|
|||
for (var i = 0, l = obj.length; i < l; i++) { |
|||
fn.call(null, obj[i], i, obj) |
|||
} |
|||
} else { |
|||
// Iterate over object keys
|
|||
for (var key in obj) { |
|||
if (Object.prototype.hasOwnProperty.call(obj, key)) { |
|||
fn.call(null, obj[key], key, obj) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 是否为boolean 值 |
|||
* @param val |
|||
* @returns {boolean} |
|||
*/ |
|||
export function isBoolean(val) { |
|||
return typeof val === 'boolean' |
|||
} |
|||
|
|||
/** |
|||
* 是否为真正的对象{} new Object |
|||
* @param {any} obj - 检测的对象 |
|||
* @returns {boolean} |
|||
*/ |
|||
export function isPlainObject(obj) { |
|||
return Object.prototype.toString.call(obj) === '[object Object]' |
|||
} |
|||
|
|||
|
|||
|
|||
/** |
|||
* Function equal to merge with the difference being that no reference |
|||
* to original objects is kept. |
|||
* |
|||
* @see merge |
|||
* @param {Object} obj1 Object to merge |
|||
* @returns {Object} Result of all merge properties |
|||
*/ |
|||
export function deepMerge(/* obj1, obj2, obj3, ... */) { |
|||
let result = {} |
|||
function assignValue(val, key) { |
|||
if (typeof result[key] === 'object' && typeof val === 'object') { |
|||
result[key] = deepMerge(result[key], val) |
|||
} else if (typeof val === 'object') { |
|||
result[key] = deepMerge({}, val) |
|||
} else { |
|||
result[key] = val |
|||
} |
|||
} |
|||
for (let i = 0, l = arguments.length; i < l; i++) { |
|||
forEach(arguments[i], assignValue) |
|||
} |
|||
return result |
|||
} |
|||
|
|||
export function isUndefined (val) { |
|||
return typeof val === 'undefined' |
|||
} |
|||
@ -0,0 +1,264 @@ |
|||
/* eslint-disable */ |
|||
var clone = (function() { |
|||
'use strict'; |
|||
|
|||
function _instanceof(obj, type) { |
|||
return type != null && obj instanceof type; |
|||
} |
|||
|
|||
var nativeMap; |
|||
try { |
|||
nativeMap = Map; |
|||
} catch(_) { |
|||
// maybe a reference error because no `Map`. Give it a dummy value that no
|
|||
// value will ever be an instanceof.
|
|||
nativeMap = function() {}; |
|||
} |
|||
|
|||
var nativeSet; |
|||
try { |
|||
nativeSet = Set; |
|||
} catch(_) { |
|||
nativeSet = function() {}; |
|||
} |
|||
|
|||
var nativePromise; |
|||
try { |
|||
nativePromise = Promise; |
|||
} catch(_) { |
|||
nativePromise = function() {}; |
|||
} |
|||
|
|||
/** |
|||
* Clones (copies) an Object using deep copying. |
|||
* |
|||
* This function supports circular references by default, but if you are certain |
|||
* there are no circular references in your object, you can save some CPU time |
|||
* by calling clone(obj, false). |
|||
* |
|||
* Caution: if `circular` is false and `parent` contains circular references, |
|||
* your program may enter an infinite loop and crash. |
|||
* |
|||
* @param `parent` - the object to be cloned |
|||
* @param `circular` - set to true if the object to be cloned may contain |
|||
* circular references. (optional - true by default) |
|||
* @param `depth` - set to a number if the object is only to be cloned to |
|||
* a particular depth. (optional - defaults to Infinity) |
|||
* @param `prototype` - sets the prototype to be used when cloning an object. |
|||
* (optional - defaults to parent prototype). |
|||
* @param `includeNonEnumerable` - set to true if the non-enumerable properties |
|||
* should be cloned as well. Non-enumerable properties on the prototype |
|||
* chain will be ignored. (optional - false by default) |
|||
*/ |
|||
function clone(parent, circular, depth, prototype, includeNonEnumerable) { |
|||
if (typeof circular === 'object') { |
|||
depth = circular.depth; |
|||
prototype = circular.prototype; |
|||
includeNonEnumerable = circular.includeNonEnumerable; |
|||
circular = circular.circular; |
|||
} |
|||
// maintain two arrays for circular references, where corresponding parents
|
|||
// and children have the same index
|
|||
var allParents = []; |
|||
var allChildren = []; |
|||
|
|||
var useBuffer = typeof Buffer != 'undefined'; |
|||
|
|||
if (typeof circular == 'undefined') |
|||
circular = true; |
|||
|
|||
if (typeof depth == 'undefined') |
|||
depth = Infinity; |
|||
|
|||
// recurse this function so we don't reset allParents and allChildren
|
|||
function _clone(parent, depth) { |
|||
// cloning null always returns null
|
|||
if (parent === null) |
|||
return null; |
|||
|
|||
if (depth === 0) |
|||
return parent; |
|||
|
|||
var child; |
|||
var proto; |
|||
if (typeof parent != 'object') { |
|||
return parent; |
|||
} |
|||
|
|||
if (_instanceof(parent, nativeMap)) { |
|||
child = new nativeMap(); |
|||
} else if (_instanceof(parent, nativeSet)) { |
|||
child = new nativeSet(); |
|||
} else if (_instanceof(parent, nativePromise)) { |
|||
child = new nativePromise(function (resolve, reject) { |
|||
parent.then(function(value) { |
|||
resolve(_clone(value, depth - 1)); |
|||
}, function(err) { |
|||
reject(_clone(err, depth - 1)); |
|||
}); |
|||
}); |
|||
} else if (clone.__isArray(parent)) { |
|||
child = []; |
|||
} else if (clone.__isRegExp(parent)) { |
|||
child = new RegExp(parent.source, __getRegExpFlags(parent)); |
|||
if (parent.lastIndex) child.lastIndex = parent.lastIndex; |
|||
} else if (clone.__isDate(parent)) { |
|||
child = new Date(parent.getTime()); |
|||
} else if (useBuffer && Buffer.isBuffer(parent)) { |
|||
if (Buffer.from) { |
|||
// Node.js >= 5.10.0
|
|||
child = Buffer.from(parent); |
|||
} else { |
|||
// Older Node.js versions
|
|||
child = new Buffer(parent.length); |
|||
parent.copy(child); |
|||
} |
|||
return child; |
|||
} else if (_instanceof(parent, Error)) { |
|||
child = Object.create(parent); |
|||
} else { |
|||
if (typeof prototype == 'undefined') { |
|||
proto = Object.getPrototypeOf(parent); |
|||
child = Object.create(proto); |
|||
} |
|||
else { |
|||
child = Object.create(prototype); |
|||
proto = prototype; |
|||
} |
|||
} |
|||
|
|||
if (circular) { |
|||
var index = allParents.indexOf(parent); |
|||
|
|||
if (index != -1) { |
|||
return allChildren[index]; |
|||
} |
|||
allParents.push(parent); |
|||
allChildren.push(child); |
|||
} |
|||
|
|||
if (_instanceof(parent, nativeMap)) { |
|||
parent.forEach(function(value, key) { |
|||
var keyChild = _clone(key, depth - 1); |
|||
var valueChild = _clone(value, depth - 1); |
|||
child.set(keyChild, valueChild); |
|||
}); |
|||
} |
|||
if (_instanceof(parent, nativeSet)) { |
|||
parent.forEach(function(value) { |
|||
var entryChild = _clone(value, depth - 1); |
|||
child.add(entryChild); |
|||
}); |
|||
} |
|||
|
|||
for (var i in parent) { |
|||
var attrs = Object.getOwnPropertyDescriptor(parent, i); |
|||
if (attrs) { |
|||
child[i] = _clone(parent[i], depth - 1); |
|||
} |
|||
|
|||
try { |
|||
var objProperty = Object.getOwnPropertyDescriptor(parent, i); |
|||
if (objProperty.set === 'undefined') { |
|||
// no setter defined. Skip cloning this property
|
|||
continue; |
|||
} |
|||
child[i] = _clone(parent[i], depth - 1); |
|||
} catch(e){ |
|||
if (e instanceof TypeError) { |
|||
// when in strict mode, TypeError will be thrown if child[i] property only has a getter
|
|||
// we can't do anything about this, other than inform the user that this property cannot be set.
|
|||
continue |
|||
} else if (e instanceof ReferenceError) { |
|||
//this may happen in non strict mode
|
|||
continue |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
if (Object.getOwnPropertySymbols) { |
|||
var symbols = Object.getOwnPropertySymbols(parent); |
|||
for (var i = 0; i < symbols.length; i++) { |
|||
// Don't need to worry about cloning a symbol because it is a primitive,
|
|||
// like a number or string.
|
|||
var symbol = symbols[i]; |
|||
var descriptor = Object.getOwnPropertyDescriptor(parent, symbol); |
|||
if (descriptor && !descriptor.enumerable && !includeNonEnumerable) { |
|||
continue; |
|||
} |
|||
child[symbol] = _clone(parent[symbol], depth - 1); |
|||
Object.defineProperty(child, symbol, descriptor); |
|||
} |
|||
} |
|||
|
|||
if (includeNonEnumerable) { |
|||
var allPropertyNames = Object.getOwnPropertyNames(parent); |
|||
for (var i = 0; i < allPropertyNames.length; i++) { |
|||
var propertyName = allPropertyNames[i]; |
|||
var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName); |
|||
if (descriptor && descriptor.enumerable) { |
|||
continue; |
|||
} |
|||
child[propertyName] = _clone(parent[propertyName], depth - 1); |
|||
Object.defineProperty(child, propertyName, descriptor); |
|||
} |
|||
} |
|||
|
|||
return child; |
|||
} |
|||
|
|||
return _clone(parent, depth); |
|||
} |
|||
|
|||
/** |
|||
* Simple flat clone using prototype, accepts only objects, usefull for property |
|||
* override on FLAT configuration object (no nested props). |
|||
* |
|||
* USE WITH CAUTION! This may not behave as you wish if you do not know how this |
|||
* works. |
|||
*/ |
|||
clone.clonePrototype = function clonePrototype(parent) { |
|||
if (parent === null) |
|||
return null; |
|||
|
|||
var c = function () {}; |
|||
c.prototype = parent; |
|||
return new c(); |
|||
}; |
|||
|
|||
// private utility functions
|
|||
|
|||
function __objToStr(o) { |
|||
return Object.prototype.toString.call(o); |
|||
} |
|||
clone.__objToStr = __objToStr; |
|||
|
|||
function __isDate(o) { |
|||
return typeof o === 'object' && __objToStr(o) === '[object Date]'; |
|||
} |
|||
clone.__isDate = __isDate; |
|||
|
|||
function __isArray(o) { |
|||
return typeof o === 'object' && __objToStr(o) === '[object Array]'; |
|||
} |
|||
clone.__isArray = __isArray; |
|||
|
|||
function __isRegExp(o) { |
|||
return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; |
|||
} |
|||
clone.__isRegExp = __isRegExp; |
|||
|
|||
function __getRegExpFlags(re) { |
|||
var flags = ''; |
|||
if (re.global) flags += 'g'; |
|||
if (re.ignoreCase) flags += 'i'; |
|||
if (re.multiline) flags += 'm'; |
|||
return flags; |
|||
} |
|||
clone.__getRegExpFlags = __getRegExpFlags; |
|||
|
|||
return clone; |
|||
})(); |
|||
|
|||
export default clone |
|||
@ -0,0 +1,13 @@ |
|||
export default { |
|||
props: { |
|||
lang: String, |
|||
sessionFrom: String, |
|||
sendMessageTitle: String, |
|||
sendMessagePath: String, |
|||
sendMessageImg: String, |
|||
showMessageCard: Boolean, |
|||
appParameter: String, |
|||
formType: String, |
|||
openType: String |
|||
} |
|||
} |
|||
@ -0,0 +1,172 @@ |
|||
import * as index from '../function/index.js'; |
|||
import * as test from '../function/test.js'; |
|||
import route from '../util/route.js'; |
|||
import debounce from '../function/debounce.js'; |
|||
import throttle from '../function/throttle.js'; |
|||
export default { |
|||
// 定义每个组件都可能需要用到的外部样式以及类名
|
|||
props: { |
|||
// 每个组件都有的父组件传递的样式,可以为字符串或者对象形式
|
|||
customStyle: { |
|||
type: [Object, String], |
|||
default: () => ({}) |
|||
}, |
|||
customClass: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 跳转的页面路径
|
|||
url: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 页面跳转的类型
|
|||
linkType: { |
|||
type: String, |
|||
default: 'navigateTo' |
|||
} |
|||
}, |
|||
data() { |
|||
return {} |
|||
}, |
|||
onLoad() { |
|||
// getRect挂载到$uv上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
|
|||
this.$uv.getRect = this.$uvGetRect |
|||
}, |
|||
created() { |
|||
// 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$uv
|
|||
this.$uv.getRect = this.$uvGetRect |
|||
}, |
|||
computed: { |
|||
$uv() { |
|||
return { |
|||
...index, |
|||
test, |
|||
route, |
|||
debounce, |
|||
throttle, |
|||
unit: uni?.$uv?.config?.unit |
|||
} |
|||
}, |
|||
/** |
|||
* 生成bem规则类名 |
|||
* 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用 |
|||
* 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式 |
|||
* @param {String} name 组件名称 |
|||
* @param {Array} fixed 一直会存在的类名 |
|||
* @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名 |
|||
* @returns {Array|string} |
|||
*/ |
|||
bem() { |
|||
return function(name, fixed, change) { |
|||
// 类名前缀
|
|||
const prefix = `uv-${name}--` |
|||
const classes = {} |
|||
if (fixed) { |
|||
fixed.map((item) => { |
|||
// 这里的类名,会一直存在
|
|||
classes[prefix + this[item]] = true |
|||
}) |
|||
} |
|||
if (change) { |
|||
change.map((item) => { |
|||
// 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类
|
|||
this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item]) |
|||
}) |
|||
} |
|||
return Object.keys(classes) |
|||
// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
|||
// #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-BAIDU
|
|||
.join(' ') |
|||
// #endif
|
|||
} |
|||
} |
|||
}, |
|||
methods: { |
|||
// 跳转某一个页面
|
|||
openPage(urlKey = 'url') { |
|||
const url = this[urlKey] |
|||
if (url) { |
|||
// 执行类似uni.navigateTo的方法
|
|||
uni[this.linkType]({ |
|||
url |
|||
}) |
|||
} |
|||
}, |
|||
// 查询节点信息
|
|||
// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
|
|||
// 解决办法为在组件根部再套一个没有任何作用的view元素
|
|||
$uvGetRect(selector, all) { |
|||
return new Promise((resolve) => { |
|||
uni.createSelectorQuery() |
|||
.in(this)[all ? 'selectAll' : 'select'](selector) |
|||
.boundingClientRect((rect) => { |
|||
if (all && Array.isArray(rect) && rect.length) { |
|||
resolve(rect) |
|||
} |
|||
if (!all && rect) { |
|||
resolve(rect) |
|||
} |
|||
}) |
|||
.exec() |
|||
}) |
|||
}, |
|||
getParentData(parentName = '') { |
|||
// 避免在created中去定义parent变量
|
|||
if (!this.parent) this.parent = {} |
|||
// 这里的本质原理是,通过获取父组件实例(也即类似uv-radio的父组件uv-radio-group的this)
|
|||
// 将父组件this中对应的参数,赋值给本组件(uv-radio的this)的parentData对象中对应的属性
|
|||
// 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
|
|||
// 此处并不会自动更新子组件的数据,而是依赖父组件uv-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取
|
|||
this.parent = this.$uv.$parent.call(this, parentName) |
|||
if (this.parent.children) { |
|||
// 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中
|
|||
this.parent.children.indexOf(this) === -1 && this.parent.children.push(this) |
|||
} |
|||
if (this.parent && this.parentData) { |
|||
// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
|
|||
Object.keys(this.parentData).map((key) => { |
|||
this.parentData[key] = this.parent[key] |
|||
}) |
|||
} |
|||
}, |
|||
// 阻止事件冒泡
|
|||
preventEvent(e) { |
|||
e && typeof(e.stopPropagation) === 'function' && e.stopPropagation() |
|||
}, |
|||
// 空操作
|
|||
noop(e) { |
|||
this.preventEvent(e) |
|||
} |
|||
}, |
|||
onReachBottom() { |
|||
uni.$emit('uvOnReachBottom') |
|||
}, |
|||
beforeDestroy() { |
|||
// 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
|
|||
// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
|
|||
if (this.parent && test.array(this.parent.children)) { |
|||
// 组件销毁时,移除父组件中的children数组中对应的实例
|
|||
const childrenList = this.parent.children |
|||
childrenList.map((child, index) => { |
|||
// 如果相等,则移除
|
|||
if (child === this) { |
|||
childrenList.splice(index, 1) |
|||
} |
|||
}) |
|||
} |
|||
}, |
|||
// 兼容vue3
|
|||
unmounted() { |
|||
if (this.parent && test.array(this.parent.children)) { |
|||
// 组件销毁时,移除父组件中的children数组中对应的实例
|
|||
const childrenList = this.parent.children |
|||
childrenList.map((child, index) => { |
|||
// 如果相等,则移除
|
|||
if (child === this) { |
|||
childrenList.splice(index, 1) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
export default { |
|||
// #ifdef MP-WEIXIN
|
|||
// 将自定义节点设置成虚拟的(去掉自定义组件包裹层),更加接近Vue组件的表现,能更好的使用flex属性
|
|||
options: { |
|||
virtualHost: true |
|||
} |
|||
// #endif
|
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
export default { |
|||
onLoad() { |
|||
// 设置默认的转发参数
|
|||
uni.$uv.mpShare = { |
|||
title: '', // 默认为小程序名称
|
|||
path: '', // 默认为当前页面路径
|
|||
imageUrl: '' // 默认为当前页面的截图
|
|||
} |
|||
}, |
|||
onShareAppMessage() { |
|||
return uni.$uv.mpShare |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
export default { |
|||
props: { |
|||
openType: String |
|||
}, |
|||
emits: ['getphonenumber','getuserinfo','error','opensetting','launchapp','contact','chooseavatar','addgroupapp','chooseaddress','subscribe','login','im'], |
|||
methods: { |
|||
onGetPhoneNumber(event) { |
|||
this.$emit('getphonenumber', event.detail) |
|||
}, |
|||
onGetUserInfo(event) { |
|||
this.$emit('getuserinfo', event.detail) |
|||
}, |
|||
onError(event) { |
|||
this.$emit('error', event.detail) |
|||
}, |
|||
onOpenSetting(event) { |
|||
this.$emit('opensetting', event.detail) |
|||
}, |
|||
onLaunchApp(event) { |
|||
this.$emit('launchapp', event.detail) |
|||
}, |
|||
onContact(event) { |
|||
this.$emit('contact', event.detail) |
|||
}, |
|||
onChooseavatar(event) { |
|||
this.$emit('chooseavatar', event.detail) |
|||
}, |
|||
onAgreeprivacyauthorization(event) { |
|||
this.$emit('agreeprivacyauthorization', event.detail) |
|||
}, |
|||
onAddgroupapp(event) { |
|||
this.$emit('addgroupapp', event.detail) |
|||
}, |
|||
onChooseaddress(event) { |
|||
this.$emit('chooseaddress', event.detail) |
|||
}, |
|||
onSubscribe(event) { |
|||
this.$emit('subscribe', event.detail) |
|||
}, |
|||
onLogin(event) { |
|||
this.$emit('login', event.detail) |
|||
}, |
|||
onIm(event) { |
|||
this.$emit('im', event.detail) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
const MIN_DISTANCE = 10 |
|||
|
|||
function getDirection(x, y) { |
|||
if (x > y && x > MIN_DISTANCE) { |
|||
return 'horizontal' |
|||
} |
|||
if (y > x && y > MIN_DISTANCE) { |
|||
return 'vertical' |
|||
} |
|||
return '' |
|||
} |
|||
|
|||
export default { |
|||
methods: { |
|||
getTouchPoint(e) { |
|||
if (!e) { |
|||
return { |
|||
x: 0, |
|||
y: 0 |
|||
} |
|||
} if (e.touches && e.touches[0]) { |
|||
return { |
|||
x: e.touches[0].pageX, |
|||
y: e.touches[0].pageY |
|||
} |
|||
} if (e.changedTouches && e.changedTouches[0]) { |
|||
return { |
|||
x: e.changedTouches[0].pageX, |
|||
y: e.changedTouches[0].pageY |
|||
} |
|||
} |
|||
return { |
|||
x: e.clientX || 0, |
|||
y: e.clientY || 0 |
|||
} |
|||
}, |
|||
resetTouchStatus() { |
|||
this.direction = '' |
|||
this.deltaX = 0 |
|||
this.deltaY = 0 |
|||
this.offsetX = 0 |
|||
this.offsetY = 0 |
|||
}, |
|||
touchStart(event) { |
|||
this.resetTouchStatus() |
|||
const touch = this.getTouchPoint(event) |
|||
this.startX = touch.x |
|||
this.startY = touch.y |
|||
}, |
|||
touchMove(event) { |
|||
const touch = this.getTouchPoint(event) |
|||
this.deltaX = touch.x - this.startX |
|||
this.deltaY = touch.y - this.startY |
|||
this.offsetX = Math.abs(this.deltaX) |
|||
this.offsetY = Math.abs(this.deltaY) |
|||
this.direction = this.direction || getDirection(this.offsetX, this.offsetY) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,216 @@ |
|||
var __getOwnPropNames = Object.getOwnPropertyNames; |
|||
var __commonJS = (cb, mod) => function __require() { |
|||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; |
|||
}; |
|||
|
|||
var require_dayjs_min = __commonJS({ |
|||
"uvuidayjs"(exports, module) { |
|||
!function(t, e) { |
|||
"object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e(); |
|||
}(exports, function() { |
|||
"use strict"; |
|||
var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", f = "month", h = "quarter", c = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) { |
|||
var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100; |
|||
return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]"; |
|||
} }, m = function(t2, e2, n2) { |
|||
var r2 = String(t2); |
|||
return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2; |
|||
}, v = { s: m, z: function(t2) { |
|||
var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60; |
|||
return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0"); |
|||
}, m: function t2(e2, n2) { |
|||
if (e2.date() < n2.date()) |
|||
return -t2(n2, e2); |
|||
var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, f), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), f); |
|||
return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0); |
|||
}, a: function(t2) { |
|||
return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2); |
|||
}, p: function(t2) { |
|||
return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t2] || String(t2 || "").toLowerCase().replace(/s$/, ""); |
|||
}, u: function(t2) { |
|||
return void 0 === t2; |
|||
} }, g = "en", D = {}; |
|||
D[g] = M; |
|||
var p = function(t2) { |
|||
return t2 instanceof _; |
|||
}, S = function t2(e2, n2, r2) { |
|||
var i2; |
|||
if (!e2) |
|||
return g; |
|||
if ("string" == typeof e2) { |
|||
var s2 = e2.toLowerCase(); |
|||
D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2); |
|||
var u2 = e2.split("-"); |
|||
if (!i2 && u2.length > 1) |
|||
return t2(u2[0]); |
|||
} else { |
|||
var a2 = e2.name; |
|||
D[a2] = e2, i2 = a2; |
|||
} |
|||
return !r2 && i2 && (g = i2), i2 || !r2 && g; |
|||
}, w = function(t2, e2) { |
|||
if (p(t2)) |
|||
return t2.clone(); |
|||
var n2 = "object" == typeof e2 ? e2 : {}; |
|||
return n2.date = t2, n2.args = arguments, new _(n2); |
|||
}, O = v; |
|||
O.l = S, O.i = p, O.w = function(t2, e2) { |
|||
return w(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset }); |
|||
}; |
|||
var _ = function() { |
|||
function M2(t2) { |
|||
this.$L = S(t2.locale, null, true), this.parse(t2); |
|||
} |
|||
var m2 = M2.prototype; |
|||
return m2.parse = function(t2) { |
|||
this.$d = function(t3) { |
|||
var e2 = t3.date, n2 = t3.utc; |
|||
if (null === e2) |
|||
return new Date(NaN); |
|||
if (O.u(e2)) |
|||
return new Date(); |
|||
if (e2 instanceof Date) |
|||
return new Date(e2); |
|||
if ("string" == typeof e2 && !/Z$/i.test(e2)) { |
|||
var r2 = e2.match($); |
|||
if (r2) { |
|||
var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3); |
|||
return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2); |
|||
} |
|||
} |
|||
return new Date(e2); |
|||
}(t2), this.$x = t2.x || {}, this.init(); |
|||
}, m2.init = function() { |
|||
var t2 = this.$d; |
|||
this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds(); |
|||
}, m2.$utils = function() { |
|||
return O; |
|||
}, m2.isValid = function() { |
|||
return !(this.$d.toString() === l); |
|||
}, m2.isSame = function(t2, e2) { |
|||
var n2 = w(t2); |
|||
return this.startOf(e2) <= n2 && n2 <= this.endOf(e2); |
|||
}, m2.isAfter = function(t2, e2) { |
|||
return w(t2) < this.startOf(e2); |
|||
}, m2.isBefore = function(t2, e2) { |
|||
return this.endOf(e2) < w(t2); |
|||
}, m2.$g = function(t2, e2, n2) { |
|||
return O.u(t2) ? this[e2] : this.set(n2, t2); |
|||
}, m2.unix = function() { |
|||
return Math.floor(this.valueOf() / 1e3); |
|||
}, m2.valueOf = function() { |
|||
return this.$d.getTime(); |
|||
}, m2.startOf = function(t2, e2) { |
|||
var n2 = this, r2 = !!O.u(e2) || e2, h2 = O.p(t2), l2 = function(t3, e3) { |
|||
var i2 = O.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2); |
|||
return r2 ? i2 : i2.endOf(a); |
|||
}, $2 = function(t3, e3) { |
|||
return O.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2); |
|||
}, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : ""); |
|||
switch (h2) { |
|||
case c: |
|||
return r2 ? l2(1, 0) : l2(31, 11); |
|||
case f: |
|||
return r2 ? l2(1, M3) : l2(0, M3 + 1); |
|||
case o: |
|||
var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2; |
|||
return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3); |
|||
case a: |
|||
case d: |
|||
return $2(v2 + "Hours", 0); |
|||
case u: |
|||
return $2(v2 + "Minutes", 1); |
|||
case s: |
|||
return $2(v2 + "Seconds", 2); |
|||
case i: |
|||
return $2(v2 + "Milliseconds", 3); |
|||
default: |
|||
return this.clone(); |
|||
} |
|||
}, m2.endOf = function(t2) { |
|||
return this.startOf(t2, false); |
|||
}, m2.$set = function(t2, e2) { |
|||
var n2, o2 = O.p(t2), h2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = h2 + "Date", n2[d] = h2 + "Date", n2[f] = h2 + "Month", n2[c] = h2 + "FullYear", n2[u] = h2 + "Hours", n2[s] = h2 + "Minutes", n2[i] = h2 + "Seconds", n2[r] = h2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2; |
|||
if (o2 === f || o2 === c) { |
|||
var y2 = this.clone().set(d, 1); |
|||
y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d; |
|||
} else |
|||
l2 && this.$d[l2]($2); |
|||
return this.init(), this; |
|||
}, m2.set = function(t2, e2) { |
|||
return this.clone().$set(t2, e2); |
|||
}, m2.get = function(t2) { |
|||
return this[O.p(t2)](); |
|||
}, m2.add = function(r2, h2) { |
|||
var d2, l2 = this; |
|||
r2 = Number(r2); |
|||
var $2 = O.p(h2), y2 = function(t2) { |
|||
var e2 = w(l2); |
|||
return O.w(e2.date(e2.date() + Math.round(t2 * r2)), l2); |
|||
}; |
|||
if ($2 === f) |
|||
return this.set(f, this.$M + r2); |
|||
if ($2 === c) |
|||
return this.set(c, this.$y + r2); |
|||
if ($2 === a) |
|||
return y2(1); |
|||
if ($2 === o) |
|||
return y2(7); |
|||
var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3; |
|||
return O.w(m3, this); |
|||
}, m2.subtract = function(t2, e2) { |
|||
return this.add(-1 * t2, e2); |
|||
}, m2.format = function(t2) { |
|||
var e2 = this, n2 = this.$locale(); |
|||
if (!this.isValid()) |
|||
return n2.invalidDate || l; |
|||
var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = O.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, f2 = n2.months, h2 = function(t3, n3, i3, s3) { |
|||
return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3); |
|||
}, c2 = function(t3) { |
|||
return O.s(s2 % 12 || 12, t3, "0"); |
|||
}, d2 = n2.meridiem || function(t3, e3, n3) { |
|||
var r3 = t3 < 12 ? "AM" : "PM"; |
|||
return n3 ? r3.toLowerCase() : r3; |
|||
}, $2 = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a2 + 1, MM: O.s(a2 + 1, 2, "0"), MMM: h2(n2.monthsShort, a2, f2, 3), MMMM: h2(f2, a2), D: this.$D, DD: O.s(this.$D, 2, "0"), d: String(this.$W), dd: h2(n2.weekdaysMin, this.$W, o2, 2), ddd: h2(n2.weekdaysShort, this.$W, o2, 3), dddd: o2[this.$W], H: String(s2), HH: O.s(s2, 2, "0"), h: c2(1), hh: c2(2), a: d2(s2, u2, true), A: d2(s2, u2, false), m: String(u2), mm: O.s(u2, 2, "0"), s: String(this.$s), ss: O.s(this.$s, 2, "0"), SSS: O.s(this.$ms, 3, "0"), Z: i2 }; |
|||
return r2.replace(y, function(t3, e3) { |
|||
return e3 || $2[t3] || i2.replace(":", ""); |
|||
}); |
|||
}, m2.utcOffset = function() { |
|||
return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); |
|||
}, m2.diff = function(r2, d2, l2) { |
|||
var $2, y2 = O.p(d2), M3 = w(r2), m3 = (M3.utcOffset() - this.utcOffset()) * e, v2 = this - M3, g2 = O.m(this, M3); |
|||
return g2 = ($2 = {}, $2[c] = g2 / 12, $2[f] = g2, $2[h] = g2 / 3, $2[o] = (v2 - m3) / 6048e5, $2[a] = (v2 - m3) / 864e5, $2[u] = v2 / n, $2[s] = v2 / e, $2[i] = v2 / t, $2)[y2] || v2, l2 ? g2 : O.a(g2); |
|||
}, m2.daysInMonth = function() { |
|||
return this.endOf(f).$D; |
|||
}, m2.$locale = function() { |
|||
return D[this.$L]; |
|||
}, m2.locale = function(t2, e2) { |
|||
if (!t2) |
|||
return this.$L; |
|||
var n2 = this.clone(), r2 = S(t2, e2, true); |
|||
return r2 && (n2.$L = r2), n2; |
|||
}, m2.clone = function() { |
|||
return O.w(this.$d, this); |
|||
}, m2.toDate = function() { |
|||
return new Date(this.valueOf()); |
|||
}, m2.toJSON = function() { |
|||
return this.isValid() ? this.toISOString() : null; |
|||
}, m2.toISOString = function() { |
|||
return this.$d.toISOString(); |
|||
}, m2.toString = function() { |
|||
return this.$d.toUTCString(); |
|||
}, M2; |
|||
}(), T = _.prototype; |
|||
return w.prototype = T, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", f], ["$y", c], ["$D", d]].forEach(function(t2) { |
|||
T[t2[1]] = function(e2) { |
|||
return this.$g(e2, t2[0], t2[1]); |
|||
}; |
|||
}), w.extend = function(t2, e2) { |
|||
return t2.$i || (t2(e2, _, w), t2.$i = true), w; |
|||
}, w.locale = S, w.isDayjs = p, w.unix = function(t2) { |
|||
return w(1e3 * t2); |
|||
}, w.en = D[g], w.Ls = D, w.p = {}, w; |
|||
}); |
|||
} |
|||
}); |
|||
export default require_dayjs_min(); |
|||
@ -0,0 +1,126 @@ |
|||
/** |
|||
* 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷 |
|||
* 并且带有路由拦截功能 |
|||
*/ |
|||
import { queryParams, deepMerge, page } from '@/uni_modules/uv-ui-tools/libs/function/index.js' |
|||
class Router { |
|||
constructor() { |
|||
// 原始属性定义
|
|||
this.config = { |
|||
type: 'navigateTo', |
|||
url: '', |
|||
delta: 1, // navigateBack页面后退时,回退的层数
|
|||
params: {}, // 传递的参数
|
|||
animationType: 'pop-in', // 窗口动画,只在APP有效
|
|||
animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
|
|||
intercept: false ,// 是否需要拦截
|
|||
events: {} // 页面间通信接口,用于监听被打开页面发送到当前页面的数据。hbuilderx 2.8.9+ 开始支持。
|
|||
} |
|||
// 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
|
|||
// 这里在构造函数中进行this绑定
|
|||
this.route = this.route.bind(this) |
|||
} |
|||
|
|||
// 判断url前面是否有"/",如果没有则加上,否则无法跳转
|
|||
addRootPath(url) { |
|||
return url[0] === '/' ? url : `/${url}` |
|||
} |
|||
|
|||
// 整合路由参数
|
|||
mixinParam(url, params) { |
|||
url = url && this.addRootPath(url) |
|||
|
|||
// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
|
|||
// 如果有url中有get参数,转换后无需带上"?"
|
|||
let query = '' |
|||
if (/.*\/.*\?.*=.*/.test(url)) { |
|||
// object对象转为get类型的参数
|
|||
query = queryParams(params, false) |
|||
// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
|
|||
return url += `&${query}` |
|||
} |
|||
// 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
|
|||
query = queryParams(params) |
|||
return url += query |
|||
} |
|||
|
|||
// 对外的方法名称
|
|||
async route(options = {}, params = {}) { |
|||
// 合并用户的配置和内部的默认配置
|
|||
let mergeConfig = {} |
|||
|
|||
if (typeof options === 'string') { |
|||
// 如果options为字符串,则为route(url, params)的形式
|
|||
mergeConfig.url = this.mixinParam(options, params) |
|||
mergeConfig.type = 'navigateTo' |
|||
} else { |
|||
mergeConfig = deepMerge(this.config, options) |
|||
// 否则正常使用mergeConfig中的url和params进行拼接
|
|||
mergeConfig.url = this.mixinParam(options.url, options.params) |
|||
} |
|||
// 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
|
|||
if (mergeConfig.url === page()) return |
|||
|
|||
if (params.intercept) { |
|||
mergeConfig.intercept = params.intercept |
|||
} |
|||
// params参数也带给拦截器
|
|||
mergeConfig.params = params |
|||
// 合并内外部参数
|
|||
mergeConfig = deepMerge(this.config, mergeConfig) |
|||
// 判断用户是否定义了拦截器
|
|||
if (typeof mergeConfig.intercept === 'function') { |
|||
// 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
|
|||
const isNext = await new Promise((resolve, reject) => { |
|||
mergeConfig.intercept(mergeConfig, resolve) |
|||
}) |
|||
// 如果isNext为true,则执行路由跳转
|
|||
isNext && this.openPage(mergeConfig) |
|||
} else { |
|||
this.openPage(mergeConfig) |
|||
} |
|||
} |
|||
|
|||
// 执行路由跳转
|
|||
openPage(config) { |
|||
// 解构参数
|
|||
const { |
|||
url, |
|||
type, |
|||
delta, |
|||
animationType, |
|||
animationDuration, |
|||
events |
|||
} = config |
|||
if (config.type == 'navigateTo' || config.type == 'to') { |
|||
uni.navigateTo({ |
|||
url, |
|||
animationType, |
|||
animationDuration, |
|||
events |
|||
}) |
|||
} |
|||
if (config.type == 'redirectTo' || config.type == 'redirect') { |
|||
uni.redirectTo({ |
|||
url |
|||
}) |
|||
} |
|||
if (config.type == 'switchTab' || config.type == 'tab') { |
|||
uni.switchTab({ |
|||
url |
|||
}) |
|||
} |
|||
if (config.type == 'reLaunch' || config.type == 'launch') { |
|||
uni.reLaunch({ |
|||
url |
|||
}) |
|||
} |
|||
if (config.type == 'navigateBack' || config.type == 'back') { |
|||
uni.navigateBack({ |
|||
delta |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default (new Router()).route |
|||
@ -0,0 +1,81 @@ |
|||
{ |
|||
"id": "uv-ui-tools", |
|||
"displayName": "uv-ui-tools 工具集 全面兼容vue3+2、app、h5、小程序等多端", |
|||
"version": "1.1.25", |
|||
"description": "uv-ui-tools,集成工具库,强大的Http请求封装,清晰的文档说明,开箱即用。方便使用,可以全局使用", |
|||
"keywords": [ |
|||
"uv-ui-tools,uv-ui组件库,工具集,uvui,uView2.x" |
|||
], |
|||
"repository": "", |
|||
"engines": { |
|||
"HBuilderX": "^3.1.0" |
|||
}, |
|||
"dcloudext": { |
|||
"type": "component-vue", |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "插件不采集任何数据", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y" |
|||
}, |
|||
"client": { |
|||
"Vue": { |
|||
"vue2": "y", |
|||
"vue3": "y" |
|||
}, |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "y" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "y", |
|||
"百度": "y", |
|||
"字节跳动": "y", |
|||
"QQ": "y", |
|||
"钉钉": "y", |
|||
"快手": "y", |
|||
"飞书": "y", |
|||
"京东": "y" |
|||
}, |
|||
"快应用": { |
|||
"华为": "y", |
|||
"联盟": "y" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
## uv-ui-tools 工具集 |
|||
|
|||
> **组件名:uv-ui-tools** |
|||
|
|||
uv-ui工具集成,包括网络Http请求、便捷工具、节流防抖、对象操作、时间格式化、路由跳转、全局唯一标识符、规则校验等等。 |
|||
|
|||
该组件推荐配合[uv-ui组件库](https://www.uvui.cn/components/intro.html)使用,单独下载也可以在自己项目中使用,需要做相应的配置,可查看文档。强烈推荐使用[uv-ui组件库](https://www.uvui.cn/components/intro.html),导入组件都会自动导入`uv-ui-tools`。需要在自己的项目中使用请参考[扩展配置](https://www.uvui.cn/components/setting.html)。 |
|||
|
|||
uv-ui破釜沉舟之兼容vue3+2、app、h5、多端小程序的uni-app生态框架,大部分组件基于uView2.x,在经过改进后全面支持vue3,部分组件做了进一步的优化,修复大量BUG,支持单独导入,方便开发者选择导入需要的组件。开箱即用,灵活配置。 |
|||
|
|||
# <a href="https://www.uvui.cn/js/intro.html" target="_blank">查看文档</a> |
|||
|
|||
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small> |
|||
|
|||
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui) |
|||
|
|||
<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank"> |
|||
|
|||
 |
|||
|
|||
</a> |
|||
|
|||
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a> |
|||
@ -0,0 +1,43 @@ |
|||
// 此文件为uvUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于 |
|||
// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大, |
|||
// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入 |
|||
|
|||
$uv-main-color: #303133; |
|||
$uv-content-color: #606266; |
|||
$uv-tips-color: #909193; |
|||
$uv-light-color: #c0c4cc; |
|||
$uv-border-color: #dadbde; |
|||
$uv-bg-color: #f3f4f6; |
|||
$uv-disabled-color: #c8c9cc; |
|||
|
|||
$uv-primary: #3c9cff; |
|||
$uv-primary-dark: #398ade; |
|||
$uv-primary-disabled: #9acafc; |
|||
$uv-primary-light: #ecf5ff; |
|||
|
|||
$uv-warning: #f9ae3d; |
|||
$uv-warning-dark: #f1a532; |
|||
$uv-warning-disabled: #f9d39b; |
|||
$uv-warning-light: #fdf6ec; |
|||
|
|||
$uv-success: #5ac725; |
|||
$uv-success-dark: #53c21d; |
|||
$uv-success-disabled: #a9e08f; |
|||
$uv-success-light: #f5fff0; |
|||
|
|||
$uv-error: #f56c6c; |
|||
$uv-error-dark: #e45656; |
|||
$uv-error-disabled: #f7b2b2; |
|||
$uv-error-light: #fef0f0; |
|||
|
|||
$uv-info: #909399; |
|||
$uv-info-dark: #767a82; |
|||
$uv-info-disabled: #c4c6c9; |
|||
$uv-info-light: #f4f4f5; |
|||
|
|||
@mixin flex($direction: row) { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: $direction; |
|||
} |
|||
Loading…
Reference in new issue