This commit is contained in:
2026-07-20 11:56:02 +08:00
commit 2dcae4165b
176 changed files with 34633 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
## 1.0.132023-12-06
1. 优化
## 1.0.122023-12-06
1. 阻止事件冒泡处理
## 1.0.112023-10-29
1. imgMode默认值改成aspectFit
## 1.0.102023-08-13
1. 优化nvue方便自定义图标
## 1.0.92023-07-28
1. 修改几个对应错误图标的BUG
## 1.0.82023-07-24
1. 优化 支持base64图片
## 1.0.72023-07-17
1. 修复 uv-icon 恢复uv-empty相关的图标
## 1.0.62023-07-13
1. 修复icon设置name属性对应图标错误的BUG
## 1.0.52023-07-04
1. 更新图标,删除一些不常用的图标
2. 删除base64修改成ttf文件引入读取图标
3. 自定义图标文档说明https://www.uvui.cn/guide/customIcon.html
## 1.0.42023-07-03
1. 修复主题颜色在APP不生效的BUG
## 1.0.32023-05-24
1. 将线上ttf字体包替换成base64避免加载时或者网络差时候显示白色方块
## 1.0.22023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.12023-05-10
1. 修复小程序中异常显示
## 1.0.02023-05-04
新发版

View File

@@ -0,0 +1,160 @@
export default {
'uvicon-level': 'e68f',
'uvicon-checkbox-mark': 'e659',
'uvicon-folder': 'e694',
'uvicon-movie': 'e67c',
'uvicon-star-fill': 'e61e',
'uvicon-star': 'e618',
'uvicon-phone-fill': 'e6ac',
'uvicon-phone': 'e6ba',
'uvicon-apple-fill': 'e635',
'uvicon-backspace': 'e64d',
'uvicon-attach': 'e640',
'uvicon-empty-data': 'e671',
'uvicon-empty-address': 'e68a',
'uvicon-empty-favor': 'e662',
'uvicon-empty-car': 'e657',
'uvicon-empty-order': 'e66b',
'uvicon-empty-list': 'e672',
'uvicon-empty-search': 'e677',
'uvicon-empty-permission': 'e67d',
'uvicon-empty-news': 'e67e',
'uvicon-empty-history': 'e685',
'uvicon-empty-coupon': 'e69b',
'uvicon-empty-page': 'e60e',
'uvicon-empty-wifi-off': 'e6cc',
'uvicon-reload': 'e627',
'uvicon-order': 'e695',
'uvicon-server-man': 'e601',
'uvicon-search': 'e632',
'uvicon-more-dot-fill': 'e66f',
'uvicon-scan': 'e631',
'uvicon-map': 'e665',
'uvicon-map-fill': 'e6a8',
'uvicon-tags': 'e621',
'uvicon-tags-fill': 'e613',
'uvicon-eye': 'e664',
'uvicon-eye-fill': 'e697',
'uvicon-eye-off': 'e69c',
'uvicon-eye-off-outline': 'e688',
'uvicon-mic': 'e66d',
'uvicon-mic-off': 'e691',
'uvicon-calendar': 'e65c',
'uvicon-trash': 'e623',
'uvicon-trash-fill': 'e6ce',
'uvicon-play-left': 'e6bf',
'uvicon-play-right': 'e6b3',
'uvicon-minus': 'e614',
'uvicon-plus': 'e625',
'uvicon-info-circle': 'e69f',
'uvicon-info-circle-fill': 'e6a7',
'uvicon-question-circle': 'e622',
'uvicon-question-circle-fill': 'e6bc',
'uvicon-close': 'e65a',
'uvicon-checkmark': 'e64a',
'uvicon-checkmark-circle': 'e643',
'uvicon-checkmark-circle-fill': 'e668',
'uvicon-setting': 'e602',
'uvicon-setting-fill': 'e6d0',
'uvicon-heart': 'e6a2',
'uvicon-heart-fill': 'e68b',
'uvicon-camera': 'e642',
'uvicon-camera-fill': 'e650',
'uvicon-more-circle': 'e69e',
'uvicon-more-circle-fill': 'e684',
'uvicon-chat': 'e656',
'uvicon-chat-fill': 'e63f',
'uvicon-bag': 'e647',
'uvicon-error-circle': 'e66e',
'uvicon-error-circle-fill': 'e655',
'uvicon-close-circle': 'e64e',
'uvicon-close-circle-fill': 'e666',
'uvicon-share': 'e629',
'uvicon-share-fill': 'e6bb',
'uvicon-share-square': 'e6c4',
'uvicon-shopping-cart': 'e6cb',
'uvicon-shopping-cart-fill': 'e630',
'uvicon-bell': 'e651',
'uvicon-bell-fill': 'e604',
'uvicon-list': 'e690',
'uvicon-list-dot': 'e6a9',
'uvicon-zhifubao-circle-fill': 'e617',
'uvicon-weixin-circle-fill': 'e6cd',
'uvicon-weixin-fill': 'e620',
'uvicon-qq-fill': 'e608',
'uvicon-qq-circle-fill': 'e6b9',
'uvicon-moments-circel-fill': 'e6c2',
'uvicon-moments': 'e6a0',
'uvicon-car': 'e64f',
'uvicon-car-fill': 'e648',
'uvicon-warning-fill': 'e6c7',
'uvicon-warning': 'e6c1',
'uvicon-clock-fill': 'e64b',
'uvicon-clock': 'e66c',
'uvicon-edit-pen': 'e65d',
'uvicon-edit-pen-fill': 'e679',
'uvicon-email': 'e673',
'uvicon-email-fill': 'e683',
'uvicon-minus-circle': 'e6a5',
'uvicon-plus-circle': 'e603',
'uvicon-plus-circle-fill': 'e611',
'uvicon-file-text': 'e687',
'uvicon-file-text-fill': 'e67f',
'uvicon-pushpin': 'e6d1',
'uvicon-pushpin-fill': 'e6b6',
'uvicon-grid': 'e68c',
'uvicon-grid-fill': 'e698',
'uvicon-play-circle': 'e6af',
'uvicon-play-circle-fill': 'e62a',
'uvicon-pause-circle-fill': 'e60c',
'uvicon-pause': 'e61c',
'uvicon-pause-circle': 'e696',
'uvicon-gift-fill': 'e6b0',
'uvicon-gift': 'e680',
'uvicon-kefu-ermai': 'e660',
'uvicon-server-fill': 'e610',
'uvicon-coupon-fill': 'e64c',
'uvicon-coupon': 'e65f',
'uvicon-integral': 'e693',
'uvicon-integral-fill': 'e6b1',
'uvicon-home-fill': 'e68e',
'uvicon-home': 'e67b',
'uvicon-account': 'e63a',
'uvicon-account-fill': 'e653',
'uvicon-thumb-down-fill': 'e628',
'uvicon-thumb-down': 'e60a',
'uvicon-thumb-up': 'e612',
'uvicon-thumb-up-fill': 'e62c',
'uvicon-lock-fill': 'e6a6',
'uvicon-lock-open': 'e68d',
'uvicon-lock-opened-fill': 'e6a1',
'uvicon-lock': 'e69d',
'uvicon-red-packet': 'e6c3',
'uvicon-photo-fill': 'e6b4',
'uvicon-photo': 'e60d',
'uvicon-volume-off-fill': 'e6c8',
'uvicon-volume-off': 'e6bd',
'uvicon-volume-fill': 'e624',
'uvicon-volume': 'e605',
'uvicon-download': 'e670',
'uvicon-arrow-up-fill': 'e636',
'uvicon-arrow-down-fill': 'e638',
'uvicon-play-left-fill': 'e6ae',
'uvicon-play-right-fill': 'e6ad',
'uvicon-arrow-downward': 'e634',
'uvicon-arrow-leftward': 'e63b',
'uvicon-arrow-rightward': 'e644',
'uvicon-arrow-upward': 'e641',
'uvicon-arrow-down': 'e63e',
'uvicon-arrow-right': 'e63c',
'uvicon-arrow-left': 'e646',
'uvicon-arrow-up': 'e633',
'uvicon-skip-back-left': 'e6c5',
'uvicon-skip-forward-right': 'e61f',
'uvicon-arrow-left-double': 'e637',
'uvicon-man': 'e675',
'uvicon-woman': 'e626',
'uvicon-en': 'e6b8',
'uvicon-twitte': 'e607',
'uvicon-twitter-circle-fill': 'e6cf'
}

View File

@@ -0,0 +1,90 @@
export default {
props: {
// 图标类名
name: {
type: String,
default: ''
},
// 图标颜色,可接受主题色
color: {
type: String,
default: '#606266'
},
// 字体大小单位px
size: {
type: [String, Number],
default: '16px'
},
// 是否显示粗体
bold: {
type: Boolean,
default: false
},
// 点击图标的时候传递事件出去的index用于区分点击了哪一个
index: {
type: [String, Number],
default: null
},
// 触摸图标时的类名
hoverClass: {
type: String,
default: ''
},
// 自定义扩展前缀,方便用户扩展自己的图标库
customPrefix: {
type: String,
default: 'uvicon'
},
// 图标右边或者下面的文字
label: {
type: [String, Number],
default: ''
},
// label的位置只能右边或者下边
labelPos: {
type: String,
default: 'right'
},
// label的大小
labelSize: {
type: [String, Number],
default: '15px'
},
// label的颜色
labelColor: {
type: String,
default: '#606266'
},
// label与图标的距离
space: {
type: [String, Number],
default: '3px'
},
// 图片的mode
imgMode: {
type: String,
default: 'aspectFit'
},
// 用于显示图片小图标时,图片的宽度
width: {
type: [String, Number],
default: ''
},
// 用于显示图片小图标时,图片的高度
height: {
type: [String, Number],
default: ''
},
// 用于解决某些情况下,让图标垂直居中的用途
top: {
type: [String, Number],
default: 0
},
// 是否阻止事件传播
stop: {
type: Boolean,
default: false
},
...uni.$uv?.props?.icon
}
}

View File

@@ -0,0 +1,226 @@
<template>
<view
class="uv-icon"
@tap="clickHandler"
:class="['uv-icon--' + labelPos]"
>
<image
class="uv-icon__img"
v-if="isImg"
:src="name"
:mode="imgMode"
:style="[imgStyle, $uv.addStyle(customStyle)]"
></image>
<text
v-else
class="uv-icon__icon"
:class="uClasses"
:style="[iconStyle, $uv.addStyle(customStyle)]"
:hover-class="hoverClass"
>{{icon}}</text>
<!-- 这里进行空字符串判断如果仅仅是v-if="label"可能会出现传递0的时候结果也无法显示 -->
<text
v-if="label !== ''"
class="uv-icon__label"
:style="{
color: labelColor,
fontSize: $uv.addUnit(labelSize),
marginLeft: labelPos == 'right' ? $uv.addUnit(space) : 0,
marginTop: labelPos == 'bottom' ? $uv.addUnit(space) : 0,
marginRight: labelPos == 'left' ? $uv.addUnit(space) : 0,
marginBottom: labelPos == 'top' ? $uv.addUnit(space) : 0
}"
>{{ label }}</text>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
// #ifdef APP-NVUE
// nvue通过weex的dom模块引入字体相关文档地址如下
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
import iconUrl from './uvicons.ttf';
const domModule = weex.requireModule('dom')
domModule.addRule('fontFace', {
'fontFamily': "uvicon-iconfont",
'src': "url('" + iconUrl + "')"
})
// #endif
// 引入图标名称已经对应的unicode
import icons from './icons';
import props from './props.js';
/**
* icon 图标
* @description 基于字体的图标集,包含了大多数常见场景的图标。
* @tutorial https://www.uvui.cn/components/icon.html
* @property {String} name 图标名称,见示例图标集
* @property {String} color 图标颜色,可接受主题色 (默认 color['uv-content-color']
* @property {String | Number} size 图标字体大小单位px (默认 '16px'
* @property {Boolean} bold 是否显示粗体 (默认 false
* @property {String | Number} index 点击图标的时候传递事件出去的index用于区分点击了哪一个
* @property {String} hoverClass 图标按下去的样式类用法同uni的view组件的hoverClass参数详情见官网
* @property {String} customPrefix 自定义扩展前缀,方便用户扩展自己的图标库 (默认 'uicon'
* @property {String | Number} label 图标右侧的label文字
* @property {String} labelPos label相对于图标的位置只能right或bottom (默认 'right'
* @property {String | Number} labelSize label字体大小单位px (默认 '15px'
* @property {String} labelColor 图标右侧的label文字颜色 默认 color['uv-content-color']
* @property {String | Number} space label与图标的距离单位px (默认 '3px'
* @property {String} imgMode 图片的mode
* @property {String | Number} width 显示图片小图标时的宽度
* @property {String | Number} height 显示图片小图标时的高度
* @property {String | Number} top 图标在垂直方向上的定位 用于解决某些情况下,让图标垂直居中的用途 (默认 0
* @property {Boolean} stop 是否阻止事件传播 (默认 false
* @property {Object} customStyle icon的样式对象形式
* @event {Function} click 点击图标时触发
* @event {Function} touchstart 事件触摸时触发
* @example <uv-icon name="photo" color="#2979ff" size="28"></uv-icon>
*/
export default {
name: 'uv-icon',
emits: ['click'],
mixins: [mpMixin, mixin, props],
data() {
return {
colorType: [
'primary',
'success',
'info',
'error',
'warning'
]
}
},
computed: {
uClasses() {
let classes = []
classes.push(this.customPrefix)
classes.push(this.customPrefix + '-' + this.name)
// 主题色,通过类配置
if (this.color && this.colorType.includes(this.color)) classes.push('uv-icon__icon--' + this.color)
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
classes = classes.join(' ')
//#endif
return classes
},
iconStyle() {
let style = {}
style = {
fontSize: this.$uv.addUnit(this.size),
lineHeight: this.$uv.addUnit(this.size),
fontWeight: this.bold ? 'bold' : 'normal',
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
top: this.$uv.addUnit(this.top)
}
// 非主题色值时,才当作颜色值
if (this.color && !this.colorType.includes(this.color)) style.color = this.color
return style
},
// 判断传入的name属性是否图片路径只要带有"/"均认为是图片形式
isImg() {
const isBase64 = this.name.indexOf('data:') > -1 && this.name.indexOf('base64') > -1;
return this.name.indexOf('/') !== -1 || isBase64;
},
imgStyle() {
let style = {}
// 如果设置width和height属性则优先使用否则使用size属性
style.width = this.width ? this.$uv.addUnit(this.width) : this.$uv.addUnit(this.size)
style.height = this.height ? this.$uv.addUnit(this.height) : this.$uv.addUnit(this.size)
return style
},
// 通过图标名,查找对应的图标
icon() {
// 如果内置的图标中找不到对应的图标就直接返回name值因为用户可能传入的是unicode代码
const code = icons['uvicon-' + this.name];
// #ifdef APP-NVUE
if(!code) {
return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? unescape(`%u${this.name}`) : '';
}
// #endif
return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? this.name : '';
}
},
methods: {
clickHandler(e) {
this.$emit('click', this.index)
// 是否阻止事件冒泡
this.stop && this.preventEvent(e)
}
}
}
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
// 变量定义
$uv-icon-primary: $uv-primary !default;
$uv-icon-success: $uv-success !default;
$uv-icon-info: $uv-info !default;
$uv-icon-warning: $uv-warning !default;
$uv-icon-error: $uv-error !default;
$uv-icon-label-line-height: 1 !default;
/* #ifndef APP-NVUE */
// 非nvue下加载字体
@font-face {
font-family: 'uvicon-iconfont';
src: url('./uvicons.ttf') format('truetype');
}
/* #endif */
.uv-icon {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
&--left {
flex-direction: row-reverse;
align-items: center;
}
&--right {
flex-direction: row;
align-items: center;
}
&--top {
flex-direction: column-reverse;
justify-content: center;
}
&--bottom {
flex-direction: column;
justify-content: center;
}
&__icon {
font-family: uvicon-iconfont;
position: relative;
@include flex;
align-items: center;
&--primary {
color: $uv-icon-primary;
}
&--success {
color: $uv-icon-success;
}
&--error {
color: $uv-icon-error;
}
&--warning {
color: $uv-icon-warning;
}
&--info {
color: $uv-icon-info;
}
}
&__img {
/* #ifndef APP-NVUE */
height: auto;
will-change: transform;
/* #endif */
}
&__label {
/* #ifndef APP-NVUE */
line-height: $uv-icon-label-line-height;
/* #endif */
}
}
</style>

Binary file not shown.

View File

@@ -0,0 +1,83 @@
{
"id": "uv-icon",
"displayName": "uv-icon 图标 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.13",
"description": "基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。可自定义颜色、大小。",
"keywords": [
"uv-ui,uvui,uv-icon,icon,图标,字体图标"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
## uv-icon 图标库
> **组件名uv-icon**
基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。
# <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
### [更多插件请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,29 @@
## 1.0.132023-12-06
1. 优化
## 1.0.122023-12-06
1. 阻止事件冒泡问题
## 1.0.112023-11-10
1. 调整清除按钮样式的marginLeft避免微信上多数情况触发不了的BUG
## 1.0.102023-10-07
1. 修复搜狗输入法下存在不可清空的情况
## 1.0.92023-09-14
1. 修复H5等情况设置禁用或可读情况下点击事件无效的问题
## 1.0.82023-08-22
1. 修复无法@keyboardheightchange无法获取键盘高度的BUG
## 1.0.72023-08-18
1. 修复ios端不能输入的BUG
## 1.0.62023-08-05
1. 修复在vue2模式下v-model设置为0时不生效的BUG
## 1.0.52023-07-18
1. 修复在微信小程序端清除内容存在不能清除的BUG
## 1.0.42023-07-13
1. 修复value/v-model更改不生效的BUG
## 1.0.32023-07-03
去除插槽判断避免某些平台不显示的BUG
## 1.0.22023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.12023-05-12
1. 修复vue3双向绑定的BUG
## 1.0.02023-05-10
uv-input 输入框

View File

@@ -0,0 +1,175 @@
export default {
props: {
value: {
type: [String, Number],
default: ''
},
modelValue: {
type: [String, Number],
default: ''
},
// 输入框类型
// number-数字输入键盘app-vue下可以输入浮点数app-nvue和小程序平台下只能输入整数
// idcard-身份证输入键盘微信、支付宝、百度、QQ小程序
// digit-带小数点的数字键盘App的nvue页面、微信、支付宝、百度、头条、QQ小程序
// text-文本输入键盘
type: {
type: String,
default: 'text'
},
// 是否禁用输入框
disabled: {
type: Boolean,
default: false
},
// 禁用状态时的背景色
disabledColor: {
type: String,
default: '#f5f7fa'
},
// 是否显示清除控件
clearable: {
type: Boolean,
default: false
},
// 是否密码类型
password: {
type: Boolean,
default: false
},
// 最大输入长度,设置为 -1 的时候不限制最大长度
maxlength: {
type: [String, Number],
default: -1
},
// 输入框为空时的占位符
placeholder: {
type: String,
default: null
},
// 指定placeholder的样式类注意页面或组件的style中写了scoped时需要在类名前写/deep/
placeholderClass: {
type: String,
default: 'input-placeholder'
},
// 指定placeholder的样式
placeholderStyle: {
type: [String, Object],
default: 'color: #c0c4cc'
},
// 设置右下角按钮的文字有效值send|search|next|go|done兼容性详见uni-app文档
// https://uniapp.dcloud.io/component/input
// https://uniapp.dcloud.io/component/textarea
confirmType: {
type: String,
default: 'done'
},
// 点击键盘右下角按钮时是否保持键盘不收起H5无效
confirmHold: {
type: Boolean,
default: false
},
// focus时点击页面的时候不收起键盘微信小程序有效
holdKeyboard: {
type: Boolean,
default: false
},
// 自动获取焦点
// 在 H5 平台能否聚焦以及软键盘是否跟随弹出取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点
focus: {
type: Boolean,
default: false
},
// 键盘收起时是否自动失去焦点目前仅App3.0.0+有效
autoBlur: {
type: Boolean,
default: false
},
// 指定focus时光标的位置
cursor: {
type: [String, Number],
default: -1
},
// 输入框聚焦时底部与键盘的距离
cursorSpacing: {
type: [String, Number],
default: 30
},
// 光标起始位置自动聚集时有效需与selection-end搭配使用
selectionStart: {
type: [String, Number],
default: -1
},
// 光标结束位置自动聚集时有效需与selection-start搭配使用
selectionEnd: {
type: [String, Number],
default: -1
},
// 键盘弹起时,是否自动上推页面
adjustPosition: {
type: Boolean,
default: true
},
// 输入框内容对齐方式可选值为left|center|right
inputAlign: {
type: String,
default: 'left'
},
// 输入框字体的大小
fontSize: {
type: [String, Number],
default: '14px'
},
// 输入框字体颜色
color: {
type: String,
default: '#303133'
},
// 输入框前置图标
prefixIcon: {
type: String,
default: ''
},
// 前置图标样式,对象或字符串
prefixIconStyle: {
type: [String, Object],
default: ''
},
// 输入框后置图标
suffixIcon: {
type: String,
default: ''
},
// 后置图标样式,对象或字符串
suffixIconStyle: {
type: [String, Object],
default: ''
},
// 边框类型surround-四周边框bottom-底部边框none-无边框
border: {
type: String,
default: 'surround'
},
// 是否只读与disabled不同之处在于disabled会置灰组件而readonly则不会
readonly: {
type: Boolean,
default: false
},
// 输入框形状circle-圆形square-方形
shape: {
type: String,
default: 'square'
},
// 用于处理或者过滤输入框内容的方法
formatter: {
type: [Function, null],
default: null
},
// 是否忽略组件内对文本合成系统事件的处理
ignoreCompositionEvent: {
type: Boolean,
default: true
},
...uni.$uv?.props?.input
}
}

View File

@@ -0,0 +1,348 @@
<template>
<view class="uv-input" :class="inputClass" :style="[wrapperStyle]">
<view class="uv-input__content">
<view class="uv-input__content__prefix-icon">
<slot name="prefix">
<uv-icon
v-if="prefixIcon"
:name="prefixIcon"
size="18"
:customStyle="prefixIconStyle"
></uv-icon>
</slot>
</view>
<view class="uv-input__content__field-wrapper" @click="clickHandler">
<!-- 根据uni-app的input组件文档H5和APP中只要声明了password参数(无论true还是false)type均失效此时
为了防止type=number时又存在password属性type无效此时需要设置password为undefined
-->
<input
class="uv-input__content__field-wrapper__field"
:style="[inputStyle]"
:type="type"
:focus="focus"
:cursor="cursor"
:value="innerValue"
:auto-blur="autoBlur"
:disabled="disabled || readonly"
:maxlength="maxlength"
:placeholder="placeholder"
:placeholder-style="placeholderStyle"
:placeholder-class="placeholderClass"
:confirm-type="confirmType"
:confirm-hold="confirmHold"
:hold-keyboard="holdKeyboard"
:cursor-spacing="cursorSpacing"
:adjust-position="adjustPosition"
:selection-end="selectionEnd"
:selection-start="selectionStart"
:password="password || type === 'password' || undefined"
:ignoreCompositionEvent="ignoreCompositionEvent"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
@confirm="onConfirm"
@keyboardheightchange="onkeyboardheightchange"
/>
</view>
<view
class="uv-input__content__clear"
v-if="isShowClear"
@tap="onClear"
>
<uv-icon
name="close"
size="11"
color="#ffffff"
customStyle="line-height: 12px"
></uv-icon>
</view>
<view class="uv-input__content__subfix-icon">
<slot name="suffix">
<uv-icon
v-if="suffixIcon"
:name="suffixIcon"
size="18"
:customStyle="suffixIconStyle"
></uv-icon>
</slot>
</view>
</view>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from "./props.js";
/**
* Input 输入框
* @description 此组件为一个输入框默认没有边框和样式是专门为配合表单组件uv-form而设计的利用它可以快速实现表单验证输入内容下拉选择等功能。
* @tutorial https://www.uvui.cn/components/input.html
* @property {String | Number} value 输入的值
* @property {String} type 输入框类型,见上方说明 默认 'text'
* @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true兼容性微信小程序、百度小程序、字节跳动小程序、QQ小程序 默认 false
* @property {Boolean} disabled 是否禁用输入框 默认 false
* @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa'
* @property {Boolean} clearable 是否显示清除控件 默认 false
* @property {Boolean} password 是否密码类型 默认 false
* @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 默认 -1
* @property {String} placeholder 输入框为空时的占位符
* @property {String} placeholderClass 指定placeholder的样式类注意页面或组件的style中写了scoped时需要在类名前写/deep/ 默认 'input-placeholder'
* @property {String | Object} placeholderStyle 指定placeholder的样式字符串/对象形式,如"color: red;"
* @property {Boolean} showWordLimit 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 默认 false
* @property {String} confirmType 设置右下角按钮的文字兼容性详见uni-app文档 默认 'done'
* @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起H5无效 默认 false
* @property {Boolean} holdKeyboard focus时点击页面的时候不收起键盘微信小程序有效 默认 false
* @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 默认 false
* @property {Boolean} autoBlur 键盘收起时是否自动失去焦点目前仅App3.0.0+有效 默认 false
* @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距仅微信小程序且type=textarea时有效 默认 false
* @property {String Number} cursor 指定focus时光标的位置 默认 -1
* @property {String Number} cursorSpacing 输入框聚焦时底部与键盘的距离 默认 30
* @property {String Number} selectionStart 光标起始位置自动聚集时有效需与selection-end搭配使用 默认 -1
* @property {String Number} selectionEnd 光标结束位置自动聚集时有效需与selection-start搭配使用 默认 -1
* @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 默认 true
* @property {String} inputAlign 输入框内容对齐方式( 默认 'left'
* @property {String | Number} fontSize 输入框字体的大小 默认 '15px'
* @property {String} color 输入框字体颜色 默认 '#303133'
* @property {Function} formatter 内容式化函数
* @property {String} prefixIcon 输入框前置图标
* @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
* @property {String} suffixIcon 输入框后置图标
* @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
* @property {String} border 边框类型surround-四周边框bottom-底部边框none-无边框 默认 'surround'
* @property {Boolean} readonly 是否只读与disabled不同之处在于disabled会置灰组件而readonly则不会 默认 false
* @property {String} shape 输入框形状circle-圆形square-方形 默认 'square'
* @property {Object} customStyle 定义需要用到的外部样式
* @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
* @example <uv-input v-model="value" :password="true" suffix-icon="lock-fill" />
*/
export default {
name: "uv-input",
mixins: [mpMixin, mixin, props],
data() {
return {
// 输入框的值
innerValue: "",
// 是否处于获得焦点状态
focused: false,
// 过滤处理方法
innerFormatter: value => value
}
},
created() {
// #ifdef VUE2
this.innerValue = this.value;
// #endif
// #ifdef VUE3
this.innerValue = this.modelValue;
// #endif
},
watch: {
value(newVal){
this.innerValue = newVal;
},
modelValue(newVal){
this.innerValue = newVal;
}
},
computed: {
// 是否显示清除控件
isShowClear() {
const { clearable, readonly, focused, innerValue } = this;
return !!clearable && !readonly && !!focused && innerValue !== "";
},
// 组件的类名
inputClass() {
let classes = [],
{ border, disabled, shape } = this;
border === "surround" &&
(classes = classes.concat(["uv-border", "uv-input--radius"]));
classes.push(`uv-input--${shape}`);
border === "bottom" &&
(classes = classes.concat([
"uv-border-bottom",
"uv-input--no-radius",
]));
return classes.join(" ");
},
// 组件的样式
wrapperStyle() {
const style = {};
// 禁用状态下,被背景色加上对应的样式
if (this.disabled) {
style.backgroundColor = this.disabledColor;
}
// 无边框时,去除内边距
if (this.border === "none") {
style.padding = "0";
} else {
// 由于uni-app的iOS开发者能力有限导致需要分开写才有效
style.paddingTop = "6px";
style.paddingBottom = "6px";
style.paddingLeft = "9px";
style.paddingRight = "9px";
}
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
},
// 输入框的样式
inputStyle() {
const style = {
color: this.color,
fontSize: this.$uv.addUnit(this.fontSize),
textAlign: this.inputAlign
};
// #ifndef APP-NVUE
if(this.disabled || this.readonly) {
style['pointer-events'] = 'none';
}
// #endif
return style;
}
},
methods: {
// 在微信小程序中不支持将函数当做props参数故只能通过ref形式调用
setFormatter(e) {
this.innerFormatter = e
},
// 当键盘输入时触发input事件
onInput(e) {
let { value = "" } = e.detail || {};
// 格式化过滤方法
const formatter = this.formatter || this.innerFormatter
const formatValue = formatter(value)
// 为了避免props的单向数据流特性需要先将innerValue值设置为当前值再在$nextTick中重新赋予设置后的值才有效
this.innerValue = value
this.$nextTick(() => {
this.innerValue = formatValue;
this.valueChange();
})
},
// 输入框失去焦点时触发
onBlur(event) {
this.$emit("blur", event.detail.value);
// H5端的blur会先于点击清除控件的点击click事件触发导致focused
// 瞬间为false从而隐藏了清除控件而无法被点击到
this.$uv.sleep(100).then(() => {
this.focused = false;
});
// 尝试调用uv-form的验证方法
this.$uv.formValidate(this, "blur");
},
// 输入框聚焦时触发
onFocus(event) {
this.focused = true;
this.$emit("focus");
},
// 点击完成按钮时触发
onConfirm(event) {
this.$emit("confirm", this.innerValue);
},
// 键盘高度发生变化的时候触发此事件
// 兼容性微信小程序2.7.0+、App 3.1.0+
onkeyboardheightchange(e) {
this.$emit("keyboardheightchange",e);
},
// 内容发生变化,进行处理
valueChange() {
if(this.isClear) this.innerValue = '';
const value = this.innerValue;
this.$nextTick(() => {
this.$emit("input", value);
this.$emit("update:modelValue", value);
this.$emit("change", value);
// 尝试调用uv-form的验证方法
this.$uv.formValidate(this, "change");
});
},
// 点击清除控件
onClear() {
this.innerValue = "";
this.isClear = true;
this.$uv.sleep(200).then(res=>{
this.isClear = false;
})
this.$nextTick(() => {
this.$emit("clear");
this.valueChange();
});
},
/**
* 在安卓nvue上事件无法冒泡
* 在某些时间我们希望监听uv-from-item的点击事件此时会导致点击uv-form-item内的uv-input后
* 无法触发uv-form-item的点击事件这里通过手动调用uv-form-item的方法进行触发
*/
clickHandler() {
// #ifdef APP-NVUE
if (this.$uv.os() === "android") {
const formItem = this.$uv.$parent.call(this, "uv-form-item");
if (formItem) {
formItem.clickHandler();
}
}
// #endif
}
}
}
</script>
<style lang="scss" scoped>
$show-border: 1;
$show-border-surround: 1;
$show-border-bottom: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
.uv-input {
@include flex(row);
align-items: center;
justify-content: space-between;
flex: 1;
&--radius,
&--square {
border-radius: 4px;
}
&--no-radius {
border-radius: 0;
}
&--circle {
border-radius: 100px;
}
&__content {
flex: 1;
@include flex(row);
align-items: center;
justify-content: space-between;
&__field-wrapper {
position: relative;
@include flex(row);
margin: 0;
flex: 1;
&__field {
line-height: 26px;
text-align: left;
color: $uv-main-color;
height: 24px;
font-size: 15px;
flex: 1;
}
}
&__clear {
width: 20px;
height: 20px;
border-radius: 100px;
background-color: #c6c7cb;
@include flex(row);
align-items: center;
justify-content: center;
transform: scale(0.82);
margin-left: 15px;
}
&__subfix-icon {
margin-left: 4px;
}
&__prefix-icon {
margin-right: 4px;
}
}
}
</style>

View File

@@ -0,0 +1,88 @@
{
"id": "uv-input",
"displayName": "uv-input 输入框 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.13",
"description": "uv-input 该组件为一个输入框默认没有边框和样式是专门为配合表单组件uv-form而设计的利用它可以快速实现表单验证输入内容下拉选择等功能。",
"keywords": [
"uv-input",
"uvui",
"uv-ui",
"input",
"输入框"
],
"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",
"uv-icon"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
## Input 输入框
> **组件名uv-input**
此组件为一个输入框默认没有边框和样式是专门为配合表单组件uv-form而设计的利用它可以快速实现表单验证输入内容下拉选择等功能。
# <a href="https://www.uvui.cn/components/input.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">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,9 @@
## 1.0.32023-08-14
1. 新增参数textStyle自定义文本样式
## 1.0.22023-06-27
优化
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
1. 新增uv-loading-icon组件

View File

@@ -0,0 +1,67 @@
export default {
props: {
// 是否显示组件
show: {
type: Boolean,
default: true
},
// 颜色
color: {
type: String,
default: '#909193'
},
// 提示文字颜色
textColor: {
type: String,
default: '#909193'
},
// 文字和图标是否垂直排列
vertical: {
type: Boolean,
default: false
},
// 模式选择circle-圆形spinner-花朵形semicircle-半圆形
mode: {
type: String,
default: 'spinner'
},
// 图标大小单位默认px
size: {
type: [String, Number],
default: 24
},
// 文字大小
textSize: {
type: [String, Number],
default: 15
},
// 文字样式
textStyle: {
type: Object,
default () {
return {}
}
},
// 文字内容
text: {
type: [String, Number],
default: ''
},
// 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
timingFunction: {
type: String,
default: 'linear'
},
// 动画执行周期时间
duration: {
type: [String, Number],
default: 1200
},
// mode=circle时的暗边颜色
inactiveColor: {
type: String,
default: ''
},
...uni.$uv?.props?.loadingIcon
}
}

View File

@@ -0,0 +1,347 @@
<template>
<view
class="uv-loading-icon"
:style="[$uv.addStyle(customStyle)]"
:class="[vertical && 'uv-loading-icon--vertical']"
v-if="show"
>
<view
v-if="!webviewHide"
class="uv-loading-icon__spinner"
:class="[`uv-loading-icon__spinner--${mode}`]"
ref="ani"
:style="{
color: color,
width: $uv.addUnit(size),
height: $uv.addUnit(size),
borderTopColor: color,
borderBottomColor: otherBorderColor,
borderLeftColor: otherBorderColor,
borderRightColor: otherBorderColor,
'animation-duration': `${duration}ms`,
'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
}"
>
<block v-if="mode === 'spinner'">
<!-- #ifndef APP-NVUE -->
<view
v-for="(item, index) in array12"
:key="index"
class="uv-loading-icon__dot"
>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<!-- 此组件内部图标部分无法设置宽高即使通过width和height配置了也无效 -->
<loading-indicator
v-if="!webviewHide"
class="uv-loading-indicator"
:animating="true"
:style="{
color: color,
width: $uv.addUnit(size),
height: $uv.addUnit(size)
}"
/>
<!-- #endif -->
</block>
</view>
<text
v-if="text"
class="uv-loading-icon__text"
:style="[{
fontSize: $uv.addUnit(textSize),
color: textColor,
},$uv.addStyle(textStyle)]"
>{{text}}</text>
</view>
</template>
<script>
import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
// #ifdef APP-NVUE
const animation = weex.requireModule('animation');
// #endif
/**
* loading 加载动画
* @description 警此组件为一个小动画目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
* @tutorial https://www.uvui.cn/components/loading.html
* @property {Boolean} show 是否显示组件 (默认 true)
* @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认#909193
* @property {String} textColor 提示文本的颜色(默认#909193
* @property {Boolean} vertical 文字和图标是否垂直排列 (默认 false )
* @property {String} mode 模式选择,见官网说明(默认 'circle'
* @property {String | Number} size 加载图标的大小单位px (默认 24
* @property {String | Number} textSize 文字大小(默认 15
* @property {String | Number} text 文字内容
* @property {Object} textStyle 文字样式
* @property {String} timingFunction 动画模式 (默认 'ease-in-out'
* @property {String | Number} duration 动画执行周期时间(默认 1200
* @property {String} inactiveColor mode=circle时的暗边颜色
* @property {Object} customStyle 定义需要用到的外部样式
* @example <uv-loading mode="circle"></uv-loading>
*/
export default {
name: 'uv-loading-icon',
mixins: [mpMixin, mixin, props],
data() {
return {
// Array.form可以通过一个伪数组对象创建指定长度的数组
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
array12: Array.from({
length: 12
}),
// 这里需要设置默认值为360否则在安卓nvue上会延迟一个duration周期后才执行
// 在iOS nvue上则会一开始默认执行两个周期的动画
aniAngel: 360, // 动画旋转角度
webviewHide: false, // 监听webview的状态如果隐藏了页面则停止动画以免性能消耗
loading: false, // 是否运行中针对nvue使用
}
},
computed: {
// 当为circle类型时给其另外三边设置一个更轻一些的颜色
// 之所以需要这么做的原因是比如父组件传了color为红色那么需要另外的三个边为浅红色
// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
otherBorderColor() {
const lightColor = colorGradient(this.color, '#ffffff', 100)[80]
if (this.mode === 'circle') {
return this.inactiveColor ? this.inactiveColor : lightColor
} else {
return 'transparent'
}
}
},
watch: {
show(n) {
// nvue中show为true且为非loading状态就重新执行动画模块
// #ifdef APP-NVUE
if (n && !this.loading) {
setTimeout(() => {
this.startAnimate()
}, 30)
}
// #endif
}
},
mounted() {
this.init()
},
methods: {
init() {
setTimeout(() => {
// #ifdef APP-NVUE
this.show && this.nvueAnimate()
// #endif
// #ifdef APP-PLUS
this.show && this.addEventListenerToWebview()
// #endif
}, 20)
},
// 监听webview的显示与隐藏
addEventListenerToWebview() {
// webview的堆栈
const pages = getCurrentPages()
// 当前页面
const page = pages[pages.length - 1]
// 当前页面的webview实例
const currentWebview = page.$getAppWebview()
// 监听webview的显示与隐藏从而停止或者开始动画(为了性能)
currentWebview.addEventListener('hide', () => {
this.webviewHide = true
})
currentWebview.addEventListener('show', () => {
this.webviewHide = false
})
},
// #ifdef APP-NVUE
nvueAnimate() {
// nvue下非spinner类型时才需要旋转因为nvue的spinner类型使用了weex的
// loading-indicator组件自带旋转功能
this.mode !== 'spinner' && this.startAnimate()
},
// 执行nvue的animate模块动画
startAnimate() {
this.loading = true
const ani = this.$refs.ani
if (!ani) return
animation.transition(ani, {
// 进行角度旋转
styles: {
transform: `rotate(${this.aniAngel}deg)`,
transformOrigin: 'center center'
},
duration: this.duration,
timingFunction: this.timingFunction,
// delay: 10
}, () => {
// 每次增加360deg为了让其重新旋转一周
this.aniAngel += 360
// 动画结束后继续循环执行动画需要同时判断webviewHide变量
// nvue安卓页面隐藏后依然会继续执行startAnimate方法
this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
})
}
// #endif
}
}
</script>
<style lang="scss" scoped>
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
$uv-loading-icon-color: #c8c9cc !default;
$uv-loading-icon-text-margin-left:4px !default;
$uv-loading-icon-text-color:$uv-content-color !default;
$uv-loading-icon-text-font-size:14px !default;
$uv-loading-icon-text-line-height:20px !default;
$uv-loading-width:30px !default;
$uv-loading-height:30px !default;
$uv-loading-max-width:100% !default;
$uv-loading-max-height:100% !default;
$uv-loading-semicircle-border-width: 2px !default;
$uv-loading-semicircle-border-color:transparent !default;
$uv-loading-semicircle-border-top-right-radius: 100px !default;
$uv-loading-semicircle-border-top-left-radius: 100px !default;
$uv-loading-semicircle-border-bottom-left-radius: 100px !default;
$uv-loading-semicircle-border-bottom-right-radiu: 100px !default;
$uv-loading-semicircle-border-style: solid !default;
$uv-loading-circle-border-top-right-radius: 100px !default;
$uv-loading-circle-border-top-left-radius: 100px !default;
$uv-loading-circle-border-bottom-left-radius: 100px !default;
$uv-loading-circle-border-bottom-right-radiu: 100px !default;
$uv-loading-circle-border-width:2px !default;
$uv-loading-circle-border-top-color:#e5e5e5 !default;
$uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;
$uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;
$uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;
$uv-loading-circle-border-style:solid !default;
$uv-loading-icon-host-font-size:0px !default;
$uv-loading-icon-host-line-height:1 !default;
$uv-loading-icon-vertical-margin:6px 0 0 !default;
$uv-loading-icon-dot-top:0 !default;
$uv-loading-icon-dot-left:0 !default;
$uv-loading-icon-dot-width:100% !default;
$uv-loading-icon-dot-height:100% !default;
$uv-loading-icon-dot-before-width:2px !default;
$uv-loading-icon-dot-before-height:25% !default;
$uv-loading-icon-dot-before-margin:0 auto !default;
$uv-loading-icon-dot-before-background-color:currentColor !default;
$uv-loading-icon-dot-before-border-radius:40% !default;
.uv-loading-icon {
/* #ifndef APP-NVUE */
// display: inline-flex;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
color: $uv-loading-icon-color;
&__text {
margin-left: $uv-loading-icon-text-margin-left;
color: $uv-loading-icon-text-color;
font-size: $uv-loading-icon-text-font-size;
line-height: $uv-loading-icon-text-line-height;
}
&__spinner {
width: $uv-loading-width;
height: $uv-loading-height;
position: relative;
/* #ifndef APP-NVUE */
box-sizing: border-box;
max-width: $uv-loading-max-width;
max-height: $uv-loading-max-height;
animation: uv-rotate 1s linear infinite;
/* #endif */
}
&__spinner--semicircle {
border-width: $uv-loading-semicircle-border-width;
border-color: $uv-loading-semicircle-border-color;
border-top-right-radius: $uv-loading-semicircle-border-top-right-radius;
border-top-left-radius: $uv-loading-semicircle-border-top-left-radius;
border-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;
border-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;
border-style: $uv-loading-semicircle-border-style;
}
&__spinner--circle {
border-top-right-radius: $uv-loading-circle-border-top-right-radius;
border-top-left-radius: $uv-loading-circle-border-top-left-radius;
border-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;
border-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;
border-width: $uv-loading-circle-border-width;
border-top-color: $uv-loading-circle-border-top-color;
border-right-color: $uv-loading-circle-border-right-color;
border-bottom-color: $uv-loading-circle-border-bottom-color;
border-left-color: $uv-loading-circle-border-left-color;
border-style: $uv-loading-circle-border-style;
}
&--vertical {
flex-direction: column
}
}
/* #ifndef APP-NVUE */
:host {
font-size: $uv-loading-icon-host-font-size;
line-height: $uv-loading-icon-host-line-height;
}
.uv-loading-icon {
&__spinner--spinner {
animation-timing-function: steps(12)
}
&__text:empty {
display: none
}
&--vertical &__text {
margin: $uv-loading-icon-vertical-margin;
color: $uv-content-color;
}
&__dot {
position: absolute;
top: $uv-loading-icon-dot-top;
left: $uv-loading-icon-dot-left;
width: $uv-loading-icon-dot-width;
height: $uv-loading-icon-dot-height;
&:before {
display: block;
width: $uv-loading-icon-dot-before-width;
height: $uv-loading-icon-dot-before-height;
margin: $uv-loading-icon-dot-before-margin;
background-color: $uv-loading-icon-dot-before-background-color;
border-radius: $uv-loading-icon-dot-before-border-radius;
content: " "
}
}
}
@for $i from 1 through 12 {
.uv-loading-icon__dot:nth-of-type(#{$i}) {
transform: rotate($i * 30deg);
opacity: 1 - 0.0625 * ($i - 1);
}
}
@keyframes uv-rotate {
0% {
transform: rotate(0deg)
}
to {
transform: rotate(1turn)
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,87 @@
{
"id": "uv-loading-icon",
"displayName": "uv-loading-icon 加载动画 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.3",
"description": "此组件为一个小动画目前用在uv-ui的uv-load-more加载更多等组件还可以运用在项目中正在加载状态场景。",
"keywords": [
"uv-loading-icon",
"uvui",
"uv-ui",
"loading",
"加载动画"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,19 @@
## LoadingIcon 加载动画
> **组件名uv-loading-icon**
此组件为一个小动画,目前用在 `uv-ui``uv-load-more` 加载更多等组件,还可以运用在项目中正在加载状态场景。
# <a href="https://www.uvui.cn/components/loadingIcon.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">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,9 @@
## 1.0.32023-07-02
uv-overlay 由于弹出层uv-transition的修改组件内部做了相应的修改参数不变。
## 1.0.22023-06-29
1. 优化H5端禁止穿透滚动
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
1. 新增uv-overlay组件

View File

@@ -0,0 +1,25 @@
export default {
props: {
// 是否显示遮罩
show: {
type: Boolean,
default: false
},
// 层级z-index
zIndex: {
type: [String, Number],
default: 10070
},
// 遮罩的过渡时间单位为ms
duration: {
type: [String, Number],
default: 300
},
// 不透明度值当做rgba的第四个参数
opacity: {
type: [String, Number],
default: 0.5
},
...uni.$uv?.props?.overlay
}
}

View File

@@ -0,0 +1,85 @@
<template>
<uv-transition
:show="show"
mode="fade"
custom-class="uv-overlay"
:duration="duration"
:custom-style="overlayStyle"
@click="clickHandler"
@touchmove.stop.prevent="clear"
>
<slot />
</uv-transition>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* overlay 遮罩
* @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
* @tutorial https://www.uvui.cn/components/overlay.html
* @property {Boolean} show 是否显示遮罩(默认 false
* @property {String | Number} zIndex zIndex 层级(默认 10070
* @property {String | Number} duration 动画时长,单位毫秒(默认 300
* @property {String | Number} opacity 不透明度值当做rgba的第四个参数 (默认 0.5
* @property {Object} customStyle 定义需要用到的外部样式
* @event {Function} click 点击遮罩发送事件
* @example <uv-overlay :show="show" @click="show = false"></uv-overlay>
*/
export default {
name: "uv-overlay",
emits: ['click'],
mixins: [mpMixin, mixin, props],
watch: {
show(newVal){
// #ifdef H5
if(newVal){
document.querySelector('body').style.overflow = 'hidden';
}else{
document.querySelector('body').style.overflow = '';
}
// #endif
}
},
computed: {
overlayStyle() {
const style = {
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: this.zIndex,
bottom: 0,
'background-color': `rgba(0, 0, 0, ${this.opacity})`
}
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
}
},
methods: {
clickHandler() {
this.$emit('click')
},
clear() {}
}
}
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
$uv-overlay-top:0 !default;
$uv-overlay-left:0 !default;
$uv-overlay-width:100% !default;
$uv-overlay-height:100% !default;
$uv-overlay-background-color:rgba(0, 0, 0, .7) !default;
.uv-overlay {
position: fixed;
top:$uv-overlay-top;
left:$uv-overlay-left;
width: $uv-overlay-width;
height:$uv-overlay-height;
background-color:$uv-overlay-background-color;
}
/* #endif */
</style>

View File

@@ -0,0 +1,88 @@
{
"id": "uv-overlay",
"displayName": "uv-overlay 遮罩层 全面兼容小程序、nvue、vue2、vue3等多端",
"version": "1.0.3",
"description": "uv-overlay 创建一个遮罩层用于强调特定的页面元素并阻止用户对遮罩下层的内容进行操作一般用于弹窗场景uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。",
"keywords": [
"uv-overlay",
"uvui",
"uv-ui",
"overlay",
"遮罩层"
],
"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",
"uv-transition"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
## Overlay 遮罩层
> **组件名uv-overlay**
创建一个遮罩层用于强调特定的页面元素并阻止用户对遮罩下层的内容进行操作一般用于弹窗场景uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。
### <a href="https://www.uvui.cn/components/overlay.html" target="_blank">查看文档</a>
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
#### 如使用过程中有任何问题或者您对uv-ui有一些好的建议欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,33 @@
## 1.0.142023-12-29
1. 修复上个版本引出的BUG
## 1.0.132023-12-26
1. 修复抖音小程序滚到底不触发change的BUG
## 1.0.122023-11-20
1. 修复issues反馈的问题uv-picker在组合式API的自定义组件中columns动态赋值无法显示选项https://gitee.com/climblee/uv-ui/issues/I8H0GQ
## 1.0.112023-10-11
1. 将immediate-change默认值改为true该值在于change回调的及时性微信小程序生效
## 1.0.102023-08-25
1. 增加round属性设置弹窗圆角默认为0
## 1.0.92023-08-24
1. 修复cli项目不返回值的问题
## 1.0.82023-08-04
1. 优化
## 1.0.72023-08-02
1. 改组件中删除uv-toolbar组件请单独下载uv-toolbar组件
## 1.0.62023-07-02
uv-picker 由于弹出层uv-popup的修改打开和关闭方法更改详情参考文档https://www.uvui.cn/components/picker.html
## 1.0.52023-06-26
1. 增加color参数
2. 增加activeColor参数
## 1.0.42023-06-15
1. 修改支付宝报错的BUG
## 1.0.32023-06-12
1. setColumnValues的使用统一化避免某些平台报错
2. 取消change回调回传的组件实例直接统一通过ref的方式调取setColumnValues方法
## 1.0.22023-05-23
1. uv-toolbar组件新增下边框属性
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-picker 选择器

View File

@@ -0,0 +1,95 @@
export default {
props: {
// 是否展示顶部的操作栏
showToolbar: {
type: Boolean,
default: true
},
// 顶部标题
title: {
type: String,
default: ''
},
// 弹窗圆角
round: {
type: [String, Number],
default: 0
},
// 对象数组,设置每一列的数据
columns: {
type: Array,
default: () => []
},
// 是否显示加载中状态
loading: {
type: Boolean,
default: false
},
// 各列中,单个选项的高度
itemHeight: {
type: [String, Number],
default: 44
},
// 取消按钮的文字
cancelText: {
type: String,
default: '取消'
},
// 确认按钮的文字
confirmText: {
type: String,
default: '确定'
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: '#909193'
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: '#3c9cff'
},
// 文字颜色
color: {
type: String,
default: ''
},
// 选中文字的颜色
activeColor: {
type: String,
default: ''
},
// 每列中可见选项的数量
visibleItemCount: {
type: [String, Number],
default: 5
},
// 选项对象中,需要展示的属性键名
keyName: {
type: String,
default: 'text'
},
// 是否允许点击遮罩关闭选择器
closeOnClickOverlay: {
type: Boolean,
default: true
},
// 是否允许点击确认关闭选择器
closeOnClickConfirm: {
type: Boolean,
default: true
},
// 各列的默认索引
defaultIndex: {
type: Array,
default: () => [],
},
// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件只在微信2.21.1及以上有效
immediateChange: {
type: Boolean,
default: true
},
...uni.$uv?.props?.picker
}
}

View File

@@ -0,0 +1,330 @@
<template>
<uv-popup
ref="pickerPopup"
mode="bottom"
:round="round"
:close-on-click-overlay="closeOnClickOverlay"
@change="popupChange"
>
<view class="uv-picker">
<uv-toolbar
v-if="showToolbar"
:cancelColor="cancelColor"
:confirmColor="confirmColor"
:cancelText="cancelText"
:confirmText="confirmText"
:title="title"
@cancel="cancel"
@confirm="confirm"
></uv-toolbar>
<!-- #ifdef MP-TOUTIAO -->
<picker-view
class="uv-picker__view"
:indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
:value="innerIndex"
:immediateChange="immediateChange"
:style="{
height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
}"
@pickend="changeHandler"
>
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO -->
<picker-view
class="uv-picker__view"
:indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
:value="innerIndex"
:immediateChange="immediateChange"
:style="{
height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
}"
@change="changeHandler"
>
<!-- #endif -->
<!-- @pickend在这里为了解决抖音等滚到底不触发change兼容性问题 -->
<picker-view-column
v-for="(item, index) in innerColumns"
:key="index"
class="uv-picker__view__column"
>
<text
v-if="$uv.test.array(item)"
class="uv-picker__view__column__item uv-line-1"
v-for="(item1, index1) in item"
:key="index1"
:style="[{
height: $uv.addUnit(itemHeight),
lineHeight: $uv.addUnit(itemHeight),
fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
},textStyle(index,index1)]"
>{{ getItemText(item1) }}</text>
</picker-view-column>
</picker-view>
<view
v-if="loading"
class="uv-picker--loading"
>
<uv-loading-icon mode="circle"></uv-loading-icon>
</view>
</view>
</uv-popup>
</template>
<script>
/**
* uv-picker
* @description 选择器
* @property {Boolean} showToolbar 是否显示顶部的操作栏(默认 true
* @property {String} title 顶部标题
* @property {Array} columns 对象数组,设置每一列的数据
* @property {Boolean} loading 是否显示加载中状态(默认 false
* @property {String | Number} itemHeight 各列中,单个选项的高度(默认 44
* @property {String} cancelText 取消按钮的文字(默认 '取消'
* @property {String} confirmText 确认按钮的文字(默认 '确定'
* @property {String} cancelColor 取消按钮的颜色(默认 '#909193'
* @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff'
* @property {String} color 文字颜色(默认 ''
* @property {String} activeColor 选中文字的颜色(默认 ''
* @property {String | Number} visibleItemCount 每列中可见选项的数量(默认 5
* @property {String} keyName 选项对象中,需要展示的属性键名(默认 'text'
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器(默认 false
* @property {Array} defaultIndex 各列的默认索引
* @property {Boolean} immediateChange 是否在手指松开时立即触发change事件默认 false
* @event {Function} close 关闭选择器时触发
* @event {Function} cancel 点击取消按钮触发
* @event {Function} change 当选择值变化时触发
* @event {Function} confirm 点击确定按钮,返回当前选择的值
*/
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
export default {
name: 'uv-picker',
emits: ['confirm','cancel','close','change'],
mixins: [mpMixin, mixin, props],
computed: {
// 为了解决支付宝不生效
textStyle(){
return (index,index1) => {
const style = {};
// #ifndef APP-NVUE
style.display = 'block';
// #endif
if(this.color) {
style.color = this.color;
}
if(this.activeColor && index1 === this.innerIndex[index]) {
style.color = this.activeColor;
}
return style;
}
}
},
data() {
return {
// 上一次选择的列索引
lastIndex: [],
// 索引值 对应picker-view的value
innerIndex: [],
// 各列的值
innerColumns: [],
// 上一次的变化列索引
columnIndex: 0,
}
},
watch: {
// 监听默认索引的变化,重新设置对应的值
defaultIndex: {
immediate: true,
handler(n) {
this.setIndexs(n, true)
}
},
// 监听columns参数的变化
columns: {
deep: true,
immediate: true,
handler(n) {
this.setColumns(n)
}
},
},
methods: {
open() {
this.$refs.pickerPopup.open();
},
close() {
this.$refs.pickerPopup.close();
},
popupChange(e) {
if(!e.show) this.$emit('close');
},
// 获取item需要显示的文字判别为对象还是文本
getItemText(item) {
if (this.$uv.test.object(item)) {
return item[this.keyName]
} else {
return item
}
},
// 点击工具栏的取消按钮
cancel() {
this.$emit('cancel');
this.close();
},
// 点击工具栏的确定按钮
confirm() {
// 在这里使用deepClone拷贝后vue3会自动转换成原始对象这样处理是因为cli项目可能出现不返回值的情况
this.$emit('confirm', this.$uv.deepClone({
indexs: this.innerIndex,
value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
values: this.innerColumns
}));
if(this.closeOnClickConfirm) {
this.close();
}
},
// 选择器某一列的数据发生变化时触发
changeHandler(e) {
const {
value
} = e.detail
let index = 0,
columnIndex = 0
// 通过对比前后两次的列索引,得出当前变化的是哪一列
for (let i = 0; i < value.length; i++) {
let item = value[i]
if (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0
// 设置columnIndex为当前变化列的索引
columnIndex = i
// index则为变化列中的变化项的索引
index = item
break // 终止循环,即使少一次循环,也是性能的提升
}
}
this.columnIndex = columnIndex
const values = this.innerColumns
// 将当前的各项变化索引,设置为"上一次"的索引变化值
this.setLastIndex(value)
this.setIndexs(value)
this.$emit('change', {
value: this.innerColumns.map((item, index) => item[value[index]]),
index,
indexs: value,
// values为当前变化列的数组内容
values,
columnIndex
})
},
// 设置index索引此方法可被外部调用设置
setIndexs(index, setLastIndex) {
this.innerIndex = this.$uv.deepClone(index)
if (setLastIndex) {
this.setLastIndex(index)
}
},
// 记录上一次的各列索引位置
setLastIndex(index) {
// 当能进入此方法意味着当前设置的各列默认索引即为“上一次”的选中值需要记录是因为changeHandler中
// 需要拿前后的变化值进行对比,得出当前发生改变的是哪一列
this.lastIndex = this.$uv.deepClone(index)
},
// 设置对应列选项的所有值
setColumnValues(columnIndex, values) {
// 替换innerColumns数组中columnIndex索引的值为values使用的是数组的splice方法
this.innerColumns.splice(columnIndex, 1, values)
// 拷贝一份原有的innerIndex做临时变量将大于当前变化列的所有的列的默认索引设置为0
let tmpIndex = this.$uv.deepClone(this.innerIndex)
for (let i = 0; i < this.innerColumns.length; i++) {
if (i > this.columnIndex) {
tmpIndex[i] = 0
}
}
// 一次性赋值,不能单个修改,否则无效
this.setIndexs(tmpIndex)
},
// 获取对应列的所有选项
getColumnValues(columnIndex) {
// 进行同步阻塞因为外部得到change事件之后可能需要执行setColumnValues更新列的值
// 索引如果在外部change的回调中调用getColumnValues的话可能无法得到变更后的列值这里进行一定延时保证值的准确性
(async () => {
await this.$uv.sleep()
})()
return this.innerColumns[columnIndex]
},
// 设置整体各列的columns的值
setColumns(columns) {
this.innerColumns = this.$uv.deepClone(columns)
// 如果在设置各列数据时没有被设置默认的各列索引defaultIndex那么用0去填充它数组长度为列的数量
if (this.innerIndex.length === 0) {
this.innerIndex = new Array(columns.length).fill(0)
}
},
// 获取各列选中值对应的索引
getIndexs() {
return this.innerIndex
},
// 获取各列选中的值
getValues() {
// 进行同步阻塞因为外部得到change事件之后可能需要执行setColumnValues更新列的值
// 索引如果在外部change的回调中调用getValues的话可能无法得到变更后的列值这里进行一定延时保证值的准确性
(async () => {
await this.$uv.sleep()
})()
return this.innerColumns.map((item, index) => item[this.innerIndex[index]])
}
},
}
</script>
<style lang="scss" scoped>
$show-lines: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
.uv-picker {
position: relative;
&__view {
&__column {
@include flex;
flex: 1;
justify-content: center;
&__item {
@include flex;
justify-content: center;
align-items: center;
font-size: 16px;
text-align: center;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
color: $uv-main-color;
&--disabled {
/* #ifndef APP-NVUE */
cursor: not-allowed;
/* #endif */
opacity: 0.35;
}
}
}
}
&--loading {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
@include flex;
justify-content: center;
align-items: center;
background-color: rgba(255, 255, 255, 0.87);
z-index: 1000;
}
}
</style>

View File

@@ -0,0 +1,90 @@
{
"id": "uv-picker",
"displayName": "uv-picker 选择器 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.14",
"description": "uv-picker 此选择器用于单列,多列,多列联动的选择场景...",
"keywords": [
"uv-picker",
"uvui",
"uv-ui",
"picker",
"联动选择"
],
"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",
"uv-popup",
"uv-loading-icon",
"uv-toolbar"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
## Picker 选择器
> **组件名uv-picker**
此选择器用于单列,多列,多列联动的选择场景。
`uv-datetime-picker`等组件也用到了该组件,功能完善,需要特别注意的是`columns`参数的形式是数组嵌套。
# <a href="https://www.uvui.cn/components/picker.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">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,18 @@
## 1.0.72023-11-20
修复issues问题https://gitee.com/climblee/uv-ui/issues/I8HDLO
## 1.0.62023-10-13
1. 优化vue内容有背景色设置圆角被遮挡的情况
## 1.0.52023-09-10
1. 修复H5默认层级过高的问题
2. 修复全局设置prop无效的问题
## 1.0.42023-08-08
1. 修复修改zIndex不生效的BUG
## 1.0.32023-07-02
uv-popup 弹出层代码重构优化性能翻倍小程序体验性能更加避免卡顿。打开和关闭方法更改详情参考文档https://www.uvui.cn/components/popup.html
## 1.0.22023-06-11
1. 修复zIndex层级问题
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
1. 新增uv-popup组件

View File

@@ -0,0 +1,45 @@
// #ifdef H5
export default {
name: 'Keypress',
props: {
disable: {
type: Boolean,
default: false
}
},
mounted () {
const keyNames = {
esc: ['Esc', 'Escape'],
tab: 'Tab',
enter: 'Enter',
space: [' ', 'Spacebar'],
up: ['Up', 'ArrowUp'],
left: ['Left', 'ArrowLeft'],
right: ['Right', 'ArrowRight'],
down: ['Down', 'ArrowDown'],
delete: ['Backspace', 'Delete', 'Del']
}
const listener = ($event) => {
if (this.disable) {
return
}
const keyName = Object.keys(keyNames).find(key => {
const keyName = $event.key
const value = keyNames[key]
return value === keyName || (Array.isArray(value) && value.includes(keyName))
})
if (keyName) {
// 避免和其他按键事件冲突
setTimeout(() => {
this.$emit(keyName, {})
}, 0)
}
}
document.addEventListener('keyup', listener)
// this.$once('hook:beforeDestroy', () => {
// document.removeEventListener('keyup', listener)
// })
},
render: () => {}
}
// #endif

View File

@@ -0,0 +1,539 @@
<template>
<view
v-if="showPopup"
class="uv-popup"
:class="[popupClass, isDesktop ? 'fixforpc-z-index' : '']"
:style="[{zIndex: zIndex}]"
>
<view @touchstart="touchstart">
<!-- 遮罩层 -->
<uv-overlay
key="1"
v-if="maskShow && overlay"
:show="showTrans"
:duration="duration"
:custom-style="overlayStyle"
:opacity="overlayOpacity"
:zIndex="zIndex"
@click="onTap"
></uv-overlay>
<uv-transition
key="2"
:mode="ani"
name="content"
:custom-style="transitionStyle"
:duration="duration"
:show="showTrans"
@click="onTap"
>
<view
class="uv-popup__content"
:style="[contentStyle]"
:class="[popupClass]"
@click="clear"
>
<uv-status-bar v-if="safeAreaInsetTop"></uv-status-bar>
<slot />
<uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
<view
v-if="closeable"
@tap.stop="close"
class="uv-popup__content__close"
:class="['uv-popup__content__close--' + closeIconPos]"
hover-class="uv-popup__content__close--hover"
hover-stay-time="150"
>
<uv-icon
name="close"
color="#909399"
size="18"
bold
></uv-icon>
</view>
</view>
</uv-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
</template>
<script>
// #ifdef H5
import keypress from './keypress.js'
// #endif
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
/**
* PopUp 弹出层
* @description 弹出层组件,为了解决遮罩弹层的问题
* @tutorial https://www.uvui.cn/components/popup.html
* @property {String} mode = [top|center|bottom|left|right] 弹出方式
* @value top 顶部弹出
* @value center 中间弹出
* @value bottom 底部弹出
* @value left 左侧弹出
* @value right 右侧弹出
* @property {Number} duration 动画时长默认300
* @property {Boolean} overlay 是否显示遮罩默认true
* @property {Boolean} overlayOpacity 遮罩透明度默认0.5
* @property {Object} overlayStyle 遮罩自定义样式
* @property {Boolean} closeOnClickOverlay = [true|false] 蒙版点击是否关闭弹窗默认true
* @property {Number | String} zIndex 弹出层的层级
* @property {Boolean} safeAreaInsetTop 是否留出顶部安全区状态栏高度默认false
* @property {Boolean} safeAreaInsetBottom 是否为留出底部安全区适配默认true
* @property {Boolean} closeable 是否显示关闭图标默认false
* @property {Boolean} closeIconPos 自定义关闭图标位置,`top-left`-左上角,`top-right`-右上角,`bottom-left`-左下角,`bottom-right`-右下角默认top-right
* @property {String} bgColor 主窗口背景色
* @property {String} maskBackgroundColor 蒙版颜色
* @property {Boolean} customStyle 自定义样式
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
export default {
name: 'uv-popup',
components: {
// #ifdef H5
keypress
// #endif
},
mixins: [mpMixin, mixin],
emits: ['change', 'maskClick'],
props: {
// 弹出层类型可选值top: 顶部弹出层bottom底部弹出层center全屏弹出层
// message: 消息提示 ; dialog : 对话框
mode: {
type: String,
default: 'center'
},
// 动画时长单位ms
duration: {
type: [String, Number],
default: 300
},
// 层级
zIndex: {
type: [String, Number],
// #ifdef H5
default: 997
// #endif
// #ifndef H5
default: 10075
// #endif
},
bgColor: {
type: String,
default: '#ffffff'
},
safeArea: {
type: Boolean,
default: true
},
// 是否显示遮罩
overlay: {
type: Boolean,
default: true
},
// 点击遮罩是否关闭弹窗
closeOnClickOverlay: {
type: Boolean,
default: true
},
// 遮罩的透明度0-1之间
overlayOpacity: {
type: [Number, String],
default: 0.4
},
// 自定义遮罩的样式
overlayStyle: {
type: [Object, String],
default: ''
},
// 是否为iPhoneX留出底部安全距离
safeAreaInsetBottom: {
type: Boolean,
default: true
},
// 是否留出顶部安全距离(状态栏高度)
safeAreaInsetTop: {
type: Boolean,
default: false
},
// 是否显示关闭图标
closeable: {
type: Boolean,
default: false
},
// 自定义关闭图标位置top-left为左上角top-right为右上角bottom-left为左下角bottom-right为右下角
closeIconPos: {
type: String,
default: 'top-right'
},
// mode=center也即中部弹出时是否使用缩放模式
zoom: {
type: Boolean,
default: true
},
round: {
type: [Number, String],
default: 0
},
...uni.$uv?.props?.popup
},
watch: {
/**
* 监听type类型
*/
type: {
handler: function(type) {
if (!this.config[type]) return
this[this.config[type]](true)
},
immediate: true
},
isDesktop: {
handler: function(newVal) {
if (!this.config[newVal]) return
this[this.config[this.mode]](true)
},
immediate: true
},
// H5 下禁止底部滚动
showPopup(show) {
// #ifdef H5
// fix by mehaotian 处理 h5 滚动穿透的问题
document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
// #endif
}
},
data() {
return {
ani: [],
showPopup: false,
showTrans: false,
popupWidth: 0,
popupHeight: 0,
config: {
top: 'top',
bottom: 'bottom',
center: 'center',
left: 'left',
right: 'right',
message: 'top',
dialog: 'center',
share: 'bottom'
},
transitionStyle: {
position: 'fixed',
left: 0,
right: 0
},
maskShow: true,
mkclick: true,
popupClass: this.isDesktop ? 'fixforpc-top' : 'top',
direction: ''
}
},
computed: {
isDesktop() {
return this.popupWidth >= 500 && this.popupHeight >= 500
},
bg() {
if (this.bgColor === '' || this.bgColor === 'none' || this.$uv.getPx(this.round)>0) {
return 'transparent'
}
return this.bgColor
},
contentStyle() {
const style = {};
if (this.bgColor) {
style.backgroundColor = this.bg
}
if(this.round) {
const value = this.$uv.addUnit(this.round)
const mode = this.direction?this.direction:this.mode
style.backgroundColor = this.bgColor
if(mode === 'top') {
style.borderBottomLeftRadius = value
style.borderBottomRightRadius = value
} else if(mode === 'bottom') {
style.borderTopLeftRadius = value
style.borderTopRightRadius = value
} else if(mode === 'center') {
style.borderRadius = value
}
}
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
}
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible()
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible()
},
// #endif
created() {
// TODO 处理 message 组件生命周期异常的问题
this.messageChild = null
// TODO 解决头条冒泡的问题
this.clearPropagation = false
},
methods: {
setH5Visible() {
// #ifdef H5
// fix by mehaotian 处理 h5 滚动穿透的问题
document.getElementsByTagName('body')[0].style.overflow = 'visible'
// #endif
},
/**
* 公用方法,不显示遮罩层
*/
closeMask() {
this.maskShow = false
},
// TODO nvue 取消冒泡
clear(e) {
// #ifndef APP-NVUE
e.stopPropagation()
// #endif
this.clearPropagation = true
},
open(direction) {
// fix by mehaotian 处理快速打开关闭的情况
if (this.showPopup) {
return
}
let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
if (!(direction && innerType.indexOf(direction) !== -1)) {
direction = this.mode
}else {
this.direction = direction;
}
if (!this.config[direction]) {
return this.$uv.error(`缺少类型:${direction}`);
}
this[this.config[direction]]()
this.$emit('change', {
show: true,
type: direction
})
},
close(type) {
this.showTrans = false
this.$emit('change', {
show: false,
type: this.mode
})
clearTimeout(this.timer)
// // 自定义关闭事件
this.timer = setTimeout(() => {
this.showPopup = false
}, 300)
},
// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
touchstart() {
this.clearPropagation = false
},
onTap() {
if (this.clearPropagation) {
// fix by mehaotian 兼容 nvue
this.clearPropagation = false
return
}
this.$emit('maskClick')
if (!this.closeOnClickOverlay) return
this.close()
},
/**
* 顶部弹出样式处理
*/
top(type) {
this.popupClass = this.isDesktop ? 'fixforpc-top' : 'top'
this.ani = ['slide-top']
this.transitionStyle = {
position: 'fixed',
zIndex: this.zIndex,
left: 0,
right: 0,
backgroundColor: this.bg
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
this.$nextTick(() => {
if (this.messageChild && this.mode === 'message') {
this.messageChild.timerClose()
}
})
},
/**
* 底部弹出样式处理
*/
bottom(type) {
this.popupClass = 'bottom'
this.ani = ['slide-bottom']
this.transitionStyle = {
position: 'fixed',
zIndex: this.zIndex,
left: 0,
right: 0,
bottom: 0,
backgroundColor: this.bg
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
/**
* 中间弹出样式处理
*/
center(type) {
this.popupClass = 'center'
this.ani = this.zoom?['zoom-in', 'fade']:['fade'];
this.transitionStyle = {
position: 'fixed',
zIndex: this.zIndex,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column',
/* #endif */
bottom: 0,
left: 0,
right: 0,
top: 0,
justifyContent: 'center',
alignItems: 'center'
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
left(type) {
this.popupClass = 'left'
this.ani = ['slide-left']
this.transitionStyle = {
position: 'fixed',
zIndex: this.zIndex,
left: 0,
bottom: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
},
right(type) {
this.popupClass = 'right'
this.ani = ['slide-right']
this.transitionStyle = {
position: 'fixed',
zIndex: this.zIndex,
bottom: 0,
right: 0,
top: 0,
backgroundColor: this.bg,
/* #ifndef APP-NVUE */
display: 'flex',
flexDirection: 'column'
/* #endif */
}
// TODO 兼容 type 属性 ,后续会废弃
if (type) return
this.showPopup = true
this.showTrans = true
}
}
}
</script>
<style lang="scss" scoped>
.uv-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uv-popup__content {
/* #ifndef APP-NVUE */
display: block;
overflow: hidden;
/* #endif */
position: relative;
&.left,
&.right {
/* #ifdef H5 */
padding-top: var(--window-top);
/* #endif */
/* #ifndef H5 */
padding-top: 0;
/* #endif */
flex: 1;
}
&__close {
position: absolute;
&--hover {
opacity: 0.4;
}
}
&__close--top-left {
top: 15px;
left: 15px;
}
&__close--top-right {
top: 15px;
right: 15px;
}
&__close--bottom-left {
bottom: 15px;
left: 15px;
}
&__close--bottom-right {
right: 15px;
bottom: 15px;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>

View File

@@ -0,0 +1,92 @@
{
"id": "uv-popup",
"displayName": "uv-popup 弹出层 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.7",
"description": "uv-popup 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。",
"keywords": [
"uv-popup",
"uvui",
"uv-ui",
"popup",
"弹出层"
],
"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",
"uv-overlay",
"uv-transition",
"uv-icon",
"uv-status-bar",
"uv-safe-bottom"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
## Popup 弹出层
> **组件名uv-popup**
弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。
该组件已经放弃原来`uview2.x`的写法,参照了官方`uni-popup`的写法进行重构。在小程序端的性能大大提升,打开和关闭避免延迟,调用方法与之前相比也有所差异,具体请查看文档。
# <a href="https://www.uvui.cn/components/popup.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">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,11 @@
## 1.0.42023-09-14
1. 飞书小程序支持
## 1.0.32023-08-14
1. 修复百度报错的BUG
## 1.0.22023-07-02
uv-safe-bottom 修复在百度程序抖音小程序不生效的BUG
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
uv-safe-bottom 底部安全区组件

View File

@@ -0,0 +1,67 @@
<template>
<view
class="uv-safe-bottom"
:style="[style]"
:class="[!isNvue && 'uv-safe-area-inset-bottom']"
>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
/**
* SafeBottom 底部安全区
* @description 这个适配主要是针对IPhone X等一些底部带指示条的机型指示条的操作区域与页面底部存在重合容易导致用户误操作因此我们需要针对这些机型进行底部安全区适配。
* @tutorial https://www.uvui.cn/components/safeAreaInset.html
* @property {type} prop_name
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function()}
* @example <uv-status-bar></uv-status-bar>
*/
export default {
name: "uv-safe-bottom",
mixins: [mpMixin, mixin],
data() {
return {
safeAreaBottomHeight: 0,
isNvue: false,
};
},
computed: {
style() {
const style = {};
// #ifdef APP-NVUE || MP-TOUTIAO || MP-LARK
// nvue下高度使用js计算填充
style.height = this.$uv.addUnit(this.$uv.sys()?.safeAreaInsets?.bottom, 'px');
// #endif
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
},
},
mounted() {
// #ifdef APP-NVUE
// 标识为是否nvue
this.isNvue = true;
// #endif
},
};
</script>
<style lang="scss" scoped>
.uv-safe-bottom {
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
}
/* #ifndef APP-NVUE */
// 历遍生成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});
}
}
/* #endif */
</style>

View File

@@ -0,0 +1,87 @@
{
"id": "uv-safe-bottom",
"displayName": "uv-safe-bottom 底部安全区 全面兼容小程序、nvue、vue2、vue3等多端",
"version": "1.0.4",
"description": "这个适配主要是针对IPhone X等一些底部带指示条的机型指示条的操作区域与页面底部存在重合容易导致用户误操作因此我们需要针对这些机型进行底部安全区适配。",
"keywords": [
"uv-safe-bottom",
"uvui",
"uv-ui",
"bottom",
"底部安全区"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
## SafeBottom 底部安全区
> **组件名uv-safe-bottom**
这个适配主要是针对IPhone X等一些底部带指示条的机型指示条的操作区域与页面底部存在重合容易导致用户误操作因此我们需要针对这些机型进行底部安全区适配。
### <a href="https://www.uvui.cn/guide/safeAreaInset.html" target="_blank">查看文档</a>
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
#### 如使用过程中有任何问题或者您对uv-ui有一些好的建议欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,7 @@
## 1.0.22023-06-05
1. 兼容渐变背景色
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
1. 新增uv-status-bar组件

View File

@@ -0,0 +1,8 @@
export default {
props: {
bgColor: {
type: String,
default: 'transparent'
}
}
}

View File

@@ -0,0 +1,54 @@
<template>
<view
:style="[style]"
class="uv-status-bar"
>
<slot />
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* StatbusBar 状态栏占位
* @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
* @tutorial https://www.uvui.cn/components/statusBar.html
* @property {String} bgColor 背景色 (默认 'transparent' )
* @property {String | Object} customStyle 自定义样式
* @example <uv-status-bar></uv-status-bar>
*/
export default {
name: 'uv-status-bar',
mixins: [mpMixin, mixin, props],
data() {
return {
}
},
computed: {
style() {
const style = {}
// 状态栏高度由于某些安卓和微信开发工具无法识别css的顶部状态栏变量所以使用js获取的方式
style.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight, 'px')
if(this.bgColor){
if (this.bgColor.indexOf("gradient") > -1) {// 渐变色
style.backgroundImage = this.bgColor;
}else{
style.background = this.bgColor;
}
}
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
}
},
}
</script>
<style lang="scss" scoped>
.uv-status-bar {
// nvue会默认100%如果nvue下显式写100%的话会导致宽度不为100%而异常
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
}
</style>

View File

@@ -0,0 +1,87 @@
{
"id": "uv-status-bar",
"displayName": "uv-status-bar 状态栏占位",
"version": "1.0.2",
"description": "状态栏占位组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。",
"keywords": [
"uv-status-bar",
"uvui",
"uv-ui",
"status-bar",
"状态栏"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,10 @@
## StatbusBar 状态栏占位
> **组件名uv-status-bar**
本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
#### 如使用过程中有任何问题或者您对uv-ui有一些好的建议欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,2 @@
## 1.0.02023-08-02
1. 新增工具条组件

View File

@@ -0,0 +1,40 @@
export default {
props: {
// 是否展示工具条
show: {
type: Boolean,
default: true
},
// 是否显示下边框
showBorder: {
type: Boolean,
default: false
},
// 取消按钮的文字
cancelText: {
type: String,
default: '取消'
},
// 确认按钮的文字
confirmText: {
type: String,
default: '确认'
},
// 取消按钮的颜色
cancelColor: {
type: String,
default: '#909193'
},
// 确认按钮的颜色
confirmColor: {
type: String,
default: '#3c9cff'
},
// 标题文字
title: {
type: String,
default: ''
},
...uni.$uv?.props?.toolbar
}
}

View File

@@ -0,0 +1,109 @@
<template>
<view
:class="['uv-toolbar',{'uv-border-bottom':showBorder}]"
@touchmove.stop.prevent="noop"
v-if="show"
>
<view
class="uv-toolbar__cancel__wrapper"
hover-class="uv-hover-class"
>
<text
class="uv-toolbar__wrapper__cancel"
@tap="cancel"
:style="{
color: cancelColor
}"
>{{ cancelText }}</text>
</view>
<text
class="uv-toolbar__title uv-line-1"
v-if="title"
>{{ title }}</text>
<view
class="uv-toolbar__confirm__wrapper"
hover-class="uv-hover-class"
>
<text
class="uv-toolbar__wrapper__confirm"
@tap="confirm"
:style="{
color: confirmColor
}"
>{{ confirmText }}</text>
</view>
</view>
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import props from './props.js';
/**
* Toolbar 工具条
* @description
* @tutorial https://www.uvui.cn/components/toolbar.html
* @property {Boolean} show 是否展示工具条(默认 true
* @property {Boolean} showBorder 是否展示工具条下方边框(默认 false
* @property {String} cancelText 取消按钮的文字(默认 '取消'
* @property {String} confirmText 确认按钮的文字(默认 '确认'
* @property {String} cancelColor 取消按钮的颜色(默认 '#909193'
* @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff'
* @property {String} title 标题文字
* @event {Function}
* @example
*/
export default {
name: 'uv-toolbar',
emits: ['confirm', 'cancel'],
mixins: [mpMixin, mixin, props],
methods: {
// 点击取消按钮
cancel() {
this.$emit('cancel')
},
// 点击确定按钮
confirm() {
this.$emit('confirm')
}
}
}
</script>
<style lang="scss" scoped>
$show-lines: 1;
$show-hover: 1;
$show-border: 1;
$show-border-bottom: 1;
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
$uv-tips-color: #909193 !default;
$uv-main-color: #303133 !default;
$uv-primary: #3c9cff !default;
.uv-toolbar {
height: 42px;
@include flex;
justify-content: space-between;
align-items: center;
&__wrapper {
&__cancel {
color: $uv-tips-color;
font-size: 15px;
padding: 0 15px;
}
}
&__title {
color: $uv-main-color;
padding: 0 60rpx;
font-size: 16px;
flex: 1;
text-align: center;
}
&__wrapper {
&__confirm {
color: $uv-primary;
font-size: 15px;
padding: 0 15px;
}
}
}
</style>

View File

@@ -0,0 +1,87 @@
{
"id": "uv-toolbar",
"displayName": "uv-toolbar 工具条",
"version": "1.0.0",
"description": "该组价是仅用于uv-ui中一个公共小工具提供一个取消和确定的样式可以设置标题主要用于弹窗顶部的选择确定工具条",
"keywords": [
"uv-toolbar",
"uvui",
"uv-ui",
"工具条",
"工具"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,31 @@
## Toolbar 工具条
> **组件名uv-toolbar**
该组价是仅用于uv-ui中一个公共小工具提供一个取消和确定的样式可以设置标题主要用于弹窗顶部的选择确定工具条。
### 基本使用
```vue
<uv-toolbar title="标题文字"></uv-toolbar>
```
### Toolbar Props
| 属性名 | 类型 | 默认值 | 说明 |
|:-|:-|:-|:-|
| show | Boolean | true | 是否展示工具条 |
| showBorder | Boolean | false | 是否显示下边框 |
| cancelText | String | '取消' | 取消按钮的文字 |
| confirmText | String | '确定' | 确定按钮的文字 |
| cancelColor | String | '#909193' | 取消按钮的颜色 |
| confirmColor | String | '#3c9cff' | 确认按钮的颜色 |
| title | String | - | 标题文字 |
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
### [更多插件请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,19 @@
## 1.0.82023-10-18
1. 修复在APP上不能正常显示的BUG
## 1.0.72023-10-12
1. 修复部分情况修改某属性自动关闭的BUG
## 1.0.62023-07-24
1. 优化 nvue模式下增加cellChild参数是否在list中cell节点下nvue中cell下建议设置成true
## 1.0.52023-07-02
修改VUE3模式下可能存在的BUG
## 1.0.42023-07-02
uv-transition 动画组件代码重构优化性能更加友好增加自定义动画功能。详情参考文档https://www.uvui.cn/components/transition.html
## 1.0.32023-06-12
1. 恢复this.$nextTick的使用经过测试百度等平台无问题
## 1.0.22023-05-23
1. 百度小程序等平台不支持this.$nextick修改成延时
## 1.0.12023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.02023-05-10
1. 新增动画组件

View File

@@ -0,0 +1,131 @@
// const defaultOption = {
// duration: 300,
// timingFunction: 'linear',
// delay: 0,
// transformOrigin: '50% 50% 0'
// }
// #ifdef APP-NVUE
const nvueAnimation = uni.requireNativePlugin('animation')
// #endif
class MPAnimation {
constructor(options, _this) {
this.options = options
// 在iOS10+QQ小程序平台下传给原生的对象一定是个普通对象而不是Proxy对象否则会报parameter should be Object instead of ProxyObject的错误
this.animation = uni.createAnimation({
...options
})
this.currentStepAnimates = {}
this.next = 0
this.$ = _this
}
_nvuePushAnimates(type, args) {
let aniObj = this.currentStepAnimates[this.next]
let styles = {}
if (!aniObj) {
styles = {
styles: {},
config: {}
}
} else {
styles = aniObj
}
if (animateTypes1.includes(type)) {
if (!styles.styles.transform) {
styles.styles.transform = ''
}
let unit = ''
if(type === 'rotate'){
unit = 'deg'
}
styles.styles.transform += `${type}(${args+unit}) `
} else {
styles.styles[type] = `${args}`
}
this.currentStepAnimates[this.next] = styles
}
_animateRun(styles = {}, config = {}) {
let ref = this.$.$refs['ani'].ref
if (!ref) return
return new Promise((resolve, reject) => {
nvueAnimation.transition(ref, {
styles,
...config
}, res => {
resolve()
})
})
}
_nvueNextAnimate(animates, step = 0, fn) {
let obj = animates[step]
if (obj) {
let {
styles,
config
} = obj
this._animateRun(styles, config).then(() => {
step += 1
this._nvueNextAnimate(animates, step, fn)
})
} else {
this.currentStepAnimates = {}
typeof fn === 'function' && fn()
this.isEnd = true
}
}
step(config = {}) {
// #ifndef APP-NVUE
this.animation.step(config)
// #endif
// #ifdef APP-NVUE
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
this.next++
// #endif
return this
}
run(fn) {
// #ifndef APP-NVUE
this.$.animationData = this.animation.export()
this.$.timer = setTimeout(() => {
typeof fn === 'function' && fn()
}, this.$.durationTime)
// #endif
// #ifdef APP-NVUE
this.isEnd = false
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
if(!ref) return
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
this.next = 0
// #endif
}
}
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
'translateZ'
]
const animateTypes2 = ['opacity', 'backgroundColor']
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
MPAnimation.prototype[type] = function(...args) {
// #ifndef APP-NVUE
this.animation[type](...args)
// #endif
// #ifdef APP-NVUE
this._nvuePushAnimates(type, args)
// #endif
return this
}
})
export function createAnimation(option, _this) {
if(!_this) return
clearTimeout(_this.timer)
return new MPAnimation(option, _this)
}

View File

@@ -0,0 +1,31 @@
export default {
props: {
// 是否展示组件
show: {
type: Boolean,
default: false
},
// 使用的动画模式
mode: {
type: [Array, String, null],
default() {
return 'fade'
}
},
// 动画的执行时间单位ms
duration: {
type: [String, Number],
default: 300
},
// 使用的动画过渡函数
timingFunction: {
type: String,
default: 'ease-out'
},
customClass: {
type: String,
default: ''
},
...uni.$uv?.props?.transition
}
}

View File

@@ -0,0 +1,320 @@
<template>
<!-- #ifndef APP-NVUE -->
<view
v-if="isShow"
ref="ani"
:animation="animationData"
:class="customClass"
:style="transformStyles"
@click="onClick">
<slot></slot>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view
v-if="isShow"
ref="ani"
:animation="animationData"
:class="customClass"
:style="transformStyles"
@click="onClick">
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
import { createAnimation } from './createAnimation'
/**
* transition 动画组件
* @description
* @tutorial
* @property {Boolean} show 控制组件显示或关闭 (默认 false
* @property {Array | String } mode 内置过渡动画类型 (默认 'fade'
* @value fade 渐隐渐出过渡
* @value slide-top 由上至下过渡
* @value slide-bottom 由下至上过渡
* @value slide-left 由左至右过渡
* @value slide-right 由右至左过渡
* @value zoom-in 由小到大过渡
* @value zoom-out 由大到小过渡
* @property {String | Number} duration 动画的执行时间单位ms (默认 300
* @property {String} timingFunction 使用的动画过渡函数 (默认 'ease-out'
* @property {Object} customStyle 自定义样式
* @property {String} customClass 自定义类名
* @event {Function} click 点击组件触发
* @event {Function} change 过渡动画结束时触发
* @example
*/
export default {
name: 'uv-transition',
mixins: [mpMixin,mixin],
emits:['click','change'],
props: {
// 是否展示组件
show: {
type: Boolean,
default: false
},
// 使用的动画模式
mode: {
type: [Array, String, null],
default() {
return 'fade'
}
},
// 动画的执行时间单位ms
duration: {
type: [String, Number],
default: 300
},
// 使用的动画过渡函数
timingFunction: {
type: String,
default: 'ease-out'
},
customClass: {
type: String,
default: ''
},
// nvue模式下 是否直接显示在uv-list等cell下面使用就需要设置
cellChild: {
type: Boolean,
default: false
}
},
data(){
return {
isShow: false,
transform: '',
opacity: 1,
animationData: {},
durationTime: 300,
config: {}
}
},
watch: {
show: {
handler(newVal) {
if (newVal) {
this.open();
} else {
// 避免上来就执行 close,导致动画错乱
if (this.isShow) {
this.close();
}
}
},
immediate: true
}
},
computed: {
// 初始化动画条件
transformStyles() {
const style = {
transform: this.transform,
opacity: this.opacity,
...this.$uv.addStyle(this.customStyle),
'transition-duration': `${this.duration / 1000}s`
};
return this.$uv.addStyle(style,'string');
}
},
created() {
// 动画默认配置
this.config = {
duration: this.duration,
timingFunction: this.timingFunction,
transformOrigin: '50% 50%',
delay: 0
};
this.durationTime = this.duration;
},
methods: {
/**
* ref 触发 初始化动画
*/
init(obj = {}) {
if (obj.duration) {
this.durationTime = obj.duration;
}
this.animation = createAnimation(Object.assign(this.config, obj),this);
},
/**
* 点击组件触发回调
*/
onClick() {
this.$emit('click', {
detail: this.isShow
})
},
/**
* ref 触发 动画分组
* @param {Object} obj
*/
step(obj, config = {}) {
if (!this.animation) return;
for (let i in obj) {
try {
if(typeof obj[i] === 'object'){
this.animation[i](...obj[i]);
}else{
this.animation[i](obj[i]);
}
} catch (e) {
console.error(`方法 ${i} 不存在`);
}
}
this.animation.step(config);
return this;
},
/**
* ref 触发 执行动画
*/
run(fn) {
if (!this.animation) return;
this.animation.run(fn);
},
// 开始过度动画
open() {
clearTimeout(this.timer);
this.transform = '';
this.isShow = true;
let { opacity, transform } = this.styleInit(false);
if (typeof opacity !== 'undefined') {
this.opacity = opacity;
}
this.transform = transform;
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
this.$nextTick(() => {
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
this.timer = setTimeout(() => {
this.animation = createAnimation(this.config, this);
this.tranfromInit(false).step();
// #ifdef APP-NVUE
if(this.cellChild) {
this.opacity = 1;
} else{
this.animation.run();
}
// #endif
// #ifndef APP-NVUE
this.animation.run();
// #endif
// #ifdef VUE3
// #ifdef H5
this.opacity = 1;
// #endif
// #endif
this.$emit('change', {
detail: this.isShow
})
// #ifdef H5
// #ifdef VUE3
this.transform = '';
// #endif
// #endif
}, 20);
})
},
// 关闭过渡动画
close(type) {
if (!this.animation) return;
this.tranfromInit(true)
.step()
.run(() => {
this.isShow = false;
this.animationData = null;
this.animation = null;
let { opacity, transform } = this.styleInit(false);
this.opacity = opacity || 1;
this.transform = transform;
this.$emit('change', {
detail: this.isShow
});
})
},
// 处理动画开始前的默认样式
styleInit(type) {
let styles = {
transform: ''
};
let buildStyle = (type, mode) => {
if (mode === 'fade') {
styles.opacity = this.animationType(type)[mode];
} else {
styles.transform += this.animationType(type)[mode] + ' ';
}
}
if (typeof this.mode === 'string') {
buildStyle(type, this.mode);
} else {
this.mode.forEach(mode => {
buildStyle(type, mode)
})
}
return styles
},
// 处理内置组合动画
tranfromInit(type) {
let buildTranfrom = (type, mode) => {
let aniNum = null;
if (mode === 'fade') {
aniNum = type ? 0 : 1;
} else {
aniNum = type ? '-100%' : '0';
if (mode === 'zoom-in') {
aniNum = type ? 0.8 : 1
}
if (mode === 'zoom-out') {
aniNum = type ? 1.2 : 1
}
if (mode === 'slide-right') {
aniNum = type ? '100%' : '0'
}
if (mode === 'slide-bottom') {
aniNum = type ? '100%' : '0'
}
}
this.animation[this.animationMode()[mode]](aniNum)
}
if (typeof this.mode === 'string') {
buildTranfrom(type, this.mode)
} else {
this.mode.forEach(mode => {
buildTranfrom(type, mode)
})
}
return this.animation;
},
animationType(type) {
return {
fade: type ? 1 : 0,
'slide-top': `translateY(${type ? '0' : '-100%'})`,
'slide-right': `translateX(${type ? '0' : '100%'})`,
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
'slide-left': `translateX(${type ? '0' : '-100%'})`,
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
}
},
// 内置动画类型与实际动画对应字典
animationMode() {
return {
fade: 'opacity',
'slide-top': 'translateY',
'slide-right': 'translateX',
'slide-bottom': 'translateY',
'slide-left': 'translateX',
'zoom-in': 'scale',
'zoom-out': 'scale'
}
},
// 驼峰转中横线
toLine(name) {
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
}
}
}
</script>

View File

@@ -0,0 +1,87 @@
{
"id": "uv-transition",
"displayName": "uv-transition 动画 全面兼容vue3+2、app、h5、小程序等多端",
"version": "1.0.8",
"description": "transition 该组件用于组件的动画过渡效果。",
"keywords": [
"uv-transition",
"uvui",
"uv-ui",
"transition",
"动画"
],
"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"
}
}
}
}
}

View File

@@ -0,0 +1,15 @@
## Transition 动画
> **组件名uv-transition**
该组件用于组件的动画过渡效果,支持自定义动画,开箱即用。
# <a href="https://www.uvui.cn/components/transition.html" target="_blank">查看文档</a>
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
### [更多插件请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -0,0 +1,76 @@
## 1.1.252024-01-20
1.1.20版本更新
## 1.1.242023-12-21
1. luch-request更新
## 1.1.232023-12-12
1. 1.1.19版本
## 1.1.222023-11-28
1. 优化
## 1.1.212023-11-10
1. 1.1.17版本
## 1.1.202023-10-30
1. 1.1.16版本
## 1.1.192023-10-13
1. 兼容vue3
## 1.1.182023-10-12
1. 1.1.15版本
## 1.1.172023-09-27
1. 1.1.14版本发布
## 1.1.162023-09-15
1. 1.1.13版本发布
## 1.1.152023-09-15
1. 更新button.js相关按钮支持open-type="agreePrivacyAuthorization"
## 1.1.142023-09-14
1. 优化dayjs
## 1.1.132023-09-13
1. 优化,$uv中增加unit参数方便组件中使用
## 1.1.122023-09-10
1. 升级版本
## 1.1.112023-09-04
1. 1.1.11版本
## 1.1.102023-08-31
1. 修复customStyle和customClass存在冲突的问题
## 1.1.92023-08-27
1. 版本升级
2. 优化
## 1.1.82023-08-24
1. 版本升级
## 1.1.72023-08-22
1. 版本升级
## 1.1.62023-08-18
uvui版本1.1.6
## 1.0.152023-08-14
1. 更新uvui版本号
## 1.0.132023-08-06
1. 优化
## 1.0.122023-08-06
1. 修改版本号
## 1.0.112023-08-06
1. 路由增加events参数
2. 路由拦截修复
## 1.0.102023-08-01
1. 优化
## 1.0.92023-06-28
优化openType.js
## 1.0.82023-06-15
1. 修改支付宝报错的BUG
## 1.0.72023-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.62023-06-04
1. uv-ui-tools 优化工具组件,兼容更多功能
2. 小程序分享功能优化等
## 1.0.52023-06-02
1. 修改扩展使用mixin中方法的问题
## 1.0.42023-05-23
1. 兼容百度小程序修改bem函数
## 1.0.32023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.22023-05-10
1. 增加Http请求封装
2. 优化
## 1.0.12023-05-04
1. 修改名称及备注
## 1.0.02023-05-04
1. uv-ui工具集首次发布

View File

@@ -0,0 +1,6 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@@ -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
}

View File

@@ -0,0 +1,7 @@
// 引入公共基础类
@import "./libs/css/common.scss";
// 非nvue的样式
/* #ifndef APP-NVUE */
@import "./libs/css/vue.scss";
/* #endif */

View File

@@ -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'
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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,返回的格式为 rgba2552552550.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
}

View File

@@ -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

View File

@@ -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,
};

View File

@@ -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 添加单位如果有rpxupx%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
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}
})
}

View File

@@ -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

View File

@@ -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
*/

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,6 @@
import adapter from '../adapters/index'
export default (config) => {
return adapter(config)
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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 请求头信息,不能设置 RefererApp、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;

View File

@@ -0,0 +1,2 @@
import Request from './core/Request'
export default Request

View File

@@ -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'
}

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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规则类名
* 由于微信小程序H5nvue之间绑定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)
}
})
}
}
}

View File

@@ -0,0 +1,8 @@
export default {
// #ifdef MP-WEIXIN
// 将自定义节点设置成虚拟的去掉自定义组件包裹层更加接近Vue组件的表现能更好的使用flex属性
options: {
virtualHost: true
}
// #endif
}

View File

@@ -0,0 +1,13 @@
export default {
onLoad() {
// 设置默认的转发参数
uni.$uv.mpShare = {
title: '', // 默认为小程序名称
path: '', // 默认为当前页面路径
imageUrl: '' // 默认为当前页面的截图
}
},
onShareAppMessage() {
return uni.$uv.mpShare
}
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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"
}
}
}
}
}

View File

@@ -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">
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
</a>
#### 如使用过程中有任何问题反馈或者您对uv-ui有一些好的建议欢迎加入uv-ui官方交流群<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

View File

@@ -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;
}

View File

@@ -0,0 +1,353 @@
## 1.1.202024-01-20
1. 优化 uv-loading-page重构避免初始加载的时候先显示页面的问题小程序平台由于性能问题可能还是会短暂出现请注意优化处理
2. 优化 uv-textarea confirmType默认值改成return支持换行
3. 优化 uv-ui-tools luch-request更新
4. 修复 uv-drop-down parentData不变的BUG
5. 修复 uv-picker 上个版本引出的BUG
6. 修复 uv-image 设置show-menu-by-longpress不生效的BUG
7. 修复 uv-upload 动态设置deletable为false不生效的BUG
## 1.1.192023-12-12
1. 优化 uv-toast部分重构支持取消遮罩不影响点击其他元素 a. 增加z-index参数b. 增加overlay参数
2. 优化 关于组件如何阻止事件冒泡,增加相关说明:[组件怎么阻止事件冒泡](https://www.uvui.cn/components/feature.html#组件怎么阻止事件冒泡)
3. 优化 持续优化文档
4. 修复 uv-tabs current为字符串activeStyle不生效的BUG
5. 修复 uv-vtabs 在change回调中设置current快速点击菜单导致死循环的BUG
6. 修复 uv-cell right-icon插槽编译到APP端不显示的BUG问题来源[https://gitee.com/climblee/uv-ui/issues/I8LXZI](https://gitee.com/climblee/uv-ui/issues/I8LXZI)
## 1.1.182023-11-28
0. 优化 uv-datetime-picker 增加round属性设置弹窗圆角
1. 优化 uv-subsection 增加customItemStyle属性方便修改bar样式比如设置圆角
2. 修复 uv-picker issues反馈的问题uv-picker在组合式API的自定义组件中columns动态赋值无法显示选项[https://gitee.com/climblee/uv-ui/issues/I8H0GQ](https://gitee.com/climblee/uv-ui/issues/I8H0GQ)
3. 修复 uv-popup issues问题[https://gitee.com/climblee/uv-ui/issues/I8HDLO](https://gitee.com/climblee/uv-ui/issues/I8HDLO)
4. 修复 uv-skeletons 支付宝小程序报错的BUG
5. 修复 uv-image webp之前未使用的BUG & 微信报错的BUG
6. 修复 uv-modal 上版本引出的确认和取消按钮均不显示还有高度的BUG
## 1.1.172023-11-10
0. 交流反馈 欢迎加入uv-ui官方群1交流反馈 5498339131000+
1. 交流反馈 欢迎加入uv-ui官方群2交流反馈 206060892
2. 新增 新增其他小组件下载入口:其他小组件
3. 优化 uv-calendars 增加readonly属性是否为只读状态只读状态下禁止选择日期
4. 修复 uv-search 禁用时不能触发click的BUG
5. 修复 uv-checkbox and uv-radio label文字过长不换行的相关说明及处理
6. 修复 uv-input and uv-search 点击clear按钮在微信小程序不好触发的BUG
7. 修复 uv-list 修复设置ellipsis不生效的BUG
8. 修复 uv-index-list 修复sticky属性不生效的BUG
## 1.1.162023-10-30
1. 交流反馈 欢迎加入uv-ui官方群1交流反馈 549833913940+/1000
2. 交流反馈 欢迎加入uv-ui官方群2交流反馈 206060892
3. 新增 uv-skeletons 骨架屏,全新升级骨架屏,更加灵活,体验更加,强烈推荐使用新版骨架屏。一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。
4. 优化 uv-button 增加后置插槽suffix方便在按钮文字后面增加图标等
5. 优化 uv-popup vue模式内容有背景色设置圆角被遮挡的情况
6. 优化 uv-tabs 点击一个选项change事件重复派发的问题
7. 优化 持续优化文档及其他
8. 修复 uv-album 设置singleSize、multipleSize、space等值带单位存在不显示的BUG
9. 修复 uv-picker 省市级示例设置defaultValue再次选择第二列错乱的BUG
10. 修复 uv-icon imgMode默认值改成aspectFit否则会导致支付宝平台name设置为本地图片显示不全的BUG
11. 修复 uv-transition 在APP-IOS上不能正常显示的BUG
12. 修复 uv-toast、uv-swipe-action、uv-sticky、uv-notify、uv-notice-bar、uv-grid、uv-count-down、uv-code等组件unmounted兼容vue3
## 1.1.152023-10-12
1. 优化 uv-keyboard a. 增加disKeys参数mode = "car"下,被禁用的键,如:['I','O'] b. 增加customabc参数mode = "car"下,是否启用自定义中英文切换内容模式,为了兼容支付宝等小程序不兼容嵌套插槽,导致同时显示自定义内容和原始内容; c. 增加ref方法changeCarModemode = "car"下, 调用此方法可以切换中英文模式; d. 增加@changeCarInputModemode = "car"下,调用此方法可以进行切换中英文; e. 增加插槽abcmode = "car"下,自定义中英文切换内容
2. 优化 uv-checkbox uv-radio 优化https://gitee.com/climblee/uv-ui/issues/I872VD
3. 优化 uv-picker 将immediate-change默认值改为true该值在于change回调的及时性微信小程序生效
4. 优化 uv-tags 兼容customStyle参数等优化
5. 修复 uv-transition 部分情况修改某属性自动关闭的BUG
6. 修复 uv-calendars 懒加载报错https://gitee.com/climblee/uv-ui/issues/I869JS
7. 修复 uv-datetime-picker 设置minDate出现选择错乱的BUG
8. 修复 uv-input 搜狗输入法下存在不可清空的情况
9. 修复 uv-calendars selected没有设置了info或者info设置为空字符串后文本则无法恢复BUG
## 1.1.142023-09-27
1. 优化 uv-list-item 可使用customStyle变量进行样式控制
2. 优化 uv-cell 增加cellStyle参数方便自定义单元格的样式
3. 优化 uv-switch 优化细节
4. 优化 不断优化[文档](https://www.uvui.cn/)
5. 修复 uv-button 通过customStyle修改按钮宽度组件中最外层节点不改变的问题
6. 修复 uv-calendars a. 修复range模式下selected设置了info后选中后导致文本不恢复的问题b. 修复multiple模式下selected自定义信息的颜色没变依然是白色
7. 修复 uv-checkbox uv-checkbox-group之change回调中v-model值不更新的BUG
## 1.1.132023-09-15
1. 优化 uv-button a. 增加参数iconSize用于控制图标的大小b. 增加open-type="agreePrivacyAuthorization"类型,用户同意隐私协议事件回调
2. 优化 uv-picker 三级联动的案例:[https://www.uvui.cn/components/picker.html#省市区三级联动](https://www.uvui.cn/components/picker.html#省市区三级联动)
3. 修复 uv-read-more 全局设置rpx时导致展开高度不对的BUG
4. 修复 uv-tabs a. 设置lineWidth未带单位产生的误差BUGb. 首次加载时,处理下划线会有左到右的过渡效果
5. 修复 uv-textaera 设置autoHeight后出现高度异常的BUG
6. 修复 uv-input H5等情况设置禁用或可读情况下点击事件无效的问题nvue需要特殊处理
7. 修复 uv-calendars a. 在vue2+小程序渲染时闪烁的问题b. 增加allowSameDay参数是否允许日期范围的起止时间为同一天mode=range时有效
8. 修复 uv-safe-bottom 兼容飞书小程序
9. 修复 uv-album 添加依赖,避免导入运行有误
10. 修复 uv-ui-tools 优化组件用到的相关
## 1.1.122023-09-10
1. 修复 uv-popup a. h5初始zIndex错误的问题b. 修复全局设置prop无效的问题
2. 修复 uv-button 修复多个按钮由view包裹显示在一行宽度不正常的BUG
3. 修复 uv-modal a. 修复两个按钮之间竖线不显示的问题b. uv-ui项目自定义按钮示例修复
4. 修复 uv-calendars 修复国际化失效的BUG
5. 修复 uv-keyboard 修复键盘change回调事件产生冲突的BUG
## 1.1.112023-09-02
1. 优化 uv-calendars a. 去除range参数由mode="range"替换b. 新增mode参数不传 / multiple / range分别为单日期 多个日期选择日期范围c. 与uv-calendar选择日期的功能保持一致
2. 优化 uv-modal a. 增加align参数设置文本对齐方式b. 增加textStyle参数扩展文本样式
3. 优化 uv-datetime-picker a. 增加mode="year"模式方便只选择年b. 增加clearDate参数是否清除上次选择
4. 修复 uv-ui-tools 设置customstyle同名计算属性报错The computed property "customStyle" is already defined as a prop
5. 修复 uv-image a. 设置widthFix时出现显示不全的BUGb. 修复抖音等平台在width和height属性改变时出现不显示的BUG
6. 修复 uv-checkbox 点击空隙处或label插槽内容不会选中的问题
7. 修复 uv-radio 点击空隙处或label插槽内容不会选中的问题
8. 修复 uv-calendars 在pages.json中设置easycom会报错的BUG
9. 修复 uv-index-list 设置customNavHeight导致定位不准确的BUG
## 1.1.102023-08-30
1. 交流反馈 欢迎加入uv-ui官方群1交流反馈 549833913
2. 交流反馈 欢迎加入uv-ui官方群2交流反馈 206060892
3. 优化 uv-calendars 1. 去除range参数由mode="range"替换2. 新增mode参数不传 / multiple / range分别为单日期 多个日期选择日期范围3. 与uv-calendar选择日期的功能保持一致
4. 新增 uv-album 新增相册组件及相关文档
5. 优化 其他优化
6. 修复 uv-text app-nvue设置align不生效的BUG
7. 修复 uv-drop-down 自定义内容,点击自定义内容时会自动关闭弹窗的问题
8. 修复 uv-image 异步修改宽高不生效的问题问题来源https://gitee.com/climblee/uv-ui/issues/I7WUQ3
9. 修复 uv-calendars 通过setConfig修改属性不生效的问题出自评论区https://ext.dcloud.net.cn/plugin?id=12287
10. 修复 uv-list 设置边框不生效的BUG
## 1.1.92023-08-27
1. 优化 uv-calendars 1. 去除range参数由mode="range"替换2. 新增mode参数不传 / multiple / range分别为单日期 多个日期选择日期范围3. 与uv-calendar选择日期的功能保持一致
2. 优化 uv-picker 增加round属性设置圆角
3. 修复 uv-calendars 点击返回今天按钮时monthSwitch方法回调参数返回月份不是当天对应月份
4. 修复 uv-radio 1. 设置 labelSize 属性设置无效的问题https://gitee.com/climblee/uv-ui/issues/I7W6UN2. v-model 绑定布尔值控制台报警https://gitee.com/climblee/uv-ui/issues/I7W714
5. 修复 uv-checkbox 1. 设置 label 属性为布尔值不生效的BUG
## 1.1.82023-08-24
1. 优化 uv-popup 弹出不丝滑优化思路https://www.uvui.cn/components/popup.html#yh
2. 修复 uv-switch 取消value传值只能使用v-model传值避免异步操作不生效的BUG
3. 修复 uv-index-list ios端滚动过程中+快速点击右侧导航页面出现空白的BUG
4. 修复 uv-rate 1. 支付宝报错的BUG; 2. 不能选半星的BUG
5. 修复 uv-model 异步loading时确认回调还会一直触发的BUG
6. 修复 uv-swiper 标题文字过多未隐藏掉的BUG
7. 修复 uv-text app-nvue编译不能自动换行的BUG
## 1.1.72023-08-22
1. 优化 uv-drop-down a. 增加@change回调,返回弹窗关闭状态; b. 增加init方法方便位置改变进行调整
2. 优化 部分文档优化
3. 修复 uv-input a. app-nvue-ios端不能输入的BUGb. 键盘高度等值不返回BUG
4. 修复 uv-scroll-list 报错导致不能移动指示器的BUG
5. 修复 uv-search 边距值在上次更新中误改导致不对的BUG
6. 修复 uv-image 设置width和height为100%不生效的BUG
## 1.1.62023-08-18
1. 优化 优化文档
2. 修复 uv-list 使用列表右侧显示 switchswitchChange回调中返回数据为undefined的BUG
3. 修复 uv-checkbox 数据多不换行的BUG
4. 修复 uv-upload 1. 图片预览位置错误的BUG2. 视频预览不生效的BUG3. 改变上传视频宽高不生效的BUG
5. 修复 uv-navbar 在部分ios高版本机型返回按钮不好操作的问题
6. 修复 uv-waterfall 只有一条数据的时候切换的时候数据会左右显示错误的BUG
## 1.1.52023-08-14
1. 优化 uv-pick-color 删除scrollTop参数内部修改后就不需要了
2. 优化 uv-loading-icon 增加textStyle参数可自定义文本样式比如给上边距
3. 修复 uv-safe-bottom 百度小程序报错的BUG
4. 修复 uv-form 设置labelWidth属性时节点渲染有闪动的BUG
5. 修复 uv-grid 设置col属性时节点渲染有闪动的BUG
6. 修复 uv-parse 阻止a标签跳转文档说明
## 1.1.42023-08-13
1. 优化 nvue自定义图标 [详细文档-nvue中自定义图标库](https://www.uvui.cn/guide/customIcon.html#nvue%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%BE%E6%A0%87%E5%BA%93)
2. 优化 uv.$uv.http 在APP.vue页面使用报错的BUG [Api集中管理](https://www.uvui.cn/js/http.html#_3-api%E9%9B%86%E4%B8%AD%E7%AE%A1%E7%90%86)
3. 修复 uv-navbar app-nvue运行ios存在背景图片错乱的问题
4. 修复 uv-list app-nvue运行ios存在分包页面不滚动
5. 修复 uv-textarea 值为null或undefined时显示错误的bug
6. 修复 uv-search 值为null或undefined时显示错误的bug
7. 修复 uv-scroll-list vue2编译报错的BUG
8. 修复 uv-calendars 选择月份弹窗层级的问题
9. 修复 uv-form 动画在vue3 setup语法糖中错乱以及表单其他相关问题解决 [Issues](https://gitee.com/my_dear_li_pan/uv-ui/issues/I7SNTT)
10. 修复 uv-picker-color 滚动页面无法点击的BUG增加scrollTop参数设置滚动条的位置。不设置如果页面出现滚动就需要传该值会出现颜色面板无法进行选颜色的情况。
11. 交流反馈 欢迎加入uv-ui官方群1交流反馈 [549833913](https://www.uvui.cn/components/addQQGroup.html)
12. 交流反馈 欢迎加入uv-ui官方群2交流反馈 [206060892](https://www.uvui.cn/components/addQQGroup.html)
## 1.1.32023-08-06
1. 优化 uv-calendars 1. 增加startText参数; 2. 增加endText参数; 3. 增加selected中的参数; 4. 优化日历范围选择
2. 优化 uv-empty icon属性支持base64图片
3. 优化 uv-navbar 增加背景图片的裁剪模式参数imgMode
4. 优化 uv-picker-color 颜色值不对的BUG
5. 优化 [API文档优化](https://www.uvui.cn/components/changelog.html)
6. 优化 常见问题增加:[怎么隐藏uv-tabs等组件的滚动条](https://www.uvui.cn/components/problem.html#%E4%B9%9D%E3%80%81%E6%80%8E%E4%B9%88%E9%9A%90%E8%97%8Fuv-tabs%E7%AD%89%E7%BB%84%E4%BB%B6%E7%9A%84%E6%BB%9A%E5%8A%A8%E6%9D%A1)
7. 修复 uv-radio name为数字0时不能选中的BUG
8. 修复 uv-textarea 1. v-model设置为数据时的BUG2. 复制过多内容计数显示错误的BUG3. maxlength为-1改成不显示计数
9. 修复 uv-code-input 在vue2模式下v-model设置为0时不生效的BUG
10. 修复 uv-input 在vue2模式下v-model设置为0时不生效的BUG
11. 修复 uv-search 在vue2模式下v-model设置为0时不生效的BUG
12. 修复 uv-ui-tools 1. 路由拦截修复2. 增加events参数
## 1.1.22023-08-03
1. 新增 uv-calendars 新版日历发布
2. 新增 uv-toolbar 组件独立发布老用户更新uv-picker需要手动删除uv-picker目录下的uv-toolbar目录否则会有冲突提示
3. 优化 uv-tags 增加cellChild参数
4. 优化 uv-navbar 兼容背景图片
5. 优化 uv-notice-bar 竖向滚动时候增加change回调
## 1.1.12023-07-30
1. 新增 uv-drop-down 下拉筛选组件兼容app-nvue及多端
2. 优化 uv-textarea 增加confirm-hold参数方便设置进行换行处理
3. 优化 其他关于文档的优化等
## 1.1.02023-07-26
1. 重构 uv-list 全面重构提高性能放弃使用scroll-view具体文档参考uv-list列表
2. 优化 uv-search 1. 增加prefix和suffix 前置和后置插槽2. 增加boxStyle参数方便控制输入框部分的样式
3. 优化 文档优化获取节点布局信息文档新增nvue获取方式的说明
## 1.0.222023-07-26
1. 优化 uv-textarea 组件 增加textStyle和countStyle属性方便控制文本样式
2. 优化 uv-swiper 增加竖向播放属性vertical
3. 优化 uv-icon 支持base64图片格式
4. 优化 uv-transition 和 uv-image 增加参数cellChild属性避免nvue中出现回收后不显示的BUG
5. 优化 uv-button 增加customTextStyle属性方便自定义按钮文字样式
6. 优化 优化部分文档说明
7. 修复 uv-slider 修改背景颜色属性为backgroundColor避免设置不生效
8. 修复 uv-index-list 1. 修复全局设置成rpx存在的高度BUG2. 修复其他BUG
## 1.0.212023-07-22
1. 新增 uv-scroll-list 横向滚动列表组件
2. 优化 增加测试占位图,方便开发者使用线上图片进行测试:[https://www.uvui.cn/components/testPic.html](https://www.uvui.cn/components/testPic.html)
3. 优化 uv-calendar 组件文档示例等优化增加setFormatter说明
4. 优化 uv-notice-bar 优化文档,说明不显示左边图标的使用方法
5. 修复 uv-input 在微信小程序端清除内容存在不能清除的BUG
6. 修复 uv-button 1. 解决微信小程序动态设置hover-class点击态不消失的BUG; 2. 文档优化
7. 修复 uv-waterfall 在tab切换等场景快速切换时会出现报错的BUG
8. 优化 优化其他
## 1.0.202023-07-18
1. 修复 uv-textarea 设置-1不生效
2. 修复 uv-icon 恢复uv-empty相关的图标
3. 修复 uv-empty 恢复设置mode属性的内置图标
4. 优化 [优化文档](https://www.uvui.cn)
## 1.0.192023-07-14
1. 优化 uv-waterfall 当changeList未处理数据时正确返回对应列的数据避免误导
2. 修复 uv-rate VUE3模式下设置value属性不生效的BUG
3. 修复 uv-input VUE3模式下设置value属性不生效的BUG
4. 修复 uv-search VUE3模式下设置value属性不生效的BUG
5. 修复 uv-code-input VUE3模式下设置value属性不生效的BUG
6. 修复 uv-number-box VUE3模式下设置value属性不生效的BUG
7. 修复 uv-radio VUE3模式下设置value属性不生效的BUG
8. 修复 uv-checkbox VUE3模式下设置value属性不生效的BUG
9. 修复 uv-textarea VUE3模式下设置value属性不生效的BUG
10. 修复 uv-switch VUE3模式下设置value属性不生效的BUG
11. 修复 uv-slider VUE3模式下设置value属性不生效的BUG
12. 修复 uv-datetime-picker VUE3模式下设置value属性不生效的BUG
13. 修复 uv-icon 部分图标错误的BUG
## 1.0.182023-07-06
1. 优化 uv-icon 1. 更新图标删除一些不常用的图标2. 删除base64修改成ttf文件引入读取图标。uv-icon 图标
2. 优化 uv-icon nvue自定义图标用法文档说明[点击跳转](https://www.uvui.cn/guide/customIcon.html)
3. 优化 uv-upload 文档示例代码增加fileList参数说明[点击跳转](https://www.uvui.cn/components/upload.html#filelist-options)
4. 修复 uv-checkbox vue3模式下动态修改v-model绑定的值无效的BUG
5. 修复 uv-radio vue3模式下动态修改v-model绑定的值无效的BUG
6. 修复 uv-datetime-picker vue3模式下动态修改v-model绑定的值无效的BUG
## 1.0.172023-07-04
1. 优化 uv-icon 修复NVUE平台主题颜色在APP不生效的BUG
2. 优化 uv-notice-bar 优化增加disableScroll属性
3. 优化 uv-input uv-back-top uv-cell uv-form uv-search uv-modal uv-navbar uv-index-list uv-empty uv-upload 去除插槽判断避免某些平台不显示的BUG
4. 优化 uv-form 优化文档
5. 优化 优化其他相关文档
## 1.0.162023-07-03
1. 优化 uv-transition 动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情[参考文档](https://www.uvui.cn/components/transition.html)
2. 优化 uv-popup 弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/popup.html)
3. 优化 uv-calendar 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/actionSheet.html)
4. 优化 uv-action-sheet 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/calendar.html)
5. 优化 uv-datetime-picker 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/datetimePicker.html)
6. 优化 uv-form 由于弹出层uv-transition的修改组件内部做了相应的修改参数不变。
7. 优化 uv-keyboard 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/keyboard.html)
8. 优化 uv-modal 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/modal.html)
9. 优化 uv-notify 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/notify.html)
10. 优化 uv-overlay 由于弹出层uv-transition的修改组件内部做了相应的修改参数不变。
11. 优化 uv-pick-color 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/pickColor.html)
12. 优化 uv-picker 由于弹出层uv-popup的修改打开和关闭方法更改详情[参考文档](https://www.uvui.cn/components/picker.html)
13. 优化 uv-tooltip 由于弹出层uv-transition的修改组件内部做了相应的修改参数不变。
14. 优化 uv-loading-page 由于弹出层uv-transition的修改组件内部做了相应的修改参数不变。
15. 优化 相关文档的优化更改。
16. 修复 uv-safe-bottom 修复在百度程序抖音小程序不生效的BUG
## 1.0.152023-06-29
1. 欢迎加QQ群交流[549833913](https://www.uvui.cn/components/addQQGroup.html)
2. 优化 uv-swiper 优化1. 增加titleStyle属性方便修改标题样式2. 标题上去掉是否是图片的判断,避免无后缀的图片不显示
3. 优化 uv-steps 优化1. 增加插槽title; 3. 文档关于插槽相关的参数说明完善增加customStyle属性
4. 优化 uv-checkbox 优化增加label文字插槽与radio保持一致优化文档相关说明
5. 优化 uv-modal 优化增加closeLoading方法方便异步加载手动取消加载状态更新文档
6. 优化 uv-image 增加文档说明uv-list、 uv-waterfall等组件在 Android平台使用了list封装所以在该组件中仍然不能使用uv-image等组件
7. 优化 优化更多文档
8. 修复 uv-vtabs 修复非联动情况下内容过多的情况滚动一段距离再切换未滚动到顶部的BUG
9. 修复 uv-image 修复duration属性不生效的BUG
10. 修复 uv-code-input 修复:使用:disabledKeyboard="true"属性事件全部失效的BUG
11. 修复 uv-button 修复设置open-type="chooseAvatar"等值不生效的BUG
## 1.0.142023-06-25
1. 欢迎加QQ群交流[549833913](https://www.uvui.cn/components/addQQGroup.html)
2. 优化 uv-count-down 增加外部样式customStyle参数
3. 优化 文档的全面优化
4. 修复 uv-count-to 1. 修复继续滚动的函数 2. 修改文档错误 4. 适配px和rpx的单位 4. 适配customStyle参数
5. 修复 uv-load-more 修复customStyle参数设置背景等不生效的BUG
6. 修复 uv-code-input 优化下边框
7. 修复 uv-tabs 添加uv-icon依赖
8. 修复 uv-grid 优化修改
9. 修复 uv-cell 优化修改
## 1.0.132023-06-20
1. 优化 uv-calendar formatter格式化中增加topInfo参数
2. 优化 uv-tabs 增加customStyle参数
3. 优化 文档优化,便于开发者直接开干
4. 优化 uv-switch 优化size属性适配单位传递
5. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
6. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
7. 修复 uv-parse 修复在nvue不显示的BUG
8. 修复 uv-form 修复某些条件下报错的BUG
## 1.0.122023-06-14
1. 优化部分组件,优化文档部分细节
2. uv-popup、uv-modal 修复遮罩层zIndex问题
3. uv-form 在vue3的setup语法中ref使用uvForm会导致报错
4. uv-tabs activeStyle设置字体大小可能会导致下划线位置不对BUG
5. uv-pick-color 百度小程序点击报错
6. uv-transition 恢复this.$nextTick
7. uv-picker 抖音小程序选择的时候报错导致不能关闭的BUG
8. uv-checkbox 多余的属性labelDisabled导致APP中报错提示
9. uv-tabbar 底部安全距离组件无效的BUG
10. uv-vtabs 头部存在的时候联动不准确的BUG
## 1.0.112023-06-12
1. uv-radio-group、uv-checkbox-group 兼容自定义样式customStyle方便通过样式调整整体位置等数据较多时允许换行
2. uv-ui-tools 优化内置样式等解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors[详情](https://www.uvui.cn/components/problem.html)
3. uv-datetime-picker 取消defaultIndex参数目前传该值也没实际意义
4. uv-tabbar 增加iconSize参数
5. uv-calendar 增加change回调
6. uv-calendar 修复BUG
7. uv-rate 修复只读或禁止状态下设置value无效的问题
8. uv-popup 修复zIndex问题
9. uv-modal 修复zIndex问题
10. 文档-扩展配置更新:[扩展配置](https://www.uvui.cn/components/setting.html)
11. 文档-优化更新:[uv-ui文档](https://www.uvui.cn/components/changelog.html)
12. 文档-新增常见问题:[常见问题](https://www.uvui.cn/components/problem.html)
13. 优化其他
## 1.0.102023-06-05
1. uv-navbar 渐变背景色兼容
2. uv-calendar 日历选择BUG修复
## 1.0.92023-06-05
1. 新增uv-vtabs垂直选项卡组件主要用于分类展示分类切换功能支持联动和不联动两种模式
2. uv-qrcodeuv-datetime-pickeruv-subsection等文档说明优化避免开发困难优化API相关说明
3. uv-notice-bar 1. 修复在触发error函数报错的BUG2. 修复在text值为undefined的时候解决报错BUG
4. uv-button 等组件修复触发两次事件的BUG
5. uv-datetime-picker 1. 修复重置值存在不更新的BUG2. 优化文档增加filter使用方法说明
6. uv-badge 修复type等属性为null或undefined的时候不显示徽标的BUG
7. uv-ui-tools 优化工具组件,兼容更多功能,小程序分享功能优化等
...
## 1.0.82023-05-27
1. uv-waterfall修复在百度小程序中可能存在的BUG去掉原有的slot方式
2. uv-image修复可能报错的问题
3. uv-pick-color 在文档预览模式中无法点击的问题
4. uv-index-list 修复select事件不触发的问题
5. 优化其他组件及示例项目等
## 1.0.72023-05-25
1. uv-icon 将线上ttf字体包替换成base64避免加载时或者网络差时候显示白色方块
2. uv-text 去掉多余的data-index属性避免警告
3. uv-upload 在fileList的watch中增加deep属性
4. uv-pick-color 去掉template中存在的this.导致头条小程序编译警告
5. uv-image 去掉template中存在的this.导致头条小程序编译警告
## 1.0.62023-05-23
1. 新增uv-pick-color颜色选择器组件
2. uv-toolbar组件增加showBorder属性是否显示下边框
3. uv-transition组件在百度小程序等平台不支持this.$nextick导致下面的逻辑不执行使用延时替换方案
4. uv-ui-tools组件中bem()函数兼容百度/头条小程序等
5. uv-waterfall组件修复在百度/头条小程序显示异常等BUG增加changeList回调函数处理数据同步更新示例等
6. uv-image组件修复在百度/头条小程序等开启observeLazyLoad后显示异常BUG
7. uv-tabs组件修复上次更新导致的在nvue中不滚动的BUG
8. uv-qrcode组件修复在部分平台不显示加载的BUG
9. 修复其他已知问题等
## 1.0.52023-05-17
1. 新增uv-qrcode二维码组件
2. 修复uv-tooltip在vue2模式下的BUG
3. 优化部分问题
## 1.0.42023-05-16
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
2. 优化部分功能
## 1.0.32023-05-12
1. 修复uv-input在vue3模式下双向绑定问题
2. 修复uv-textarea在vue3模式下双向绑定问题
3. 修复uv-rate在vue3模式下双向绑定问题
## 1.0.22023-05-11
1. 更新文档
2. 增加插件下载入口
## 1.0.12023-05-10
1. 所有组件依赖
2. 上传示例项目
## 1.0.02023-05-10
1. uv-ui

View File

@@ -0,0 +1,7 @@
<template>
<view>占位组件请勿使用如需下载示例项目请使用下载插件并导入HBuilderX使用 HBuilderX 导入示例项目下载示例项目ZIP</view>
</template>
<script>
</script>
<style>
</style>

View File

@@ -0,0 +1,162 @@
{
"id": "uv-ui",
"displayName": "uv-ui 破釜沉舟之兼容vue3+2、app、h5、小程序等多端灵活导入利剑出击",
"version": "1.1.20",
"description": "uv-ui 是基于uni-app、部分组件基于uView2.x、全端兼容、支持独立导入、内容丰富的UI框架。破釜沉舟之兼容vue3+2、app、h5、小程序等多端利剑出击开箱即用。",
"keywords": [
"uv-ui",
"uvui",
"UI组件库",
"ui框架",
"ui库"
],
"repository": "https://github.com/climblee/uv-ui",
"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": "https://www.npmjs.com/package/@climblee/uv-ui"
},
"uni_modules": {
"dependencies": [
"uv-skeletons",
"uv-album",
"uv-drop-down",
"uv-calendars",
"uv-scroll-list",
"uv-vtabs",
"uv-pick-color",
"uv-qrcode",
"uv-ui-tools",
"uv-action-sheet",
"uv-alert",
"uv-avatar",
"uv-back-top",
"uv-badge",
"uv-button",
"uv-calendar",
"uv-cell",
"uv-checkbox",
"uv-code",
"uv-code-input",
"uv-collapse",
"uv-count-down",
"uv-count-to",
"uv-datetime-picker",
"uv-divider",
"uv-empty",
"uv-form",
"uv-gap",
"uv-grid",
"uv-icon",
"uv-image",
"uv-index-list",
"uv-input",
"uv-keyboard",
"uv-line",
"uv-line-progress",
"uv-link",
"uv-list",
"uv-loading-icon",
"uv-loading-page",
"uv-load-more",
"uv-modal",
"uv-navbar",
"uv-no-network",
"uv-notice-bar",
"uv-notify",
"uv-number-box",
"uv-overlay",
"uv-parse",
"uv-picker",
"uv-popup",
"uv-radio",
"uv-rate",
"uv-read-more",
"uv-row",
"uv-safe-bottom",
"uv-search",
"uv-skeleton",
"uv-slider",
"uv-status-bar",
"uv-steps",
"uv-sticky",
"uv-subsection",
"uv-swipe-action",
"uv-swiper",
"uv-switch",
"uv-tabbar",
"uv-tabs",
"uv-tags",
"uv-text",
"uv-textarea",
"uv-toast",
"uv-tooltip",
"uv-transition",
"uv-upload",
"uv-waterfall"
],
"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"
}
}
}
}
}

164
uni_modules/uv-ui/readme.md Normal file
View File

@@ -0,0 +1,164 @@
<p align="center">
<span>&nbsp;&nbsp;&nbsp;&nbsp;</span><img alt="logo" src="https://www.uvui.cn/common/logo.png" width="120" height="120">
</p>
<h3 align="center">uv-ui</h3>
<h3 align="center">兼容vue3+2多平台快速开发的UI框架</h3>
[![star](https://gitee.com/climblee/uv-ui/badge/star.svg?theme=gvp)](https://gitee.com/climblee/uv-ui)
[![star](https://gitee.com/climblee/uv-ui/badge/fork.svg?theme=gvp)](https://gitee.com/climblee/uv-ui)
[![star](https://img.shields.io/github/stars/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui)
[![issues](https://img.shields.io/github/issues/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui/issues)
[![Website](https://img.shields.io/badge/uvui-up-blue?style=flat-square)](https://www.uvui.cn)
[![version](https://img.shields.io/badge/version-1.1.16-brightgreen.svg)](https://www.uvui.cn/components/changelog.html)
[![license](https://img.shields.io/github/license/climblee/uv-ui?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
## 温馨提示如需下载uv-ui示例项目请不要使用【下载插件ZIP】按钮。
### uvui官方群1<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=aaoyDvfV7nSee9vzWfzyZM1vKlu3xrNZ&authKey=pdU6HDpyzGUoc1QvQvCmzCbwzsoHgBbToF%2F0ChP4lNjPIPgWHGRE4I99XYGiTcNI&noverify=0&group_code=549833913" target="_blank">549833913</a>
### uvui官方群2<a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=2QbEeKBn6ysCyQ09V4mgst1W8onxFybQ&authKey=aMTGL5zCYwsinG%2FeH0qMlAqXAdyKr5AjXVf2oMnsmj7NCg%2F2HraMU%2FNFxELLIPvp&noverify=0&group_code=206060892" target="_blank">206060892</a>
## uvui特点
1. **uv-ui的前世今生**`uv-ui` 是基于 `uview2.x` 版本改造而来。重命名也是为了避开发布冲突和很多组件 `u-``nvue` 中不能使用的情况,所以这才诞生了`uv-ui`。感谢 `uview-ui` 作者的开源奉献,再次为开源点赞。 同时 `uv-ui` 也是无条件开源。
2. **全端兼容**`uv-ui`支持vue3、vue2、app-vue、app-nvue、h5、小程序等。`uv-ui`的组件都是多端自适应的底层会抹平很多小程序平台的差异或bug。
3. **扩展配置**`uv-ui`内置的方法默认不再挂载到`uni`对象之上,也就意味着默认情况下不能在项目中直接使用`uni.$uv.xxx`使用内置方法。但是可以通过扩展可以解决,通过如下方式进行配置即可,使用方式请参考[扩展配置](https://www.uvui.cn/components/setting.html)。其中包括[ JS工具库](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-js%E5%B7%A5%E5%85%B7%E5%BA%93)、[ 自定义主题](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%BB%E9%A2%98)、[ 基础样式](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E5%9F%BA%E7%A1%80%E6%A0%B7%E5%BC%8F)、[ setconfig](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-setconfig)等。
## 预览
通过微信APP下载不支持微信扫码或浏览器扫码查看演示效果。
<img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-wx.png" width="200" />&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-app.png" width="200" />&nbsp;&nbsp;&nbsp;&nbsp;<img src="https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-h5.png" width="200" />
## 链接
- [官方文档](https://www.uvui.cn)
- [演示地址](https://h5.uvui.cn)
- [更新日志](https://www.uvui.cn/components/changelog.html)
- [关于我们](https://www.uvui.cn/cooperation/about.html)
- <a href="#list">组件列表</a>
## 交流反馈
欢迎加入我们的QQ群交流反馈[点此跳转](https://www.uvui.cn/components/addQQGroup.html)
## 快速开始
方式一:`uv-ui` 强烈建议通过 `下载插件并导入HbuilderX` 导入组件。
方式二:下载完整 [uv-ui项目](https://ext.dcloud.net.cn/plugin?id=12287) 将 `uni_modules` 复制到自己的项目。
方式三:通过 `npm i @climblee/uv-ui` 下载,此方法需要配置 easycom配置详情可查看[安装](https://www.uvui.cn/components/install.html)。
请通过[快速上手](https://www.uvui.cn/components/quickstart.html)了解更详细的内容。
**注意:导入插件后,建议`HBuilderX`重新运行项目,可能新导入的插件不能实时更新而导致不能运行。**
## 使用方法
组件导入 `uni_modules`直接在项目中使用无需通过import引入组件。
```html
<template>
<uv-icon name="baidu" size="30" color="#909399"></uv-icon>
</template>
```
## 扩展功能
`uv-ui` 内置了强大的工具函数、请求封装等,可以根据自身需求进行扩展配置,详情请查看[扩展配置](https://www.uvui.cn/components/setting.html)。
**注意:只有[扩展配置](https://www.uvui.cn/components/setting.html)后才能在自己的项目页面中使用组件库内置方法和变量等**
<div id="list"></div>
## 组件列表
下表为 `uv-ui` 的扩展组件清单,点击每个组件**点击下载&安装**即可在详情页面导入组件到项目下导入后建议重新运行即可直接使用组件无需import和注册。
| 组件名 | 组件说明 |
| --- | --- |
| uv-skeletons | [新版骨架屏(推荐)](https://www.uvui.cn/components/skeletons.html) |
| uv-calendars | [新版日历(推荐)](https://www.uvui.cn/components/calendars.html) |
| uv-drop-down | [下拉筛选](https://www.uvui.cn/components/dropDown.html) |
| uv-scroll-list | [横向滚动列表](https://www.uvui.cn/components/scrollList.html) |
| uv-vtabs | [垂直选项卡](https://www.uvui.cn/components/vtabs.html) |
| uv-pick-color | [颜色选择器](https://www.uvui.cn/components/pickColor.html) |
| uv-qrcode | [二维码](https://www.uvui.cn/components/qrcode.html) |
| uv-waterfall | [瀑布流](https://www.uvui.cn/components/waterfall.html) |
| uv-row | [Layout 布局](https://www.uvui.cn/components/layout.html) |
| uv-icon | [图标](https://www.uvui.cn/components/icon.html) |
| uv-button | [按钮](https://www.uvui.cn/components/button.html) |
| uv-text | [文本](https://www.uvui.cn/components/text.html) |
| uv-link | [超链接](https://www.uvui.cn/components/link.html) |
| uv-image | [图片](https://www.uvui.cn/components/image.html) |
| uv-transition | [动画](https://www.uvui.cn/components/transition.html) |
| uv-form | [表单](https://www.uvui.cn/components/form.html) |
| uv-input | [增强输入框](https://www.uvui.cn/components/input.html) |
| uv-textarea | [增强文本域](https://www.uvui.cn/components/textarea.html) |
| uv-checkbox | [复选框](https://www.uvui.cn/components/checkbox.html) |
| uv-radio | [单选框](https://www.uvui.cn/components/radio.html) |
| uv-switch | [开关选择器](https://www.uvui.cn/components/switch.html) |
| uv-calendar | [日历](https://www.uvui.cn/components/calendar.html) |
| uv-picker | [选择器](https://www.uvui.cn/components/picker.html) |
| uv-datetime-picker | [时间选择器](https://www.uvui.cn/components/datetimePicker.html) |
| uv-code | [验证码倒计时](https://www.uvui.cn/components/code.html) |
| uv-keyboard | [键盘](https://www.uvui.cn/components/keyboard.html) |
| uv-rate | [评分](https://www.uvui.cn/components/rate.html) |
| uv-search | [多功能搜索框](https://www.uvui.cn/components/search.html) |
| uv-number-box | [步进器](https://www.uvui.cn/components/numberBox.html) |
| uv-upload | [上传](https://www.uvui.cn/components/upload.html) |
| uv-slider | [滑动选择器](https://www.uvui.cn/components/slider.html) |
| uv-list | [列表](https://www.uvui.cn/components/list.html) |
| uv-index-list | [索引列表](https://www.uvui.cn/components/indexList.html) |
| uv-tags | [标签](https://www.uvui.cn/components/tag.html) |
| uv-line-progress | [线形进度条](https://www.uvui.cn/components/lineProgress.html) |
| uv-badge | [徽标数](https://www.uvui.cn/components/badge.html) |
| uv-count-down | [倒计时](https://www.uvui.cn/components/countDown.html) |
| uv-count-to | [数字滚动](https://www.uvui.cn/components/countTo.html) |
| uv-avatar | [头像](https://www.uvui.cn/components/avatar.html) |
| uv-skeleton | [骨架屏](https://www.uvui.cn/components/skeleton.html) |
| uv-loading-icon | [加载动画](https://www.uvui.cn/components/loadingIcon.html) |
| uv-loading-page | [加载页](https://www.uvui.cn/components/loadingPage.html) |
| uv-load-more | [加载更多](https://www.uvui.cn/components/loadMore.html) |
| uv-empty | [内容为空](https://www.uvui.cn/components/empty.html) |
| uv-tooltip | [长按提示](https://www.uvui.cn/components/tooltip.html) |
| uv-alert | [警告提示](https://www.uvui.cn/components/alert.html) |
| uv-toast | [消息提示](https://www.uvui.cn/components/toast.html) |
| uv-notice-bar | [滚动通知](https://www.uvui.cn/components/noticeBar.html) |
| uv-notify | [消息提示](https://www.uvui.cn/components/notify.html) |
| uv-no-network | [无网络提示](https://www.uvui.cn/components/noNetwork.html) |
| uv-popup | [弹出层](https://www.uvui.cn/components/popup.html) |
| uv-modal | [模态框](https://www.uvui.cn/components/modal.html) |
| uv-cell | [单元格](https://www.uvui.cn/components/cell.html) |
| uv-swipe-action | [滑动单元格](https://www.uvui.cn/components/swipeAction.html) |
| uv-swiper | [轮播图](https://www.uvui.cn/components/swiper.html) |
| uv-collapse | [折叠面板](https://www.uvui.cn/components/collapse.html) |
| uv-grid | [宫格布局](https://www.uvui.cn/components/grid.html) |
| uv-album | [相册](https://www.uvui.cn/components/album.html) |
| uv-tabbar | [底部导航栏](https://www.uvui.cn/components/tabbar.html) |
| uv-back-top | [返回顶部](https://www.uvui.cn/components/backTop.html) |
| uv-navbar | [自定义导航栏](https://www.uvui.cn/components/navbar.html) |
| uv-action-sheet | [底部操作菜单](https://www.uvui.cn/components/actionSheet.html) |
| uv-tabs | [标签选项卡](https://www.uvui.cn/components/tabs.html) |
| uv-steps | [步骤条](https://www.uvui.cn/components/steps.html) |
| uv-subsection | [分段器](https://www.uvui.cn/components/subsection.html) |
| uv-sticky | [吸顶](https://www.uvui.cn/components/sticky.html) |
| uv-parse | [富文本解析器](https://www.uvui.cn/components/parse.html) |
| uv-overlay | [遮罩层](https://www.uvui.cn/components/overlay.html) |
| uv-code-input | [验证码输入](https://www.uvui.cn/components/codeInput.html) |
| uv-read-more | [展开阅读更多](https://www.uvui.cn/components/readMore.html) |
| uv-line | [线条](https://www.uvui.cn/components/line.html) |
| uv-gap | [间隔槽](https://www.uvui.cn/components/gap.html) |
| uv-divider | [分割线](https://www.uvui.cn/components/divider.html) |
## 版权信息
uv-ui遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议意味着您无需支付任何费用也无需授权即可将uv-ui应用到您的产品中。
## 作者想说
- 开源真的不易,不图大家的钱财,所以希望大家多多鼓励支持,希望不要恶意评论,有问题加群快速解决。
- 遇到BUG是一件很正常的事情是程序肯定就有BUG所以希望大家能以理解的心态去提出BUG然后作者才有动力去努力修复。
- 最后觉得好用的小伙伴,不要吝啬你的双手,给个好评就是给我们最大的鼓励。
# 恶评者手下留情有事加QQ群解决549833913

View File

@@ -0,0 +1,15 @@
## 2.0.52024-04-24
## 流式输出代码块解决方案
## 2.0.42023-12-06
### 长按复制代码改为点击代码块复制
## 2.0.32023-10-30
doc: 文档说明
## 2.0.22023-10-30
- 新增长按复制代码-仅小程序可用
- 新增代码块语言显示
## 2.0.12023-10-27
##支持vue2,vue3
## 2.0.02022-11-01
使用mp-html自带的插件,重新生成uniapp包,大幅减少插件体积
## 1.0.02022-09-13
首次发布

Some files were not shown because too many files have changed in this diff Show More