Commit f21fbd7c authored by PC-20180318CEYD\Administrator's avatar PC-20180318CEYD\Administrator

Merge branch 'tonghao' into tmp

# Conflicts:
#	uview/components/u-alert-tips/u-alert-tips.vue
#	uview/components/u-badge/u-badge.vue
#	uview/components/u-count-down/u-count-down.vue
#	uview/components/u-count-to/u-count-to.vue
#	uview/components/u-divider/u-divider.vue
#	uview/components/u-gap/u-gap.vue
#	uview/components/u-grid/u-grid.vue
#	uview/components/u-lazy-load/u-lazy-load.vue
#	uview/components/u-loadmore/u-loadmore.vue
#	uview/components/u-message-input/u-message-input.vue
#	uview/components/u-navbar/u-navbar.vue
#	uview/components/u-notice-bar/u-notice-bar.vue
#	uview/components/u-search/u-search.vue
#	uview/components/u-section/u-section.vue
parents 09385673 da09bd81
<template> <template>
<u-popup mode="bottom" :border-radius="borderRadius" <u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
:popup="false" v-model="value" :maskCloseAble="maskCloseAble" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" <view class="u-tips u-border-bottom" v-if="tips.text" :style="tipsStyle">
@close="popupClose" {{tips.text}}
:z-index="uZIndex" </view>
> <block v-for="(item, index) in list" :key="index">
<view class="u-tips u-border-bottom" v-if="tips.text" :style="tipsStyle"> <view @touchmove.stop.prevent @tap="itemClick(index)" :style="[itemStyle(index)]" class="u-action-sheet-item" :class="[index < list.length - 1 ? 'u-border-bottom' : '']"
{{tips.text}} hover-class="u-hover-class" :hover-stay-time="150">
</view> {{item.text}}
<block v-for="(item, index) in list" :key="index"> </view>
<view @touchmove.stop.prevent @tap="itemClick(index)" :style="[itemStyle(index)]" class="u-action-sheet-item" :class="[index < list.length - 1 ? 'u-border-bottom' : '']" </block>
hover-class="u-hover-class" :hover-stay-time="150"> <view class="u-gab" v-if="cancelBtn">
{{item.text}} </view>
</view> <view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
</block> :hover-stay-time="150" v-if="cancelBtn" @tap="close">取消</view>
<view class="u-gab" v-if="cancelBtn"> </u-popup>
</view> </template>
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class" :hover-stay-time="150" v-if="cancelBtn"
@tap="close">取消</view> <script>
</u-popup> /**
</template> * alertTips 提示
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
<script> * @tutorial https://www.uviewui.com/components/actionSheet.html
export default { * @property {Array<Object>} list 按钮的文字数组,见官方文档示例
props: { * @property {Object} tips 顶部的提示文字,见官方文档示例
// 点击遮罩是否可以关闭actionsheet * @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
maskCloseAble: { * @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
type: Boolean, * @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
default: true * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
}, * @property {Number String} z-index z-index值(默认1075)
// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx * @event {Function} click 点击ActionSheet列表项时触发
list: { * @event {Function} close 点击取消按钮时触发
type: Array, * @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
default () { */
// 如下 export default {
// return [{ name: "u-action-sheet",
// text: '确定', props: {
// color: '', // 点击遮罩是否可以关闭actionsheet
// fontSize: '' maskCloseAble: {
// }] type: Boolean,
return []; default: true
} },
}, // 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
// 顶部的提示文字 list: {
tips: { type: Array,
type: Object, default () {
default () { // 如下
return { // return [{
text: '', // text: '确定',
color: '', // color: '',
fontSize: '26' // fontSize: ''
} // }]
} return [];
}, }
// 底部的取消按钮 },
cancelBtn: { // 顶部的提示文字
type: Boolean, tips: {
default: true type: Object,
}, default () {
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 return {
safeAreaInsetBottom: { text: '',
type: Boolean, color: '',
default: false fontSize: '26'
}, }
// 通过双向绑定控制组件的弹出与收起 }
value: { },
type: Boolean, // 底部的取消按钮
default: false cancelBtn: {
}, type: Boolean,
// 弹出的顶部圆角值 default: true
borderRadius: { },
type: [String, Number], // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
default: 0 safeAreaInsetBottom: {
}, type: Boolean,
// 弹出的z-index值 default: false
zIndex: { },
type: [String, Number], // 通过双向绑定控制组件的弹出与收起
default: 0 value: {
} type: Boolean,
}, default: false
computed: { },
// 顶部提示的样式 // 弹出的顶部圆角值
tipsStyle() { borderRadius: {
let style = {}; type: [String, Number],
if (this.tips.color) style.color = this.tips.color; default: 0
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'; },
return style; // 弹出的z-index值
}, zIndex: {
// 操作项目的样式 type: [String, Number],
itemStyle() { default: 0
return (index) => { }
let style = {}; },
if (this.list[index].color) style.color = this.list[index].color; computed: {
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'; // 顶部提示的样式
return style; tipsStyle() {
} let style = {};
}, if (this.tips.color) style.color = this.tips.color;
uZIndex() { if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
// 如果用户有传递z-index值,优先使用 return style;
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; },
} // 操作项目的样式
}, itemStyle() {
methods: { return (index) => {
// 点击取消按钮 let style = {};
close() { if (this.list[index].color) style.color = this.list[index].color;
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数 if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
// 这是一个vue发送事件的特殊用法 return style;
this.popupClose(); }
this.$emit('close'); },
}, uZIndex() {
// 弹窗关闭 // 如果用户有传递z-index值,优先使用
popupClose() { return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
this.$emit('input', false); }
}, },
// 点击某一个itemif (!this.show) return; methods: {
itemClick(index) { // 点击取消按钮
this.$emit('click', index); close() {
this.$emit('input', false); // 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
} // 这是一个vue发送事件的特殊用法
} this.popupClose();
} this.$emit('close');
</script> },
// 弹窗关闭
<style lang="scss" scoped> popupClose() {
.u-tips { this.$emit('input', false);
font-size: 26rpx; },
text-align: center; // 点击某一个itemif (!this.show) return;
padding: 34rpx 0; itemClick(index) {
line-height: 1; this.$emit('click', index);
color: $u-tips-color; this.$emit('input', false);
} }
}
.u-action-sheet-item { }
display: flex; </script>
line-height: 1;
justify-content: center; <style lang="scss" scoped>
align-items: center; .u-tips {
font-size: 34rpx; font-size: 26rpx;
padding: 34rpx 0; text-align: center;
} padding: 34rpx 0;
line-height: 1;
.u-gab { color: $u-tips-color;
height: 12rpx; }
background-color: rgb(234, 234, 236);
} .u-action-sheet-item {
display: flex;
.u-actionsheet-cancel { line-height: 1;
color: $u-main-color; justify-content: center;
} align-items: center;
font-size: 34rpx;
padding: 34rpx 0;
}
.u-gab {
height: 12rpx;
background-color: rgb(234, 234, 236);
}
.u-actionsheet-cancel {
color: $u-main-color;
}
</style> </style>
...@@ -7,6 +7,16 @@ ...@@ -7,6 +7,16 @@
</template> </template>
<script> <script>
/**
* alertTips 提示
* @description 该组件一般的图片裁剪需求场景,尤其适合于头像裁剪方面。
* @tutorial https://www.uviewui.com/components/avatarCropper.html
* @property {String Number} dest-width 输出图片宽度,高等于宽,单位px(默认200)
* @property {String Number} rect-width 裁剪框宽度,高等于宽,单位px(默认200)
* @property {String} file-type 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可(默认jpg)
* @event {Function} uAvatarCropper 裁剪结束后的事件,通过uni.$on监听
* @example <image class="u-avatar-demo" :src="avatar" mode="aspectFill"></image>
*/
let base64Avatar = "data:image/png;base64,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"; let base64Avatar = "data:image/png;base64,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";
export default { export default {
props: { props: {
......
<template> <template>
<button id="u-wave-btn" class="u-btn u-line-1 u-fix-ios-appearance" :class="[ <button id="u-wave-btn" class="u-btn u-line-1 u-fix-ios-appearance" :class="[
'u-size-' + size, 'u-size-' + size,
plain ? 'u-' + type + '-plain' : '', plain ? 'u-' + type + '-plain' : '',
loading ? 'u-loading' : '', loading ? 'u-loading' : '',
shape == 'circle' ? 'u-round-circle' : '', shape == 'circle' ? 'u-round-circle' : '',
hairLine ? showHairLineBorder : 'u-bold-border', hairLine ? showHairLineBorder : 'u-bold-border',
]" ]"
:disabled="disabled" :disabled="disabled" :form-type="formType" :open-type="openType" :app-parameter="appParameter"
:form-type="formType" :hover-stop-propagation="hoverStopPropagation" :send-message-title="sendMessageTitle" send-message-path="sendMessagePath"
:open-type="openType" :lang="lang" :session-from="sessionFrom" :send-message-img="sendMessageImg" :show-message-card="showMessageCard"
:app-parameter="appParameter" @getphonenumber="getphonenumber" @getuserinfo="getuserinfo" @error="error" @opensetting="opensetting" @launchapp="launchapp"
:hover-stop-propagation="hoverStopPropagation" :style="[buttonStyle]" @tap="click($event)" :hover-class="getHoverClass" :loading="loading">
:send-message-title="sendMessageTitle" <slot></slot>
send-message-path="sendMessagePath" <view v-if="ripple" class="u-wave-ripple" :class="[waveActive ? 'u-wave-active' : '']" :style="{
:lang="lang" 'top': rippleTop + 'px',
:session-from="sessionFrom" 'left': rippleLeft + 'px',
:send-message-img="sendMessageImg" 'width': fields.targetWidth + 'px',
:show-message-card="showMessageCard" 'height': fields.targetWidth + 'px',
@getphonenumber="getphonenumber" 'background-color': (rippleBgColor || 'rgba(0, 0, 0, 0.15)')
@getuserinfo="getuserinfo" }">
@error="error" </view>
@opensetting="opensetting" </button>
@launchapp="launchapp" </template>
:style="[buttonStyle]" @tap="click($event)" :hover-class="getHoverClass" :loading="loading">
<slot></slot> <script>
<view v-if="ripple" class="u-wave-ripple" :class="[waveActive ? 'u-wave-active' : '']" :style="{ /**
'top': rippleTop + 'px', * alertTips 提示
'left': rippleLeft + 'px', * @description Button 按钮
'width': fields.targetWidth + 'px', * @tutorial https://www.uviewui.com/components/button.html
'height': fields.targetWidth + 'px', * @property {String} size 按钮的大小
'background-color': (rippleBgColor || 'rgba(0, 0, 0, 0.15)') * @property {Boolean} ripple 是否开启点击水波纹效果
}"> * @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效
</view> * @property {String} type 按钮的样式类型
</button> * @property {Boolean} plain 按钮是否镂空,背景色透明
</template> * @property {Boolean} disabled 是否禁用
* @property {Boolean} shape 按钮外观形状,见文档说明
<script> * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈)
* @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
* @property {String} open-type 开放能力
* @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持)
* @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒
* @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒
* @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明
* @event {Function} click 按钮点击
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
* @event {Function} error 当使用开放能力时,发生错误的回调
* @event {Function} opensetting 在打开授权设置页并关闭后回调
* @event {Function} launchapp 打开 APP 成功的回调
* @example <u-button>月落</u-button>
*/
export default { export default {
props: { name:"u-button",
// 是否细边框 props: {
hairLine: { // 是否细边框
type: Boolean, hairLine: {
default: true type: Boolean,
}, default: true
// 按钮的预置样式,default,primary,error,warning,success },
type: { // 按钮的预置样式,default,primary,error,warning,success
type: String, type: {
default: 'default' type: String,
}, default: 'default'
// 按钮尺寸,default,medium,mini },
size: { // 按钮尺寸,default,medium,mini
type: String, size: {
default: 'default' type: String,
}, default: 'default'
// 按钮形状,circle(两边为半圆),square(带圆角) },
shape: { // 按钮形状,circle(两边为半圆),square(带圆角)
type: String, shape: {
default: 'square' type: String,
}, default: 'square'
// 按钮是否镂空 },
plain: { // 按钮是否镂空
type: Boolean, plain: {
default: false type: Boolean,
}, default: false
// 是否禁止状态 },
disabled: { // 是否禁止状态
type: Boolean, disabled: {
default: false type: Boolean,
}, default: false
// 是否加载中 },
loading: { // 是否加载中
type: Boolean, loading: {
default: false type: Boolean,
}, default: false
// 开放能力,具体请看uniapp稳定关于button组件部分说明 },
// https://uniapp.dcloud.io/component/button // 开放能力,具体请看uniapp稳定关于button组件部分说明
openType: { // https://uniapp.dcloud.io/component/button
type: String, openType: {
default: '' type: String,
}, default: ''
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 },
// 取值为submit(提交表单),reset(重置表单) // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
formType: { // 取值为submit(提交表单),reset(重置表单)
type: String, formType: {
default: '' type: String,
}, default: ''
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 },
// 只微信小程序、QQ小程序有效 // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
appParameter: { // 只微信小程序、QQ小程序有效
type: String, appParameter: {
default: '' type: String,
}, default: ''
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 },
hoverStopPropagation: { // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
type: Boolean, hoverStopPropagation: {
default: false type: Boolean,
}, default: false
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 },
lang: { // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
type: String, lang: {
default: 'en' type: String,
}, default: 'en'
// 会话来源,open-type="contact"时有效。只微信小程序有效 },
sessionFrom: { // 会话来源,open-type="contact"时有效。只微信小程序有效
type: String, sessionFrom: {
default: '' type: String,
}, default: ''
// 会话内消息卡片标题,open-type="contact"时有效 },
// 默认当前标题,只微信小程序有效 // 会话内消息卡片标题,open-type="contact"时有效
sendMessageTitle: { // 默认当前标题,只微信小程序有效
type: String, sendMessageTitle: {
default: '' type: String,
}, default: ''
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 },
// 默认当前分享路径,只微信小程序有效 // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
sendMessagePath: { // 默认当前分享路径,只微信小程序有效
type: String, sendMessagePath: {
default: '' type: String,
}, default: ''
// 会话内消息卡片图片,open-type="contact"时有效 },
// 默认当前页面截图,只微信小程序有效 // 会话内消息卡片图片,open-type="contact"时有效
sendMessageImg: { // 默认当前页面截图,只微信小程序有效
type: String, sendMessageImg: {
default: '' type: String,
}, default: ''
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, },
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效 // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
showMessageCard: { // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
type: Boolean, showMessageCard: {
default: false type: Boolean,
}, default: false
// 手指按(触摸)按钮时按钮时的背景颜色 },
hoverBgColor: { // 手指按(触摸)按钮时按钮时的背景颜色
type: String, hoverBgColor: {
default: '' type: String,
}, default: ''
// 水波纹的背景颜色 },
rippleBgColor: { // 水波纹的背景颜色
type: String, rippleBgColor: {
default: '' type: String,
}, default: ''
// 是否开启水波纹效果 },
ripple: { // 是否开启水波纹效果
type: Boolean, ripple: {
default: false type: Boolean,
}, default: false
// 按下的类名 },
hoverClass: { // 按下的类名
type: String, hoverClass: {
default: '' type: String,
}, default: ''
// 自定义样式,对象形式 },
customStyle: { // 自定义样式,对象形式
type: Object, customStyle: {
default() { type: Object,
return {} default () {
} return {}
} }
}, }
computed: { },
// 当没有传bgColor变量时,按钮按下去的颜色类名 computed: {
getHoverClass() { // 当没有传bgColor变量时,按钮按下去的颜色类名
// 如果开启水波纹效果,则不启用hover-class效果 getHoverClass() {
if (this.loading || this.disabled || this.ripple || this.hoverClass) return ''; // 如果开启水波纹效果,则不启用hover-class效果
let hoverClass = ''; if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover'; let hoverClass = '';
return hoverClass; hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
}, return hoverClass;
// 按钮主题 },
buttonStyle() { // 按钮主题
let style = {}; buttonStyle() {
if (this.type == 'default') { let style = {};
if (this.disabled) { if (this.type == 'default') {
style.color = "#c0c4cc"; if (this.disabled) {
style.backgroundColor = "#ffffff"; style.color = "#c0c4cc";
style.borderColor = "#e4e7ed"; style.backgroundColor = "#ffffff";
} else { style.borderColor = "#e4e7ed";
style.color = this.$u.color['contentColor']; } else {
style.backgroundColor = "#ffffff"; style.color = this.$u.color['contentColor'];
style.borderColor = "#c0c4cc"; style.backgroundColor = "#ffffff";
} style.borderColor = "#c0c4cc";
} else { }
if (this.disabled) { } else {
if(this.plain) { if (this.disabled) {
style.color = this.$u.color[this.type + 'Disabled']; if (this.plain) {
style.backgroundColor = this.$u.color[this.type + 'Light']; style.color = this.$u.color[this.type + 'Disabled'];
style.borderColor = this.$u.color[this.type + 'Disabled']; style.backgroundColor = this.$u.color[this.type + 'Light'];
} else { style.borderColor = this.$u.color[this.type + 'Disabled'];
style.color = "#ffffff"; } else {
style.backgroundColor = this.$u.color[this.type + 'Disabled']; style.color = "#ffffff";
style.borderColor = this.$u.color[this.type + 'Disabled']; style.backgroundColor = this.$u.color[this.type + 'Disabled'];
} style.borderColor = this.$u.color[this.type + 'Disabled'];
} else { }
if(this.plain) { } else {
style.color = this.$u.color[this.type] if (this.plain) {
style.backgroundColor = this.$u.color[this.type + 'Light']; style.color = this.$u.color[this.type]
style.borderColor = this.$u.color[this.type + 'Disabled']; style.backgroundColor = this.$u.color[this.type + 'Light'];
} else { style.borderColor = this.$u.color[this.type + 'Disabled'];
style.color = "#ffffff"; } else {
style.backgroundColor = this.$u.color[this.type]; style.color = "#ffffff";
style.borderColor = this.$u.color[this.type]; style.backgroundColor = this.$u.color[this.type];
} style.borderColor = this.$u.color[this.type];
} }
} }
}
return Object.assign(style, this.customStyle);
}, return Object.assign(style, this.customStyle);
// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象 },
showHairLineBorder() { // 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象
if(['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) { showHairLineBorder() {
return ''; if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
} else { return '';
return 'u-hairline-border'; } else {
} return 'u-hairline-border';
} }
}, }
data() { },
return { data() {
rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离 return {
rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离 rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离
fields: {}, // 波纹按钮节点信息 rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离
waveActive: false, // 激活水波纹 fields: {}, // 波纹按钮节点信息
} waveActive: false, // 激活水波纹
}, }
methods: { },
// 按钮点击 methods: {
click(e) { // 按钮点击
// 如果按钮时disabled和loading状态,不触发水波纹效果 click(e) {
if (this.loading === true || this.disabled === true) return; // 如果按钮时disabled和loading状态,不触发水波纹效果
// 是否开启水波纹效果 if (this.loading === true || this.disabled === true) return;
if (this.ripple) { // 是否开启水波纹效果
// 每次点击时,移除上一次的类,再次添加,才能触发动画效果 if (this.ripple) {
this.waveActive = false; // 每次点击时,移除上一次的类,再次添加,才能触发动画效果
this.$nextTick(function() { this.waveActive = false;
this.getWaveQuery(e); this.$nextTick(function() {
}); this.getWaveQuery(e);
} });
this.$emit('click'); }
}, this.$emit('click');
// 查询按钮的节点信息 },
getWaveQuery(e) { // 查询按钮的节点信息
this.getElQuery().then(res => { getWaveQuery(e) {
// 查询返回的是一个数组节点 this.getElQuery().then(res => {
let data = res[0]; // 查询返回的是一个数组节点
// 查询不到节点信息,不操作 let data = res[0];
if (!data.width || !data.width) return; // 查询不到节点信息,不操作
// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边 if (!data.width || !data.width) return;
// 最终的方形(变换后的圆形)才能覆盖整个按钮 // 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边
data.targetWidth = (data.height > data.width ? data.height : data.width); // 最终的方形(变换后的圆形)才能覆盖整个按钮
if (!data.targetWidth) return; data.targetWidth = (data.height > data.width ? data.height : data.width);
this.fields = data; if (!data.targetWidth) return;
let touchesX = '', this.fields = data;
touchesY = ''; let touchesX = '',
// #ifdef MP-BAIDU touchesY = '';
touchesX = e.changedTouches[0].clientX; // #ifdef MP-BAIDU
touchesY = e.changedTouches[0].clientY; touchesX = e.changedTouches[0].clientX;
// #endif touchesY = e.changedTouches[0].clientY;
// #ifdef MP-ALIPAY // #endif
touchesX = e.detail.clientX; // #ifdef MP-ALIPAY
touchesY = e.detail.clientY; touchesX = e.detail.clientX;
// #endif touchesY = e.detail.clientY;
// #ifndef MP-BAIDU || MP-ALIPAY // #endif
touchesX = e.touches[0].clientX; // #ifndef MP-BAIDU || MP-ALIPAY
touchesY = e.touches[0].clientY; touchesX = e.touches[0].clientX;
// #endif touchesY = e.touches[0].clientY;
// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top // #endif
// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置 // 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top
// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置 // 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置
this.rippleTop = (touchesY - data.top - (data.targetWidth / 2)); // 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置
this.rippleLeft = (touchesX - data.left - (data.targetWidth / 2)); this.rippleTop = (touchesY - data.top - (data.targetWidth / 2));
this.$nextTick(() => { this.rippleLeft = (touchesX - data.left - (data.targetWidth / 2));
this.waveActive = true; this.$nextTick(() => {
}) this.waveActive = true;
}) })
}, })
// 获取节点信息 },
getElQuery() { // 获取节点信息
return new Promise(resolve => { getElQuery() {
let queryInfo = ''; return new Promise(resolve => {
// 获取元素节点信息,请查看uniapp相关文档 let queryInfo = '';
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect // 获取元素节点信息,请查看uniapp相关文档
queryInfo = uni.createSelectorQuery().in(this); // https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
queryInfo.select('.u-btn').boundingClientRect(); queryInfo = uni.createSelectorQuery().in(this);
queryInfo.exec((data) => { queryInfo.select('.u-btn').boundingClientRect();
resolve(data) queryInfo.exec((data) => {
}); resolve(data)
}) });
}, })
// 下面为对接uniapp官方按钮开放能力事件回调的对接 },
getphonenumber(res) { // 下面为对接uniapp官方按钮开放能力事件回调的对接
this.$emit('getphonenumber', res); getphonenumber(res) {
}, this.$emit('getphonenumber', res);
getuserinfo(res) { },
this.$emit('getuserinfo', res); getuserinfo(res) {
}, this.$emit('getuserinfo', res);
error(res) { },
this.$emit('error', res); error(res) {
}, this.$emit('error', res);
opensetting(res) { },
this.$emit('opensetting', res); opensetting(res) {
}, this.$emit('opensetting', res);
launchapp(res) { },
this.$emit('launchapp', res); launchapp(res) {
}, this.$emit('launchapp', res);
} },
} }
</script> }
</script>
<style scoped lang="scss">
button::after { <style scoped lang="scss">
border: none; button::after {
} border: none;
}
.u-btn {
position: relative; .u-btn {
border: 0; position: relative;
//border-radius: 10rpx; border: 0;
display: inline-block; //border-radius: 10rpx;
overflow: hidden; display: inline-block;
line-height: 1; overflow: hidden;
display: flex; line-height: 1;
align-items: center; display: flex;
justify-content: center; align-items: center;
cursor: pointer; justify-content: center;
padding: 0 40rpx; cursor: pointer;
z-index: 1; padding: 0 40rpx;
box-sizing: border-box; z-index: 1;
transition: all 0.15s; box-sizing: border-box;
} transition: all 0.15s;
}
.u-hairline-border:after {
content: ' '; .u-hairline-border:after {
position: absolute; content: ' ';
pointer-events: none; position: absolute;
// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border) pointer-events: none;
box-sizing: border-box; // 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border)
// 中心点作为变形(scale())的原点 box-sizing: border-box;
-webkit-transform-origin: 0 0; // 中心点作为变形(scale())的原点
transform-origin: 0 0; -webkit-transform-origin: 0 0;
left: 0; transform-origin: 0 0;
top: 0; left: 0;
width: 200%; top: 0;
height: 200%; width: 200%;
-webkit-transform: scale(0.5, 0.5); height: 200%;
transform: scale(0.5, 0.5); -webkit-transform: scale(0.5, 0.5);
border: 1px solid currentColor; transform: scale(0.5, 0.5);
z-index: 0; border: 1px solid currentColor;
} z-index: 0;
}
.u-bold-border {
border: 1px solid #FFFFFF; .u-bold-border {
} border: 1px solid #FFFFFF;
}
.u-wave-ripple {
z-index: 0; .u-wave-ripple {
position: absolute; z-index: 0;
border-radius: 100%; position: absolute;
background-clip: padding-box; border-radius: 100%;
pointer-events: none; background-clip: padding-box;
user-select: none; pointer-events: none;
transform: scale(0); user-select: none;
opacity: 1; transform: scale(0);
transform-origin: center; opacity: 1;
} transform-origin: center;
}
.u-wave-ripple.u-wave-active {
opacity: 0; .u-wave-ripple.u-wave-active {
transform: scale(2); opacity: 0;
transition: opacity 1s linear, transform 0.4s linear; transform: scale(2);
} transition: opacity 1s linear, transform 0.4s linear;
}
.u-round-circle {
border-radius: 100rpx; .u-round-circle {
} border-radius: 100rpx;
}
.u-round-circle::after {
border-radius: 100rpx; .u-round-circle::after {
} border-radius: 100rpx;
}
.u-loading::after {
background-color: hsla(0, 0%, 100%, .35); .u-loading::after {
} background-color: hsla(0, 0%, 100%, .35);
}
.u-size-default {
font-size: 30rpx; .u-size-default {
height: 80rpx; font-size: 30rpx;
line-height: 80rpx; height: 80rpx;
} line-height: 80rpx;
}
.u-size-medium {
display: inline-flex; .u-size-medium {
width: auto; display: inline-flex;
font-size: 26rpx; width: auto;
height: 70rpx; font-size: 26rpx;
line-height: 70rpx; height: 70rpx;
padding: 0 80rpx; line-height: 70rpx;
} padding: 0 80rpx;
}
.u-size-mini {
display: inline-flex; .u-size-mini {
width: auto; display: inline-flex;
font-size: 22rpx; width: auto;
padding-top: 1px; font-size: 22rpx;
height: 50rpx; padding-top: 1px;
line-height: 50rpx; height: 50rpx;
padding: 0 20rpx; line-height: 50rpx;
} padding: 0 20rpx;
}
.u-primary-plain-hover {
color: #FFFFFF!important; .u-primary-plain-hover {
background: $u-type-primary-dark !important; color: #FFFFFF !important;
} background: $u-type-primary-dark !important;
}
.u-default-plain-hover {
color: $u-type-primary-dark !important; .u-default-plain-hover {
background: $u-type-primary-light !important; color: $u-type-primary-dark !important;
} background: $u-type-primary-light !important;
}
.u-success-plain-hover {
color: #FFFFFF!important; .u-success-plain-hover {
background: $u-type-success-dark!important; color: #FFFFFF !important;
} background: $u-type-success-dark !important;
}
.u-warning-plain-hover {
color: #FFFFFF!important; .u-warning-plain-hover {
background: $u-type-warning-dark !important; color: #FFFFFF !important;
} background: $u-type-warning-dark !important;
}
.u-error-plain-hover {
color: #FFFFFF!important; .u-error-plain-hover {
background: $u-type-error-dark !important; color: #FFFFFF !important;
} background: $u-type-error-dark !important;
}
.u-info-plain-hover {
color: #FFFFFF!important; .u-info-plain-hover {
background: $u-type-info-dark !important; color: #FFFFFF !important;
} background: $u-type-info-dark !important;
}
.u-default-hover {
color: $u-type-primary-dark !important; .u-default-hover {
border-color: $u-type-primary-dark !important; color: $u-type-primary-dark !important;
background-color: $u-type-primary-light !important;; border-color: $u-type-primary-dark !important;
} background-color: $u-type-primary-light !important;
;
.u-primary-hover { }
background: $u-type-primary-dark !important;
color: #fff; .u-primary-hover {
} background: $u-type-primary-dark !important;
color: #fff;
.u-success-hover { }
background: $u-type-success-dark !important;
color: #fff; .u-success-hover {
} background: $u-type-success-dark !important;
color: #fff;
.u-info-hover { }
background: $u-type-info-dark !important;
color: #fff; .u-info-hover {
} background: $u-type-info-dark !important;
color: #fff;
.u-warning-hover { }
background: $u-type-warning-dark !important;
color: #fff; .u-warning-hover {
} background: $u-type-warning-dark !important;
color: #fff;
.u-error-hover { }
background: $u-type-error-dark !important;
color: #fff; .u-error-hover {
} background: $u-type-error-dark !important;
color: #fff;
}
</style> </style>
<template> <template>
<view class="u-keyboard" @touchmove.stop.prevent> <view class="u-keyboard" @touchmove.stop.prevent>
<view class="u-keyboard-grids"> <view class="u-keyboard-grids">
<block> <block>
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i"> <view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn" v-for="(item, j) in group" :key="j"> <view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
{{ item }} v-for="(item, j) in group" :key="j">
</view> {{ item }}
</view> </view>
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back" hover-class="u-hover-class"> </view>
<u-icon :size="38" name="backspace" :bold="true"></u-icon> <view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
</view> hover-class="u-hover-class">
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode"> <u-icon :size="38" name="backspace" :bold="true"></u-icon>
<text class="zh" :class="[!abc ? 'active' : 'inactive']"></text> </view>
/ <view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
<text class="en" :class="[abc ? 'active' : 'inactive']"></text> <text class="zh" :class="[!abc ? 'active' : 'inactive']"></text>
</view> /
</block> <text class="en" :class="[abc ? 'active' : 'inactive']"></text>
</view> </view>
</view> </block>
</template> </view>
</view>
<script> </template>
export default {
props: { <script>
// 是否打乱键盘按键的顺序 /**
random: { * alertTips 提示
type: Boolean, * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。
default: false * @tutorial https://www.uviewui.com/components/keyboard.html
} * @property {String} mode 键盘类型,见官网基本使用的说明(默认number)
}, * @property {Boolean} dot-enabled 是否显示"."按键,只在mode=number时有效(默认true)
data() { * @property {Boolean} tooltip 是否显示键盘顶部工具条(默认true)
return { * @property {String} tips 工具条中间的提示文字,见官网基本使用的说明,如不需要,请传""空字符
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 * @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮(默认true)
abc: false * @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮(默认true)
}; * @property {Boolean} mask 是否显示遮罩(默认true)
}, * @property {Number String} z-index 弹出键盘的z-index值(默认1075)
computed: { * @property {Boolean} random 是否打乱键盘按键的顺序(默认false)
areaList() { * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
let data = [ * @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘(默认true)
'', * @event {Function} change 按键被点击(不包含退格键被点击)
'', * @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击
'', * @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击
'', * @event {Function} backspace 键盘退格键被点击
'', * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
'', */
'', export default {
'', name: "u-keyboard",
'', props: {
'', // 是否打乱键盘按键的顺序
'', random: {
'', type: Boolean,
'', default: false
'', }
'', },
'', data() {
'', return {
'', // 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
'', abc: false
'', };
'', },
'', computed: {
'', areaList() {
'', let data = [
'', '',
'', '',
'', '',
'', '',
'', '',
'', '',
'', '',
'', '',
'', '',
'', '',
'使', '',
'' '',
]; '',
let tmp = []; '',
// 打乱顺序 '',
if (this.random) data = this.$u.randomArray(data); '',
// 切割成二维数组 '',
tmp[0] = data.slice(0, 10); '',
tmp[1] = data.slice(10, 20); '',
tmp[2] = data.slice(20, 30); '',
tmp[3] = data.slice(30, 36); '',
return tmp; '',
}, '',
EngKeyBoardList() { '',
let data = [ '',
1, '',
2, '',
3, '',
4, '',
5, '',
6, '',
7, '',
8, '',
9, '',
0, '使',
'Q', ''
'W', ];
'E', let tmp = [];
'R', // 打乱顺序
'T', if (this.random) data = this.$u.randomArray(data);
'Y', // 切割成二维数组
'U', tmp[0] = data.slice(0, 10);
'I', tmp[1] = data.slice(10, 20);
'O', tmp[2] = data.slice(20, 30);
'P', tmp[3] = data.slice(30, 36);
'A', return tmp;
'S', },
'D', EngKeyBoardList() {
'F', let data = [
'G', 1,
'H', 2,
'J', 3,
'K', 4,
'L', 5,
'Z', 6,
'X', 7,
'C', 8,
'V', 9,
'B', 0,
'N', 'Q',
'M' 'W',
]; 'E',
let tmp = []; 'R',
if (this.random) data = this.$u.randomArray(data); 'T',
tmp[0] = data.slice(0, 10); 'Y',
tmp[1] = data.slice(10, 20); 'U',
tmp[2] = data.slice(20, 30); 'I',
tmp[3] = data.slice(30, 36); 'O',
return tmp; 'P',
} 'A',
}, 'S',
methods: { 'D',
// 点击键盘按钮 'F',
carInputClick(i, j) { 'G',
let value = ''; 'H',
// 不同模式,获取不同数组的值 'J',
if (this.abc) value = this.EngKeyBoardList[i][j]; 'K',
else value = this.areaList[i][j]; 'L',
this.$emit('change', value); 'Z',
}, 'X',
// 修改汽车牌键盘的输入模式,中文|英文 'C',
changeCarInputMode() { 'V',
this.abc = !this.abc; 'B',
}, 'N',
// 点击退格键 'M'
backspaceClick() { ];
this.$emit('backspace'); let tmp = [];
 clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 if (this.random) data = this.$u.randomArray(data);
      this.timer = setInterval(() => { tmp[0] = data.slice(0, 10);
    this.$emit('backspace'); tmp[1] = data.slice(10, 20);
      }, 250); tmp[2] = data.slice(20, 30);
}, tmp[3] = data.slice(30, 36);
clearTimer() { return tmp;
clearInterval(this.timer); }
}, },
} methods: {
}; // 点击键盘按钮
</script> carInputClick(i, j) {
let value = '';
<style lang="scss" scoped> // 不同模式,获取不同数组的值
.u-keyboard-grids { if (this.abc) value = this.EngKeyBoardList[i][j];
background: rgb(215, 215, 217); else value = this.areaList[i][j];
padding: 24rpx 0; this.$emit('change', value);
position: relative; },
} // 修改汽车牌键盘的输入模式,中文|英文
changeCarInputMode() {
.u-keyboard-grids-item { this.abc = !this.abc;
display: flex; },
align-items: center; // 点击退格键
justify-content: center; backspaceClick() {
} this.$emit('backspace');
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
.u-keyboard-grids-btn { this.timer = setInterval(() => {
text-decoration: none; this.$emit('backspace');
width: 62rpx; }, 250);
flex: 0 0 64rpx; },
height: 80rpx; clearTimer() {
display: inline-block; clearInterval(this.timer);
font-size: 36rpx; },
text-align: center; }
line-height: 80rpx; };
background-color: #fff; </script>
margin: 8rpx 5rpx;
border-radius: 8rpx; <style lang="scss" scoped>
box-shadow: 0 2rpx 0rpx #888992; .u-keyboard-grids {
font-weight: 500; background: rgb(215, 215, 217);
} padding: 24rpx 0;
position: relative;
.u-carinput-hover { }
background-color: rgb(185, 188, 195)!important;
} .u-keyboard-grids-item {
display: flex;
.u-keyboard-back { align-items: center;
position: absolute; justify-content: center;
width: 96rpx; }
right: 22rpx;
bottom: 32rpx; .u-keyboard-grids-btn {
height: 80rpx; text-decoration: none;
background-color: rgb(185, 188, 195); width: 62rpx;
display: flex; flex: 0 0 64rpx;
align-items: center; height: 80rpx;
border-radius: 8rpx; display: inline-block;
justify-content: center; font-size: 36rpx;
box-shadow: 0 2rpx 0rpx #888992; text-align: center;
} line-height: 80rpx;
background-color: #fff;
.u-keyboard-change { margin: 8rpx 5rpx;
font-size: 24rpx; border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx #888992; box-shadow: 0 2rpx 0rpx #888992;
position: absolute; font-weight: 500;
width: 96rpx; }
left: 22rpx;
line-height: 1; .u-carinput-hover {
bottom: 32rpx; background-color: rgb(185, 188, 195) !important;
height: 80rpx; }
background-color: #ffffff;
display: flex; .u-keyboard-back {
align-items: center; position: absolute;
border-radius: 8rpx; width: 96rpx;
justify-content: center; right: 22rpx;
} bottom: 32rpx;
height: 80rpx;
.u-keyboard-change .inactive.zh { background-color: rgb(185, 188, 195);
transform: scale(0.85) translateY(-10rpx); display: flex;
} align-items: center;
border-radius: 8rpx;
.u-keyboard-change .inactive.en { justify-content: center;
transform: scale(0.85) translateY(10rpx); box-shadow: 0 2rpx 0rpx #888992;
} }
.u-keyboard-change .active { .u-keyboard-change {
color: rgb(237, 112, 64); font-size: 24rpx;
font-size: 30rpx; box-shadow: 0 2rpx 0rpx #888992;
} position: absolute;
width: 96rpx;
.u-keyboard-change .zh { left: 22rpx;
transform: translateY(-10rpx); line-height: 1;
} bottom: 32rpx;
height: 80rpx;
.u-keyboard-change .en { background-color: #ffffff;
transform: translateY(10rpx); display: flex;
} align-items: center;
border-radius: 8rpx;
justify-content: center;
}
.u-keyboard-change .inactive.zh {
transform: scale(0.85) translateY(-10rpx);
}
.u-keyboard-change .inactive.en {
transform: scale(0.85) translateY(10rpx);
}
.u-keyboard-change .active {
color: rgb(237, 112, 64);
font-size: 30rpx;
}
.u-keyboard-change .zh {
transform: translateY(-10rpx);
}
.u-keyboard-change .en {
transform: translateY(10rpx);
}
</style> </style>
<template> <template>
<view class="u-cell-box"> <view class="u-cell-box">
<view class="u-cell-title" v-if="title" :style="[titleStyle]"> <view class="u-cell-title" v-if="title" :style="[titleStyle]">
{{title}} {{title}}
</view> </view>
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}"> <view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
<slot /> <slot />
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 分组标题 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item
title: { * @tutorial https://www.uviewui.com/components/cell.html
type: String, * @property {String} title 分组标题
default: '' * @property {Boolean} border 是否显示外边框(默认true)
}, * @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'}
// 是否显示分组list上下边框 * @example <u-cell-group title="设置喜好">
border: { */
type: Boolean, export default {
default: true name: "u-cell-group",
}, props: {
// 分组标题的样式,对象形式,注意驼峰属性写法 // 分组标题
// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'} title: {
titleStyle: { type: String,
type: Object, default: ''
default() { },
return {}; // 是否显示分组list上下边框
} border: {
} type: Boolean,
}, default: true
data() { },
return { // 分组标题的样式,对象形式,注意驼峰属性写法
index: 0, // 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'}
} titleStyle: {
}, type: Object,
provide() { default () {
return { return {};
uCellGroup: this }
} }
}, },
} data() {
</script> return {
index: 0,
<style lang="scss" scoped> }
.u-cell-box { },
width: 100%; provide() {
} return {
uCellGroup: this
.u-cell-title { }
padding: 30rpx 32rpx 10rpx 32rpx; },
font-size: 30rpx; }
text-align: left; </script>
color: $u-tips-color;
} <style lang="scss" scoped>
.u-cell-box {
.u-cell-item-box { width: 100%;
background-color: #FFFFFF; }
}
.u-cell-title {
padding: 30rpx 32rpx 10rpx 32rpx;
font-size: 30rpx;
text-align: left;
color: $u-tips-color;
}
.u-cell-item-box {
background-color: #FFFFFF;
}
</style> </style>
<template> <template>
<view @tap="click" class="u-cell-item-box" :class="{'u-cell-border': itemIndex > 0 }" hover-stay-time="150" :hover-class="hover ? 'u-hover-class' : ''" :style="{ <view @tap="click" class="u-cell-item-box" :class="{'u-cell-border': itemIndex > 0 }" hover-stay-time="150"
backgroundColor: bgColor :hover-class="hover ? 'u-hover-class' : ''" :style="{
}"> backgroundColor: bgColor
<view class="u-cell-content"> }">
<view class="u-icon-wrap" v-if="icon"> <view class="u-cell-content">
<u-icon size="32" :name="icon" class="u-icon"></u-icon> <view class="u-icon-wrap" v-if="icon">
</view> <u-icon size="32" :name="icon" class="u-icon"></u-icon>
<view class="u-icon-wrap"> </view>
<slot name="icon"></slot> <view class="u-icon-wrap">
</view> <slot name="icon"></slot>
<view class="u-cell-title" :style="[titleStyle]"> </view>
<text class="u-title-text" v-if="title">{{title}}</text> <view class="u-cell-title" :style="[titleStyle]">
<slot name="left"></slot> <text class="u-title-text" v-if="title">{{title}}</text>
</view> <slot name="left"></slot>
<view class="u-cell-value" v-if="value"> </view>
<text class="u-value-text" :style="[valueStyle]" v-if="value">{{value}}</text> <view class="u-cell-value" v-if="value">
</view> <text class="u-value-text" :style="[valueStyle]" v-if="value">{{value}}</text>
<view v-else class="u-slot-wrap"> </view>
<slot name="right"></slot> <view v-else class="u-slot-wrap">
</view> <slot name="right"></slot>
<view :style="[arrowStyle]" class="u-icon-wrap"> </view>
<u-icon v-if="arrow" size="26" class="u-arror-right" color="#969799" name="arrow-right"></u-icon> <view :style="[arrowStyle]" class="u-icon-wrap">
</view> <u-icon v-if="arrow" size="26" class="u-arror-right" color="#969799" name="arrow-right"></u-icon>
</view> </view>
<view class="u-cell-label" v-if="label" :style="[labelStyle]"> </view>
{{label}} <view class="u-cell-label" v-if="label" :style="[labelStyle]">
</view> {{label}}
</view> </view>
</template> </view>
</template>
<script>
export default { <script>
props: { /**
// 左侧图标名称(只能uView内置图标),或者图标src * alertTips 提示
icon: { * @description 警告提示,展现需要关注的信息。
type: String, * @tutorial https://www.uviewui.com/components/cell.html
default: '' * @property {String} title 左侧标题
}, * @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标
// 左侧标题 * @property {String} value 右侧内容
title: { * @property {String} label 标题下方的描述信息
type: String, * @property {Boolean} border 是否显示每个cell的下边框(默认true)
default: '' * @property {Boolean} hover 是否开启点击反馈,hover-class形式,如果右侧通过slot传递switch进去的话,可以将此值设置为false(默认true)
}, * @property {Boolean} arrow 是否显示右侧箭头(默认true)
// 右侧内容 * @property {Boolean} arrow-direction 箭头方向,可选值(默认right)
value: { * @property {Object} title-style 标题样式,对象形式
type: String, * @property {Object} value-style 右侧内容样式,对象形式
default: '' * @property {Object} label-style 标题下方描述信息的样式,对象形式
}, * @property {String} bg-color 背景颜色(默认#ffffff)
// 标题下方的描述信息 * @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item
label: { * @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
type: String, */
default: '' export default {
}, name: "u-cell-item",
// 是否显示内边框 props: {
border: { // 左侧图标名称(只能uView内置图标),或者图标src
type: Boolean, icon: {
default: true type: String,
}, default: ''
// 是否开启点击反馈,即点击是cell背景为灰色 },
hover: { // 左侧标题
type: Boolean, title: {
default: true type: String,
}, default: ''
// 是否显示右侧箭头 },
arrow: { // 右侧内容
type: Boolean, value: {
default: true type: String,
}, default: ''
// 右侧箭头方向,可选值:right|up|down,默认为right },
arrowDirection: { // 标题下方的描述信息
type: String, label: {
default: 'right' type: String,
}, default: ''
// 控制标题的样式 },
titleStyle: { // 是否显示内边框
type: Object, border: {
default() { type: Boolean,
return {}; default: true
} },
}, // 是否开启点击反馈,即点击是cell背景为灰色
// 右侧显示内容的样式 hover: {
valueStyle: { type: Boolean,
type: Object, default: true
default() { },
return {}; // 是否显示右侧箭头
} arrow: {
}, type: Boolean,
// 描述信息的样式 default: true
labelStyle: { },
type: Object, // 右侧箭头方向,可选值:right|up|down,默认为right
default() { arrowDirection: {
return {}; type: String,
} default: 'right'
}, },
// 背景颜色 // 控制标题的样式
bgColor: { titleStyle: {
type: String, type: Object,
default: '#ffffff' default () {
}, return {};
// 用于识别被点击的是第几个cell }
index: { },
type: [String, Number], // 右侧显示内容的样式
default: '' valueStyle: {
} type: Object,
}, default () {
inject: ['uCellGroup'], return {};
data() { }
return { },
itemIndex: 0, // 描述信息的样式
} labelStyle: {
}, type: Object,
created() { default () {
this.itemIndex = this.uCellGroup.index++; return {};
}, }
computed: { },
arrowStyle() { // 背景颜色
let style = {}; bgColor: {
if(this.arrowDirection == 'top') style.transform = 'rotate(-90deg)'; type: String,
else if(this.arrowDirection == 'bottom') style.transform = 'rotate(90deg)'; default: '#ffffff'
else style.transform = 'rotate(0deg)'; },
return style; // 用于识别被点击的是第几个cell
} index: {
}, type: [String, Number],
methods: { default: ''
click() { }
this.$emit('click', this.index); },
} inject: ['uCellGroup'],
} data() {
} return {
</script> itemIndex: 0,
}
<style lang="scss" scoped> },
.u-cell-item-box { created() {
padding: 28rpx 32rpx; this.itemIndex = this.uCellGroup.index++;
position: relative; },
} computed: {
arrowStyle() {
.u-cell-border:after { let style = {};
left: 32rpx!important; if (this.arrowDirection == 'top') style.transform = 'rotate(-90deg)';
position: absolute; else if (this.arrowDirection == 'bottom') style.transform = 'rotate(90deg)';
box-sizing: border-box; else style.transform = 'rotate(0deg)';
content: ' '; return style;
pointer-events: none; }
right: 0; },
top: 0; methods: {
border-bottom: 1px solid $u-border-color; click() {
-webkit-transform: scaleY(0.5); this.$emit('click', this.index);
transform: scaleY(0.5); }
} }
}
.u-cell-content { </script>
display: flex;
align-items: center; <style lang="scss" scoped>
} .u-cell-item-box {
padding: 28rpx 32rpx;
.u-cell-title { position: relative;
color: #323233; }
font-size: 30rpx;
flex: 1; .u-cell-border:after {
margin-left: 6rpx; left: 32rpx !important;
text-align: left; position: absolute;
} box-sizing: border-box;
content: ' ';
.u-cell-value { pointer-events: none;
flex: 1; right: 0;
font-size: 26rpx; top: 0;
color: #969799; border-bottom: 1px solid $u-border-color;
text-align: right; -webkit-transform: scaleY(0.5);
} transform: scaleY(0.5);
}
.u-cell-label {
color: #969799; .u-cell-content {
font-size: 26rpx; display: flex;
margin-top: 10rpx; align-items: center;
text-align: left; }
}
.u-cell-title {
.u-slot-wrap { color: #323233;
display: flex; font-size: 30rpx;
align-items: center; flex: 1;
} margin-left: 6rpx;
text-align: left;
// 微信小程序需要额外处理可能通过slot传入badge和switch的问题 }
// 否则无法垂直居中
/* #ifdef MP-WEIXIN */ .u-cell-value {
.u-slot-wrap /deep/ u-badge, flex: 1;
.u-slot-wrap /deep/ u-switch { font-size: 26rpx;
display: flex; color: #969799;
align-items: center; text-align: right;
} }
/* #endif */
.u-cell-label {
.u-icon { color: #969799;
margin-right: 6rpx; font-size: 26rpx;
} margin-top: 10rpx;
text-align: left;
.u-value-text { }
margin-right: 10rpx;
} .u-slot-wrap {
display: flex;
.u-title-text { align-items: center;
margin-right: 10rpx; }
}
</style> // 微信小程序需要额外处理可能通过slot传入badge和switch的问题
\ No newline at end of file // 否则无法垂直居中
/* #ifdef MP-WEIXIN */
.u-slot-wrap /deep/ u-badge,
.u-slot-wrap /deep/ u-switch {
display: flex;
align-items: center;
}
/* #endif */
.u-icon {
margin-right: 6rpx;
}
.u-value-text {
margin-right: 10rpx;
}
.u-title-text {
margin-right: 10rpx;
}
</style>
...@@ -5,6 +5,16 @@ ...@@ -5,6 +5,16 @@
</template> </template>
<script> <script>
/**
* alertTips 提示
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
* @tutorial https://www.uviewui.com/components/checkbox.html
* @property {String Number} max 最多能选中多少个checkbox(默认999)
* @property {Boolean} disabled 是否禁用所有checkbox(默认false)
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff)
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
* @example <u-checkbox-group></u-checkbox-group>
*/
export default { export default {
props: { props: {
// 最多能选中多少个checkbox // 最多能选中多少个checkbox
......
<template> <template>
<view class="u-checkbox"> <view class="u-checkbox">
<view class="u-checkbox__icon-wrap" @tap="toggle"> <view class="u-checkbox__icon-wrap" @tap="toggle">
<u-icon :class="iconClass" name="checkbox-mark" :size="iconSize" :color="iconColor" class="u-checkbox__icon" :style="[iconStyle]" /> <u-icon :class="iconClass" name="checkbox-mark" :size="iconSize" :color="iconColor" class="u-checkbox__icon" :style="[iconStyle]" />
</view> </view>
<view class="u-label-class u-checkbox__label" @tap="onClickLabel"><slot /></view> <view class="u-label-class u-checkbox__label" @tap="onClickLabel">
</view> <slot />
</template> </view>
</view>
<script> </template>
export default {
props: { <script>
// checkbox的名称 /**
name: { * alertTips 提示
type: [String, Number], * @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。
default: '' * @tutorial https://www.uviewui.com/components/checkbox.html
}, * @property {String Number} icon-size 图标大小,单位rpx(默认24)
// 形状,square为方形,circle为原型 * @property {String Number} size 组件整体的大小,单位rpx(默认40)
shape: { * @property {String Number} name checkbox组件的标示符
type: String, * @property {String} shape 形状,见官网说明(默认circle)
default: 'square' * @property {Boolean} disabled 是否禁用(默认false)
}, * @property {Boolean} label-disabled 点击文本是否可以操作checkbox(默认true)
// 是否为选中状态 * @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效
value: { * @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象
type: Boolean, * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
default: false */
}, export default {
// 是否禁用 name: "u-checkbox",
disabled: { props: {
type: Boolean, // checkbox的名称
default: false name: {
}, type: [String, Number],
// 是否禁止点击提示语选中复选框 default: ''
labelDisabled: { },
type: Boolean, // 形状,square为方形,circle为原型
default: false shape: {
}, type: String,
// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值 default: 'square'
activeColor: { },
type: String, // 是否为选中状态
default: '' value: {
}, type: Boolean,
// 图标的大小,单位rpx default: false
iconSize: { },
type: [String, Number], // 是否禁用
default: 24 disabled: {
}, type: Boolean,
}, default: false
inject: ['checkboxGroup'], },
data() { // 是否禁止点击提示语选中复选框
return { labelDisabled: {
parentDisabled: false, type: Boolean,
}; default: false
}, },
created() { // 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值
this.parentDisabled = this.checkboxGroup.disabled; activeColor: {
this.checkboxGroup.children.push(this); type: String,
}, default: ''
computed: { },
iconStyle() { // 图标的大小,单位rpx
let style = {}; iconSize: {
if (this.checkboxActiveColor && this.value && !this.disabled && !this.parentDisabled) { type: [String, Number],
style.borderColor = this.checkboxActiveColor; default: 24
style.backgroundColor = this.checkboxActiveColor; },
} },
style.width = this.checkboxGroup.size + 'rpx'; inject: ['checkboxGroup'],
style.height = this.checkboxGroup.size + 'rpx'; data() {
return style; return {
}, parentDisabled: false,
iconColor() { };
return this.value ? '#ffffff' : 'transparent'; },
}, created() {
iconClass() { this.parentDisabled = this.checkboxGroup.disabled;
let classs = []; this.checkboxGroup.children.push(this);
classs.push('u-checkbox__icon--' + this.shape); },
if(this.value == true) classs.push('u-checkbox__icon--checked'); computed: {
if(this.disabled || this.parentDisabled) classs.push('u-checkbox__icon--disabled'); iconStyle() {
if(this.value && (this.disabled || this.parentDisabled)) classs.push('u-checkbox__icon--disabled--checked'); let style = {};
return classs; if (this.checkboxActiveColor && this.value && !this.disabled && !this.parentDisabled) {
}, style.borderColor = this.checkboxActiveColor;
// 激活的颜色,可能受checkboxGroup和本组件的activeColor影响 style.backgroundColor = this.checkboxActiveColor;
// 本组件的activeColor值优先 }
checkboxActiveColor() { style.width = this.checkboxGroup.size + 'rpx';
return this.activeColor ? this.activeColor : this.checkboxGroup.activeColor; style.height = this.checkboxGroup.size + 'rpx';
} return style;
}, },
methods: { iconColor() {
onClickLabel() { return this.value ? '#ffffff' : 'transparent';
if (!this.disabled && !this.labelDisabled && !this.parentDisabled) { },
this.setValue(); iconClass() {
} let classs = [];
}, classs.push('u-checkbox__icon--' + this.shape);
toggle() { if (this.value == true) classs.push('u-checkbox__icon--checked');
if (!this.disabled && !this.parentDisabled) { if (this.disabled || this.parentDisabled) classs.push('u-checkbox__icon--disabled');
this.setValue(); if (this.value && (this.disabled || this.parentDisabled)) classs.push('u-checkbox__icon--disabled--checked');
} return classs;
}, },
emitEvent() { // 激活的颜色,可能受checkboxGroup和本组件的activeColor影响
this.$emit('change', {value: this.value, name: this.name}) // 本组件的activeColor值优先
this.checkboxGroup.emitEvent(); checkboxActiveColor() {
}, return this.activeColor ? this.activeColor : this.checkboxGroup.activeColor;
// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值 }
setValue() { },
// 判断是否超过了可选的最大数量 methods: {
let checkedNum = 0; onClickLabel() {
this.checkboxGroup.children.map(val => { if (!this.disabled && !this.labelDisabled && !this.parentDisabled) {
if(val.value) checkedNum ++; this.setValue();
}) }
// 如果原来为选中状态,那么可以取消 },
if(this.value == true) { toggle() {
this.$emit('input', !this.value); if (!this.disabled && !this.parentDisabled) {
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 this.setValue();
this.$nextTick(function(){ }
this.emitEvent(); },
}) emitEvent() {
} else if(checkedNum < this.checkboxGroup.max && this.value == false) { this.$emit('change', {
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中 value: this.value,
this.$emit('input', !this.value); name: this.name
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 })
this.$nextTick(function(){ this.checkboxGroup.emitEvent();
this.emitEvent(); },
}) // 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值
} setValue() {
// 判断是否超过了可选的最大数量
} let checkedNum = 0;
} this.checkboxGroup.children.map(val => {
}; if (val.value) checkedNum++;
</script> })
// 如果原来为选中状态,那么可以取消
<style lang="scss" scoped> if (this.value == true) {
.u-checkbox { this.$emit('input', !this.value);
display: -webkit-flex; // 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
display: flex; this.$nextTick(function() {
-webkit-align-items: center; this.emitEvent();
align-items: center; })
overflow: hidden; } else if (checkedNum < this.checkboxGroup.max && this.value == false) {
-webkit-user-select: none; // 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
user-select: none; this.$emit('input', !this.value);
} // 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
this.$nextTick(function() {
.u-checkbox__icon-wrap, this.emitEvent();
.u-checkbox__label { })
color: $u-content-color; }
}
}
.u-checkbox__icon-wrap { }
-webkit-flex: none; };
flex: none; </script>
}
<style lang="scss" scoped>
.u-checkbox__icon { .u-checkbox {
display: -webkit-flex; display: -webkit-flex;
display: flex; display: flex;
-webkit-align-items: center; -webkit-align-items: center;
align-items: center; align-items: center;
-webkit-justify-content: center; overflow: hidden;
justify-content: center; -webkit-user-select: none;
box-sizing: border-box; user-select: none;
width: 42rpx; }
height: 42rpx;
color: transparent; .u-checkbox__icon-wrap,
text-align: center; .u-checkbox__label {
transition-property: color, border-color, background-color; color: $u-content-color;
font-size: 20px; }
border: 1px solid #c8c9cc;
transition-duration: 0.2s; .u-checkbox__icon-wrap {
} -webkit-flex: none;
flex: none;
.u-checkbox__icon--circle { }
border-radius: 100%;
} .u-checkbox__icon {
display: -webkit-flex;
.u-checkbox__icon--square { display: flex;
border-radius: 3px; -webkit-align-items: center;
} align-items: center;
-webkit-justify-content: center;
.u-checkbox__icon--checked { justify-content: center;
color: #fff; box-sizing: border-box;
background-color: #2979ff; width: 42rpx;
border-color: #2979ff; height: 42rpx;
} color: transparent;
text-align: center;
.u-checkbox__icon--disabled { transition-property: color, border-color, background-color;
background-color: #ebedf0; font-size: 20px;
border-color: #c8c9cc; border: 1px solid #c8c9cc;
} transition-duration: 0.2s;
}
.u-checkbox__icon--disabled--checked {
color: #c8c9cc!important; .u-checkbox__icon--circle {
} border-radius: 100%;
}
.u-checkbox__label {
word-wrap: break-word; .u-checkbox__icon--square {
margin-left: 10rpx; border-radius: 3px;
margin-right: 18rpx; }
color: $u-content-color;
font-size: 30rpx; .u-checkbox__icon--checked {
} color: #fff;
background-color: #2979ff;
.u-checkbox__label--disabled { border-color: #2979ff;
color: #c8c9cc; }
}
.u-checkbox__icon--disabled {
.u-checkbox__label:empty { background-color: #ebedf0;
margin: 0; border-color: #c8c9cc;
} }
.u-checkbox__icon--disabled--checked {
color: #c8c9cc !important;
}
.u-checkbox__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 18rpx;
color: $u-content-color;
font-size: 30rpx;
}
.u-checkbox__label--disabled {
color: #c8c9cc;
}
.u-checkbox__label:empty {
margin: 0;
}
</style> </style>
<template> <template>
<view class="u-circle-progress" :style="{ <view class="u-circle-progress" :style="{
width: widthPx + 'px', width: widthPx + 'px',
height: widthPx + 'px', height: widthPx + 'px',
backgroundColor: bgColor backgroundColor: bgColor
}"> }">
<canvas class="u-canvas-bg" :canvas-id="elBgId" :style="{ <canvas class="u-canvas-bg" :canvas-id="elBgId" :style="{
width: widthPx + 'px', width: widthPx + 'px',
height: widthPx + 'px' height: widthPx + 'px'
}"></canvas> }"></canvas>
<canvas class="u-canvas" :canvas-id="elId" :style="{ <canvas class="u-canvas" :canvas-id="elId" :style="{
width: widthPx + 'px', width: widthPx + 'px',
height: widthPx + 'px' height: widthPx + 'px'
}"></canvas> }"></canvas>
<slot></slot> <slot></slot>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 圆环进度百分比值 * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。
percent: { * @tutorial https://www.uviewui.com/components/circleProgress.html
type: Number, * @property {String Number} percent 圆环进度百分比值,为数值类型,0-100
default: 0, * @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec)
// 限制值在0到100之间 * @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b)
validator: val => { * @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200)
return val >= 0 && val <= 100; * @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14)
} * @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500)
}, * @property {String} type 如设置,active-color值将会失效
// 底部圆环的颜色(灰色的圆环) * @property {String} bg-color 整个组件背景颜色,默认为白色
inactiveColor: { * @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
type: String, */
default: "#ececec" export default {
}, name: "u-circle-progress",
// 圆环激活部分的颜色 props: {
activeColor: { // 圆环进度百分比值
type: String, percent: {
default: '#19be6b' type: Number,
}, default: 0,
// 圆环线条的宽度,单位rpx // 限制值在0到100之间
borderWidth: { validator: val => {
type: [Number, String], return val >= 0 && val <= 100;
default: 14 }
}, },
// 整个圆形的宽度,单位rpx // 底部圆环的颜色(灰色的圆环)
width: { inactiveColor: {
type: [Number, String], type: String,
default: 200 default: "#ececec"
}, },
// 整个圆环执行一圈的时间,单位ms // 圆环激活部分的颜色
duration: { activeColor: {
type: [Number, String], type: String,
default: 1500 default: '#19be6b'
}, },
// 主题类型 // 圆环线条的宽度,单位rpx
type: { borderWidth: {
type: String, type: [Number, String],
default: '' default: 14
}, },
// 整个圆环进度区域的背景色 // 整个圆形的宽度,单位rpx
bgColor: { width: {
type: String, type: [Number, String],
default: "#ffffff" default: 200
} },
}, // 整个圆环执行一圈的时间,单位ms
data() { duration: {
return { type: [Number, String],
elBgId: this.$u.guid(), default: 1500
elId: this.$u.guid(), },
ctxBg: '', // 背景canvas实例 // 主题类型
ctx: '', // 前景(激活时候)canvas的实例 type: {
count: 0, // 计数器 type: String,
timer: null, // 定时器 default: ''
times: 0, // 总共要执行的动画次数,setInterval的次数 },
time: 0, // 执行整个动画的时间 // 整个圆环进度区域的背景色
widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度 bgColor: {
borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度 type: String,
mode: 'more', // more-percent增加,less-percent减少 default: "#ffffff"
} }
}, },
watch: { data() {
percent: { return {
immediate: true, elBgId: this.$u.guid(),
handler(nVal, oVal = 0) { elId: this.$u.guid(),
this.mode = nVal > oVal ? 'more' : 'less'; ctxBg: '', // 背景canvas实例
this.times = Math.ceil(nVal * 3.6); ctx: '', // 前景(激活时候)canvas的实例
this.time = Math.ceil(this.duration / 360 * this.times); count: 0, // 计数器
setTimeout(() => { timer: null, // 定时器
this.countInterval(); times: 0, // 总共要执行的动画次数,setInterval的次数
}, 50) time: 0, // 执行整个动画的时间
} widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度
} borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度
}, mode: 'more', // more-percent增加,less-percent减少
computed: { }
// 有type主题时,优先起作用 },
circleColor() { watch: {
if(['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type]; percent: {
else return this.activeColor; immediate: true,
} handler(nVal, oVal = 0) {
}, this.mode = nVal > oVal ? 'more' : 'less';
mounted() { this.times = Math.ceil(nVal * 3.6);
this.ctxBg = uni.createCanvasContext(this.elBgId, this); this.time = Math.ceil(this.duration / 360 * this.times);
this.ctx = uni.createCanvasContext(this.elId, this); setTimeout(() => {
// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7) this.countInterval();
setTimeout(() => { }, 50)
this.drawProgressBg(); }
}, 50) }
}, },
methods: { computed: {
drawProgressBg() { // 有type主题时,优先起作用
this.ctxBg.setLineWidth(this.borderWidthPx); // 设置圆环宽度 circleColor() {
this.ctxBg.setStrokeStyle(this.inactiveColor); // 线条颜色 if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
this.ctxBg.setLineCap('round'); // 圆环端点的形状为圆形 else return this.activeColor;
this.ctxBg.beginPath(); // 开始描绘路径 }
// 设置一个原点(110,110),半径为100的圆的路径到当前路径 },
this.ctxBg.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, 0, 2 * Math.PI, false); mounted() {
this.ctxBg.stroke(); // 对路径进行描绘 this.ctxBg = uni.createCanvasContext(this.elBgId, this);
this.ctxBg.draw(); this.ctx = uni.createCanvasContext(this.elId, this);
}, // 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7)
drawCircle(step) { setTimeout(() => {
this.ctx.setLineWidth(this.borderWidthPx); this.drawProgressBg();
this.ctx.setStrokeStyle(this.circleColor); }, 50)
this.ctx.setLineCap('round'); },
this.ctx.beginPath(); methods: {
// 参数step 为绘制的圆环周长,从0到2为一周 。 -Math.PI / 2 将起始角设在12点钟位置 ,结束角 通过改变 step 的值确定 drawProgressBg() {
if(this.mode == 'more') { this.ctxBg.setLineWidth(this.borderWidthPx); // 设置圆环宽度
this.ctx.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, -Math.PI / 2, step * this.ctxBg.setStrokeStyle(this.inactiveColor); // 线条颜色
Math.PI - Math.PI / 2, false); this.ctxBg.setLineCap('round'); // 圆环端点的形状为圆形
} else { this.ctxBg.beginPath(); // 开始描绘路径
this.ctx.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, -Math.PI / 2, Math.PI / 2 - step * // 设置一个原点(110,110),半径为100的圆的路径到当前路径
Math.PI, false); this.ctxBg.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, 0, 2 * Math.PI,
} false);
this.ctx.stroke(); this.ctxBg.stroke(); // 对路径进行描绘
this.ctx.draw() this.ctxBg.draw();
}, },
countInterval() { drawCircle(step) {
this.countTimer = setInterval(() => { this.ctx.setLineWidth(this.borderWidthPx);
if (this.count <= this.times) { this.ctx.setStrokeStyle(this.circleColor);
// 全一个圆时候,值为2,这里求出每一份的值为2/360 this.ctx.setLineCap('round');
this.drawCircle(this.count * 2 / 360); this.ctx.beginPath();
this.count++; // 参数step 为绘制的圆环周长,从0到2为一周 。 -Math.PI / 2 将起始角设在12点钟位置 ,结束角 通过改变 step 的值确定
} else { if (this.mode == 'more') {
clearInterval(this.countTimer); this.ctx.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, -Math.PI / 2, step *
} Math.PI - Math.PI / 2, false);
}, Math.ceil(this.duration / 360)); // 总过渡时间分为360份,这里为每一份的时间 } else {
}, this.ctx.arc(this.widthPx / 2, this.widthPx / 2, this.widthPx / 2 - this.borderWidthPx / 2 - 1, -Math.PI / 2, Math
} .PI / 2 - step *
} Math.PI, false);
</script> }
this.ctx.stroke();
<style lang="scss" scoped> this.ctx.draw()
.u-circle-progress { },
position: relative; countInterval() {
display: inline-flex; this.countTimer = setInterval(() => {
align-items: center; if (this.count <= this.times) {
justify-content: center; // 全一个圆时候,值为2,这里求出每一份的值为2/360
} this.drawCircle(this.count * 2 / 360);
this.count++;
.u-canvas-bg { } else {
position: absolute; clearInterval(this.countTimer);
} }
}, Math.ceil(this.duration / 360)); // 总过渡时间分为360份,这里为每一份的时间
.u-canvas { },
position: absolute; }
} }
</script>
<style lang="scss" scoped>
.u-circle-progress {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
}
.u-canvas-bg {
position: absolute;
}
.u-canvas {
position: absolute;
}
</style> </style>
<template> <template>
<view class="u-collapse-item"> <view class="u-collapse-item">
<view <view class="u-collapse-head" @tap.stop="headClick" hover-class="u-hover-class" :style="[headStyle]">
class="u-collapse-head" <view class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
@tap.stop="headClick" isShow && activeStyle && !arrow ? activeStyle : '']">
hover-class="u-hover-class" {{ title }}
:style="[headStyle]" </view>
> <view class="u-icon-wrap">
<view class="u-collapse-title u-line-1" <u-icon v-if="arrow" :color="arrowColor ? arrowColor : $u.color.tipsColor" :class="{ 'u-arrow-down-icon-active': isShow }"
:style="[{ textAlign: align ? align : 'left' }, class="u-arrow-down-icon" name="arrow-down"></u-icon>
isShow && activeStyle && !arrow ? activeStyle : '']" </view>
> </view>
{{ title }} <view class="u-collapse-body" :style="[{
</view> height: isShow ? height + 'px' : '0'
<view class="u-icon-wrap"> }, bodyStyle]">
<u-icon <view class="u-collapse-content" :id="elId">
v-if="arrow" <slot></slot>
:color="arrowColor ? arrowColor : $u.color.tipsColor" </view>
:class="{ 'u-arrow-down-icon-active': isShow }" </view>
class="u-arrow-down-icon" </view>
name="arrow-down" </template>
></u-icon>
</view> <script>
</view> /**
<view * alertTips 提示
class="u-collapse-body" * @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
:style="[{ * @tutorial https://www.uviewui.com/components/collapse.html
height: isShow ? height + 'px' : '0' * @property {String} title 面板标题
}, bodyStyle]" * @property {String Number} index 主要用于事件的回调,标识那个Item被点击
> * @property {Boolean} disabled 面板是否可以打开或收起(默认false)
<view class="u-collapse-content" :id="elId"><slot></slot></view> * @property {Boolean} open 设置某个面板的初始状态是否打开(默认false)
</view> * @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值
</view> * @property {String} align 标题的对齐方式(默认left)
</template> * @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式
* @event {Function} change 某个item被打开或者收起时触发
<script> * @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
export default { */
props: { export default {
// 标题 name: "u-collapse-item",
title: { props: {
type: String, // 标题
default: '' title: {
}, type: String,
// 标题的对齐方式 default: ''
align: { },
type: String, // 标题的对齐方式
default: 'left' align: {
}, type: String,
// 是否可以点击收起 default: 'left'
disabled: { },
type: Boolean, // 是否可以点击收起
default: false disabled: {
}, type: Boolean,
// collapse显示与否 default: false
open: { },
type: Boolean, // collapse显示与否
default: false open: {
}, type: Boolean,
// 唯一标识符 default: false
name: { },
type: [Number, String], // 唯一标识符
default: '' name: {
}, type: [Number, String],
//活动样式 default: ''
activeStyle: { },
type: Object, //活动样式
default() { activeStyle: {
return {} type: Object,
} default () {
}, return {}
// 标识当前为第几个 }
index: { },
type: [String, Number], // 标识当前为第几个
default: '' index: {
} type: [String, Number],
}, default: ''
inject: ['uCollapse'], }
data() { },
return { inject: ['uCollapse'],
isShow: false, data() {
elId: this.$u.guid(), return {
height: 0, // body内容的高度 isShow: false,
headStyle: {}, // 头部样式,对象形式 elId: this.$u.guid(),
bodyStyle: {}, // 主体部分样式 height: 0, // body内容的高度
arrowColor: '' headStyle: {}, // 头部样式,对象形式
}; bodyStyle: {}, // 主体部分样式
}, arrowColor: ''
mounted() { };
this.$nextTick(() => { },
this.queryRect(); mounted() {
}); this.$nextTick(() => {
}, this.queryRect();
watch: { });
open(val) { },
this.isShow = val; watch: {
} open(val) {
}, this.isShow = val;
computed: { }
arrow() { },
return this.uCollapse.arrow; computed: {
} arrow() {
}, return this.uCollapse.arrow;
created() { }
this.isShow = this.open; },
this.nameSync = this.name ? this.name : this.uCollapse.childrens.length; created() {
this.uCollapse.childrens.push(this); this.isShow = this.open;
this.headStyle = this.uCollapse.headStyle; this.nameSync = this.name ? this.name : this.uCollapse.childrens.length;
this.bodyStyle = this.uCollapse.bodyStyle; this.uCollapse.childrens.push(this);
this.arrowColor = this.uCollapse.arrowColor; this.headStyle = this.uCollapse.headStyle;
}, this.bodyStyle = this.uCollapse.bodyStyle;
methods: { this.arrowColor = this.uCollapse.arrowColor;
// 点击collapsehead头部 },
headClick() { methods: {
if (this.disabled) return; // 点击collapsehead头部
if (this.uCollapse.accordion == true) { headClick() {
this.uCollapse.childrens.map(val => { if (this.disabled) return;
// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了 if (this.uCollapse.accordion == true) {
if (this != val) { this.uCollapse.childrens.map(val => {
val.isShow = false; // 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
} if (this != val) {
}); val.isShow = false;
} }
});
this.isShow = !this.isShow; }
// 触发本组件的事件
this.$emit('change', { this.isShow = !this.isShow;
index: this.index, // 触发本组件的事件
show: this.isShow this.$emit('change', {
}) index: this.index,
show: this.isShow
// 只有在打开时才发出事件 })
if (this.isShow) this.uCollapse.onChange();
this.$forceUpdate(); // 只有在打开时才发出事件
}, if (this.isShow) this.uCollapse.onChange();
// 查询内容高度 this.$forceUpdate();
queryRect() { },
const query = uni.createSelectorQuery().in(this); // 查询内容高度
query queryRect() {
.select('#' + this.elId) const query = uni.createSelectorQuery().in(this);
.boundingClientRect(data => { query
if (!data.height) { .select('#' + this.elId)
setTimeout(() => { .boundingClientRect(data => {
this.queryRect(); if (!data.height) {
}, 10); setTimeout(() => {
return; this.queryRect();
} }, 10);
this.height = data.height; return;
}) }
.exec(); this.height = data.height;
} })
} .exec();
}; }
</script> }
};
<style lang="scss" scoped> </script>
.u-collapse-head {
position: relative; <style lang="scss" scoped>
display: flex; .u-collapse-head {
justify-content: space-between; position: relative;
align-items: center; display: flex;
color: $u-main-color; justify-content: space-between;
} align-items: center;
color: $u-main-color;
.u-collapse-title { }
flex: 1;
overflow: hidden; .u-collapse-title {
margin-right: 14rpx; flex: 1;
font-size: 30rpx; overflow: hidden;
color: $u-main-color; margin-right: 14rpx;
line-height: 1; font-size: 30rpx;
padding: 24rpx 0; color: $u-main-color;
text-align: left; line-height: 1;
} padding: 24rpx 0;
text-align: left;
.u-arrow-down-icon { }
transition: all 0.3s;
margin-right: 24rpx; .u-arrow-down-icon {
} transition: all 0.3s;
margin-right: 24rpx;
.u-arrow-down-icon-active { }
transform: rotate(180deg);
transform-origin: center center; .u-arrow-down-icon-active {
} transform: rotate(180deg);
transform-origin: center center;
.u-collapse-body { }
overflow: hidden;
transition: all 0.3s; .u-collapse-body {
} overflow: hidden;
transition: all 0.3s;
.u-collapse-content { }
overflow: hidden;
font-size: 28rpx; .u-collapse-content {
color: $u-tips-color; overflow: hidden;
text-align: left; font-size: 28rpx;
} color: $u-tips-color;
text-align: left;
}
</style> </style>
<template> <template>
<view class="u-collapse"> <view class="u-collapse">
<slot /> <slot />
</view> </view>
</template> </template>
<script> <script>
/**
* alertTips 提示
* @description 通过折叠面板收纳内容区域
* @tutorial https://www.uviewui.com/components/collapse.html
* @property {Boolean} accordion 是否手风琴模式(默认true)
* @property {Boolean} arrow 是否显示标题右侧的箭头(默认true)
* @property {String} arrow-color 标题右侧箭头的颜色(默认#909399)
* @property {String} head-bg-color 标题的背景颜色(默认#ffffff)
* @property {String} body-bg-color 主体内容的背景颜色(默认#ffffff)
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
* @example <u-collapse></u-collapse>
*/
export default { export default {
props: { name:"u-collapse",
// 是否手风琴模式 props: {
accordion: { // 是否手风琴模式
type: Boolean, accordion: {
default: true type: Boolean,
}, default: true
// 头部的样式 },
headStyle: { // 头部的样式
type: Object, headStyle: {
default() { type: Object,
return {} default () {
} return {}
}, }
// 主体的样式 },
bodyStyle: { // 主体的样式
type: Object, bodyStyle: {
default() { type: Object,
return {} default () {
} return {}
}, }
// 是否显示右侧的箭头 },
arrow: { // 是否显示右侧的箭头
type: Boolean, arrow: {
default: true type: Boolean,
}, default: true
// 箭头的颜色 },
arrowColor: { // 箭头的颜色
type: String, arrowColor: {
default: '' type: String,
}, default: ''
}, },
provide() { },
return { provide() {
uCollapse: this return {
} uCollapse: this
}, }
created() { },
this.childrens = [] created() {
}, this.childrens = []
data() { },
return { data() {
return {
}
}, }
methods: { },
// collapse item被点击,由collapse item调用父组件方法 methods: {
onChange() { // collapse item被点击,由collapse item调用父组件方法
let activeItem = []; onChange() {
this.childrens.forEach((vm, index) => { let activeItem = [];
if (vm.isShow) { this.childrens.forEach((vm, index) => {
activeItem.push(vm.nameSync); if (vm.isShow) {
} activeItem.push(vm.nameSync);
}) }
// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串 })
if(this.accordion) activeItem = activeItem.join(''); // 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
this.$emit('change', activeItem); if (this.accordion) activeItem = activeItem.join('');
} this.$emit('change', activeItem);
} }
} }
</script> }
</script>
<style lang="scss" scoped>
<style lang="scss" scoped>
</style> </style>
<template> <template>
<view class="u-empty" v-if="show"> <view class="u-empty" v-if="show">
<image class="u-image" :src="src ? src : icons[mode].image" mode="widthFix" :style="{ <image class="u-image" :src="src ? src : icons[mode].image" mode="widthFix" :style="{
width: imgWidth + 'rpx', width: imgWidth + 'rpx',
height: imgHeight == 'auto' ? 'auto' : imgHeight + 'rpx' height: imgHeight == 'auto' ? 'auto' : imgHeight + 'rpx'
}"></image> }"></image>
<text :style="{ <text :style="{
color: color, color: color,
fontSize: fontSize + 'rpx', fontSize: fontSize + 'rpx',
}"> }">
{{text ? text : icons[mode].text}} {{text ? text : icons[mode].text}}
</text> </text>
<view class="u-slot-wrap"> <view class="u-slot-wrap">
<slot name="bottom"></slot> <slot name="bottom"></slot>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import icon from "./icon.js"; /**
export default { * alertTips 提示
props: { * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
// 图标路径 * @tutorial https://www.uviewui.com/components/empty.html
src: { * @property {String} color 文字颜色(默认#c0c4cc)
type: String, * @property {String} text 文字提示(默认“无内容”)
default: '' * @property {String} src 自定义图标路径,如定义,mode参数会失效
}, * @property {String Number} font-size 提示文字的大小,单位rpx(默认28)
// 提示文字 * @property {String} mode 内置的图标,见官网说明(默认data)
text: { * @property {String Number} img-width 图标的宽度,单位rpx(默认240)
type: String, * @property {String} img-height 图标的高度,单位rpx(默认auto)
default: '' * @property {Boolean} show 是否显示组件(默认true)
}, * @event {Function} click 点击组件时触发
// 文字颜色 * @event {Function} close 点击关闭按钮时触发
color: { * @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
type: String, */
default: '#c0c4cc' import icon from "./icon.js";
}, export default {
// 文字大小,单位rpx name: "u-empty",
fontSize: { props: {
type: [String, Number], // 图标路径
default: 26 src: {
}, type: String,
// 选择预置的图标类型 default: ''
mode: { },
type: String, // 提示文字
default: 'data' text: {
}, type: String,
// 图标宽度,单位rpx default: ''
imgWidth: { },
type: [String, Number], // 文字颜色
default: 240 color: {
}, type: String,
// 图标高度,单位rpx default: '#c0c4cc'
imgHeight: { },
type: [String, Number], // 文字大小,单位rpx
default: 'auto' fontSize: {
}, type: [String, Number],
// 是否显示组件 default: 26
show: { },
type: Boolean, // 选择预置的图标类型
default: true mode: {
} type: String,
}, default: 'data'
data() { },
return { // 图标宽度,单位rpx
icons: icon imgWidth: {
} type: [String, Number],
} default: 240
} },
</script> // 图标高度,单位rpx
imgHeight: {
<style lang="scss" scoped> type: [String, Number],
.u-empty { default: 'auto'
display: flex; },
flex-direction: column; // 是否显示组件
justify-content: center; show: {
align-items: center; type: Boolean,
padding-top: 30%; default: true
} }
},
.u-image { data() {
margin-bottom: 20rpx; return {
} icons: icon
}
.u-slot-wrap { }
display: flex; }
justify-content: center; </script>
align-items: center;
margin-top: 20rpx; <style lang="scss" scoped>
} .u-empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 30%;
}
.u-image {
margin-bottom: 20rpx;
}
.u-slot-wrap {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20rpx;
}
</style> </style>
<template> <template>
<view class="u-field" :class="{'u-field-border': itemIndex > 0 }"> <view class="u-field" :class="{'u-field-border': itemIndex > 0 }">
<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPostion]"> <view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPostion]">
<view class="u-label" :class="[required ? 'u-required' : '']" <view class="u-label" :class="[required ? 'u-required' : '']" :style="{
:style="{ justifyContent: justifyContent,
justifyContent: justifyContent, flex: labelPostion == 'left' ? `0 0 ${labelWidth}rpx` : '1'
flex: labelPostion == 'left' ? `0 0 ${labelWidth}rpx` : '1' }">
}"> <view class="u-icon-wrap" v-if="icon">
<view class="u-icon-wrap" v-if="icon"> <u-icon size="32" :name="icon" :color="iconColor" class="u-icon"></u-icon>
<u-icon size="32" :name="icon" :color="iconColor" class="u-icon"></u-icon> </view>
</view> <slot name="icon"></slot>
<slot name="icon"></slot> <text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text>
<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text> </view>
</view> <view class="fild-body">
<view class="fild-body"> <textarea v-if="type == 'textarea'" class="u-input-class u-textarea-class" :style="[inputStyle]" :value="value"
<textarea :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength"
v-if="type == 'textarea'" :focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"
class="u-input-class u-textarea-class" @tap="fieldClick" />
:style="[inputStyle]" <input
:value="value" v-else
:placeholder="placeholder" :style="[inputStyle]"
:placeholderStyle="placeholderStyle" class="u-input-class"
:disabled="disabled" :type="type"
:maxlength="inputMaxlength" :value="value"
:focus="focus" :password="password || type === 'password'"
:autoHeight="autoHeight" :placeholder="placeholder"
:fixed="fixed" :placeholderStyle="placeholderStyle"
@input="onInput" :disabled="disabled"
@blur="onBlur" :maxlength="inputMaxlength"
@focus="onFocus" :focus="focus"
@confirm="onConfirm" :confirmType="confirmType"
@tap="fieldClick" @focus="onFocus"
/> @blur="onBlur"
<input @input="onInput"
v-else @confirm="onConfirm"
:style="[inputStyle]" @tap="fieldClick"
class="u-input-class" />
:type="type" <u-icon v-if="clearable && value && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @touchstart="onClear"/>
:value="value" <view class="u-button-wrap"><slot name="button" /></view>
:password="password || type === 'password'" <u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" />
:placeholder="placeholder" </view>
:placeholderStyle="placeholderStyle" </view>
:disabled="disabled" <view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{
:maxlength="inputMaxlength" paddingLeft: labelWidth + 'rpx'
:focus="focus" }">{{ errorMessage }}</view>
:confirmType="confirmType" </view>
@focus="onFocus" </template>
@blur="onBlur"
@input="onInput" <script>
@confirm="onConfirm" /**
@tap="fieldClick" * alertTips 提示
/> * @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。
<u-icon v-if="clearable && value && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @touchstart="onClear"/> * @tutorial https://www.uviewui.com/components/field.html
<view class="u-button-wrap"><slot name="button" /></view> * @property {String} type 输入框的类型(默认text)
<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" /> * @property {String} icon label左边的图标,限uView的图标名称
</view> * @property {Boolean} right-icon 输入框右边的(默认false)
</view> * @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false)
<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{ * @property {String} label 输入框左边的文字提示
paddingLeft: labelWidth + 'rpx' * @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
}">{{ errorMessage }}</view> * @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
</view> * @property {Number String} label-width label的宽度,单位rpx(默认130)
</template> * @property {String} label-align label的文字对齐方式(默认left)
* @property {String} input-align 输入框内容对齐方式(默认left)
<script> * @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266)
export default { * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
props: { * @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
icon: String, * @property {String} placeholder 输入框的提示文字
rightIcon: String, * @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd"
// arrowDirection: { * @property {Boolean} focus 是否自动获得焦点(默认false)
// type: String, * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
// default: 'right' * @property {Boolean} disabled 是否不可输入(默认false)
// }, * @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
required: Boolean, * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
label: String, * @event {Function} input 输入框内容发生变化时触发
password: Boolean, * @event {Function} focus 输入框获得焦点时触发
clearable: { * @event {Function} blur 输入框失去焦点时触发
type: Boolean, * @event {Function} confirm 点击完成按钮时触发
default: true * @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发
}, * @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明
// 左边标题的宽度单位rpx * @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
labelWidth: { */
type: [Number, String], export default {
default: 130 name:"u-field",
}, props: {
// 对齐方式,left|center|right icon: String,
labelAlign: { rightIcon: String,
type: String, // arrowDirection: {
default: 'left' // type: String,
}, // default: 'right'
inputAlign: { // },
type: String, required: Boolean,
default: 'left' label: String,
}, password: Boolean,
iconColor: { clearable: {
type: String, type: Boolean,
default: '#606266' default: true
}, },
autoHeight: { // 左边标题的宽度单位rpx
type: Boolean, labelWidth: {
default: true type: [Number, String],
}, default: 130
errorMessage: { },
type: [String, Boolean], // 对齐方式,left|center|right
default: '' labelAlign: {
}, type: String,
placeholder: String, default: 'left'
placeholderStyle: String, },
focus: Boolean, inputAlign: {
fixed: Boolean, type: String,
value: [Number, String], default: 'left'
type: { },
type: String, iconColor: {
default: 'text' type: String,
}, default: '#606266'
disabled: { },
type: Boolean, autoHeight: {
default: false type: Boolean,
}, default: true
maxlength: { },
type: [Number, String], errorMessage: {
default: 140 type: [String, Boolean],
}, default: ''
confirmType: { },
type: String, placeholder: String,
default: 'done' placeholderStyle: String,
}, focus: Boolean,
// lable的位置,可选为 left-左边,top-上边 fixed: Boolean,
labelPostion: { value: [Number, String],
type: String, type: {
default: 'left' type: String,
} default: 'text'
}, },
inject: ['uCellGroup'], disabled: {
data() { type: Boolean,
return { default: false
focused: false, },
itemIndex: 0, maxlength: {
}; type: [Number, String],
}, default: 140
created() { },
if(this.uCellGroup) { confirmType: {
this.itemIndex = this.uCellGroup.index++; type: String,
} default: 'done'
}, },
computed: { // lable的位置,可选为 left-左边,top-上边
inputStyle() { labelPostion: {
let style = {}; type: String,
style.textAlign = this.inputAlign; default: 'left'
// 判断lable的位置,如果是left的话,让input左边两边有间隙 }
if(this.labelPostion == 'left') { },
style.margin = `0 8rpx`; inject: ['uCellGroup'],
} else { data() {
// 如果lable是top的,input的左边就没必要有间隙了 return {
style.marginRight = `8rpx`; focused: false,
} itemIndex: 0,
return style; };
}, },
rightIconStyle() { created() {
let style = {}; if(this.uCellGroup) {
if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)'; this.itemIndex = this.uCellGroup.index++;
if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)'; }
else style.transform = 'roate(0deg)'; },
return style; computed: {
}, inputStyle() {
labelStyle() { let style = {};
let style = {}; style.textAlign = this.inputAlign;
if(this.labelAlign == 'left') style.justifyContent = 'flext-start'; // 判断lable的位置,如果是left的话,让input左边两边有间隙
if(this.labelAlign == 'center') style.justifyContent = 'center'; if(this.labelPostion == 'left') {
if(this.labelAlign == 'right') style.justifyContent = 'flext-end'; style.margin = `0 8rpx`;
return style; } else {
}, // 如果lable是top的,input的左边就没必要有间隙了
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法 style.marginRight = `8rpx`;
justifyContent() { }
if(this.labelAlign == 'left') return 'flex-start'; return style;
if(this.labelAlign == 'center') return 'center'; },
if(this.labelAlign == 'right') return 'flex-end'; rightIconStyle() {
}, let style = {};
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)';
inputMaxlength() { if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)';
return Number(this.maxlength) else style.transform = 'roate(0deg)';
}, return style;
// label的位置 },
fieldInnerStyle() { labelStyle() {
let style = {}; let style = {};
if(this.labelPostion == 'left') { if(this.labelAlign == 'left') style.justifyContent = 'flext-start';
style.flexDirection = 'row'; if(this.labelAlign == 'center') style.justifyContent = 'center';
} else { if(this.labelAlign == 'right') style.justifyContent = 'flext-end';
style.flexDirection = 'column'; return style;
} },
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
return style; justifyContent() {
} if(this.labelAlign == 'left') return 'flex-start';
}, if(this.labelAlign == 'center') return 'center';
methods: { if(this.labelAlign == 'right') return 'flex-end';
onInput(event) { },
this.$emit('input', event.target.value); // 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
}, inputMaxlength() {
onFocus(event) { return Number(this.maxlength)
this.focused = true; },
this.$emit('focus', event); // label的位置
}, fieldInnerStyle() {
onBlur(event) { let style = {};
this.focused = false; if(this.labelPostion == 'left') {
this.$emit('blur', event); style.flexDirection = 'row';
}, } else {
onConfirm(e) { style.flexDirection = 'column';
this.$emit('confirm', e.detail.value); }
},
onClear(event) { return style;
this.$emit('input', ''); }
}, },
rightIconClick() { methods: {
this.$emit('right-icon-click'); onInput(event) {
this.$emit('click'); this.$emit('input', event.target.value);
}, },
fieldClick() { onFocus(event) {
this.$emit('click'); this.focused = true;
} this.$emit('focus', event);
} },
}; onBlur(event) {
</script> this.focused = false;
this.$emit('blur', event);
<style lang="scss" scoped> },
.u-field { onConfirm(e) {
font-size: 28rpx; this.$emit('confirm', e.detail.value);
padding: 20rpx 28rpx; },
text-align: left; onClear(event) {
position: relative; this.$emit('input', '');
color: $u-main-color; },
} rightIconClick() {
this.$emit('right-icon-click');
.u-field-inner { this.$emit('click');
display: flex; },
align-items: center; fieldClick() {
} this.$emit('click');
}
.u-textarea-inner { }
align-items: flex-start; };
} </script>
.u-textarea-class { <style lang="scss" scoped>
min-height: 96rpx; .u-field {
} font-size: 28rpx;
padding: 20rpx 28rpx;
.fild-body { text-align: left;
display: flex; position: relative;
flex: 1; color: $u-main-color;
align-items: center; }
}
.u-field-inner {
.u-arror-right { display: flex;
margin-left: 8rpx; align-items: center;
} }
.u-label-text { .u-textarea-inner {
display: inline-block; align-items: flex-start;
} }
.u-label-left-gap { .u-textarea-class {
margin-left: 6rpx; min-height: 96rpx;
} }
.u-label-postion-top { .fild-body {
flex-direction: column; display: flex;
align-items: flex-start; flex: 1;
} align-items: center;
}
.u-label {
width: 130rpx; .u-arror-right {
flex: 1 1 130rpx; margin-left: 8rpx;
text-align: left; }
position: relative;
display: flex; .u-label-text {
align-items: center; display: inline-block;
} }
.u-required::before { .u-label-left-gap {
content: '*'; margin-left: 6rpx;
position: absolute; }
left: -16rpx;
font-size: 14px; .u-label-postion-top {
color: $u-type-error; flex-direction: column;
height: 9px; align-items: flex-start;
line-height: 1; }
}
.u-label {
.u-input-class { width: 130rpx;
position: relative; flex: 1 1 130rpx;
overflow: hidden; text-align: left;
font-size: 28rpx; position: relative;
height: 48rpx; display: flex;
flex: 1; align-items: center;
width: auto; }
}
.u-required::before {
.u-clear-icon { content: '*';
display: flex; position: absolute;
align-items: center; left: -16rpx;
} font-size: 14px;
color: $u-type-error;
.u-field-border:after { height: 9px;
left: 32rpx!important; line-height: 1;
position: absolute; }
box-sizing: border-box;
content: ' '; .u-input-class {
pointer-events: none; position: relative;
right: 0; overflow: hidden;
top: 0; font-size: 28rpx;
border-bottom: 1px solid $u-border-color; height: 48rpx;
-webkit-transform: scaleY(0.5); flex: 1;
transform: scaleY(0.5); width: auto;
} }
.u-error-message { .u-clear-icon {
color: $u-type-error; display: flex;
font-size: 26rpx; align-items: center;
text-align: left; }
}
.u-field-border:after {
.placeholder-style { left: 32rpx!important;
color: rgb(150, 151, 153); position: absolute;
} box-sizing: border-box;
content: ' ';
pointer-events: none;
right: 0;
top: 0;
border-bottom: 1px solid $u-border-color;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
}
.u-error-message {
color: $u-type-error;
font-size: 26rpx;
text-align: left;
}
.placeholder-style {
color: rgb(150, 151, 153);
}
</style> </style>
<template> <template>
<view <view class="u-grid-item" :class="[showBorder ? 'u-border-right u-border-bottom' : '']" :hover-class="hoverClass ? 'u-grid-item-hover' : ''"
class="u-grid-item" :hover-stay-time="200" @tap="click" :style="{
:class="[showBorder ? 'u-border-right u-border-bottom' : '']" background: bgColor,
:hover-class="hoverClass ? 'u-grid-item-hover' : ''" width: width + 'px'
:hover-stay-time="200" }">
@tap="click" <view class="u-grid-item-box">
:style="{ <slot />
background: bgColor, </view>
width: width + 'px' </view>
}" </template>
>
<view class="u-grid-item-box"> <script>
<slot /> /**
</view> * alertTips 提示
</view> * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配<u-grid>使用
</template> * @tutorial https://www.uviewui.com/components/grid.html
* @property {String} bg-color 宫格的背景颜色(默认#ffffff)
<script> * @property {String Number} index 点击宫格时,返回的值
export default { * @event {Function} click 点击宫格触发
props: { * @example <u-grid-item></u-grid-item>
// 背景颜色 */
bgColor: { export default {
type: String, name: "u-grid-item",
default: '#ffffff' props: {
}, // 背景颜色
// 点击时返回的index bgColor: {
index: { type: String,
type: [Number, String], default: '#ffffff'
default: '' },
}, // 点击时返回的index
}, index: {
// 父组件通过provide传递过来的整个this type: [Number, String],
inject: ['uGrid'], default: ''
data() { },
return { },
hoverClass: '', // 按下去的时候,是否显示背景灰色 // 父组件通过provide传递过来的整个this
}; inject: ['uGrid'],
}, data() {
created() { return {
this.hoverClass = this.uGrid.hoverClass; hoverClass: '', // 按下去的时候,是否显示背景灰色
}, };
computed: { },
// 小于2,显示2列;大于12,显示12列 created() {
colNum() { this.hoverClass = this.uGrid.hoverClass;
return this.col < 2 ? 2 : this.col > 12 ? 12 : this.col; },
}, computed: {
// 每个grid-item的宽度 // 小于2,显示2列;大于12,显示12列
width() { colNum() {
return this.uGrid.width / this.uGrid.col; return this.col < 2 ? 2 : this.col > 12 ? 12 : this.col;
}, },
// 是否显示边框 // 每个grid-item的宽度
// 为了迎合演示的需要,从created生命周期移到此,以为演示中可能需要动态修改有无边框 width() {
showBorder() { return this.uGrid.width / this.uGrid.col;
return this.uGrid.border; },
} // 是否显示边框
}, // 为了迎合演示的需要,从created生命周期移到此,以为演示中可能需要动态修改有无边框
methods: { showBorder() {
click() { return this.uGrid.border;
this.$emit('click', this.index); }
this.uGrid.click(this.index); },
} methods: {
}, click() {
}; this.$emit('click', this.index);
</script> this.uGrid.click(this.index);
}
<style scoped lang="scss"> },
};
.u-grid-item { </script>
box-sizing: border-box;
background: #fff; <style scoped lang="scss">
display: flex; .u-grid-item {
align-items: center; box-sizing: border-box;
justify-content: center; background: #fff;
position: relative; display: flex;
flex-direction: column; align-items: center;
position: relative; justify-content: center;
} position: relative;
flex-direction: column;
.u-grid-item-hover { position: relative;
background: #f7f7f7 !important; }
}
.u-grid-item-hover {
.u-grid-marker-box { background: #f7f7f7 !important;
position: absolute; }
display: inline-block;
line-height: 0; .u-grid-marker-box {
} position: absolute;
display: inline-block;
.u-grid-marker-wrap { line-height: 0;
position: absolute; }
}
.u-grid-marker-wrap {
.u-grid-item-box { position: absolute;
padding: 30rpx 0; }
display: flex;
align-items: center; .u-grid-item-box {
justify-content: center; padding: 30rpx 0;
flex-direction: column; display: flex;
} align-items: center;
justify-content: center;
flex-direction: column;
}
</style> </style>
<template> <template>
<text class="uicon" :class="customClass" :style="{ <text class="uicon" :class="customClass" :style="{
color: color, color: color,
fontSize: size + 'rpx', fontSize: size + 'rpx',
fontWeight: bold ? 'bold' : 'normal', fontWeight: bold ? 'bold' : 'normal',
}" @tap="click" :hover-class="hoverClass" @touchstart="touchstart"> }"
@tap="click" :hover-class="hoverClass" @touchstart="touchstart">
</text>
</template> </text>
</template>
<script>
export default { <script>
props: { /**
// 图标类名 * alertTips 提示
name: { * @description 基于字体的图标集,包含了大多数常见场景的图标。
type: String, * @tutorial https://www.uviewui.com/components/icon.html
default: '' * @property {String} name 图标名称,见示例图标集
}, * @property {String} color 图标颜色(默认inherit)
// 图标颜色 * @property {String Number} size 图标字体大小,单位rpx(默认32)
color: { * @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出
type: String, * @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网
default: 'inherit' * @event {Function} click 点击图标时触发
}, * @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
// 字体大小,单位rpx */
size: { export default {
type: [Number, String], name: "u-icon",
default: 'inherit' props: {
}, // 图标类名
// 是否显示粗体 name: {
bold: { type: String,
type: Boolean, default: ''
default: false },
}, // 图标颜色
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个) color: {
index: { type: String,
type: [Number, String], default: 'inherit'
default: '' },
}, // 字体大小,单位rpx
// 触摸图标时的类名 size: {
hoverClass: { type: [Number, String],
type: String, default: 'inherit'
default: '' },
}, // 是否显示粗体
// 自定义扩展前缀,方便用户扩展自己的图标库 bold: {
customPrefix: { type: Boolean,
type: String, default: false
default: 'uicon' },
} // 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
}, index: {
data() { type: [Number, String],
return { default: ''
},
} // 触摸图标时的类名
}, hoverClass: {
computed: { type: String,
customClass() { default: ''
let classes = []; },
classes.push(this.customPrefix + '-' + this.name); // 自定义扩展前缀,方便用户扩展自己的图标库
// uView的自定义图标类名为u-iconfont customPrefix: {
if(this.customPrefix == 'uicon') classes.push('u-iconfont'); type: String,
else classes.push(this.customPrefix); default: 'uicon'
return classes; }
} },
}, data() {
methods: { return {
click() {
this.$emit('click', this.index); }
}, },
touchstart() { computed: {
this.$emit('touchstart', this.index); customClass() {
} let classes = [];
} classes.push(this.customPrefix + '-' + this.name);
} // uView的自定义图标类名为u-iconfont
</script> if (this.customPrefix == 'uicon') classes.push('u-iconfont');
else classes.push(this.customPrefix);
<style scoped lang="scss"> return classes;
@import '../../../uview/iconfont.css'; }
},
.uicon { methods: {
display: inline-flex; click() {
align-items: center; this.$emit('click', this.index);
} },
touchstart() {
this.$emit('touchstart', this.index);
}
}
}
</script>
<style scoped lang="scss">
@import '../../../uview/iconfont.css';
.uicon {
display: inline-flex;
align-items: center;
}
</style> </style>
<template> <template>
<view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]"> <view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]">
<view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]"> <view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]">
<slot v-if="useSlot" /> <slot v-if="useSlot" />
<block v-else> <block v-else>
<text>{{ index }}</text> <text>{{ index }}</text>
</block> </block>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
useSlot: { * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
type: Boolean, * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
default: false * @property {Boolean} use-slot 是否使用自定义内容的插槽(默认false)
}, * @property {String Number} index 索引字符,如果定义了use-slot,此参数自动失效
index: { * @property {Object} custStyle 自定义样式,对象形式,如"{color: 'red'}"
type: String, * @event {Function} default 锚点位置显示内容,默认为索引字符
default: '' * @example <u-index-anchor :index="item" />
}, */
customStyle: { export default {
type: Object, name: "u-index-anchor",
default() { props: {
return {} useSlot: {
} type: Boolean,
} default: false
}, },
data() { index: {
return { type: String,
active: false, default: ''
wrapperStyle: {}, },
anchorStyle: {} customStyle: {
} type: Object,
}, default () {
inject: ['UIndexList'], return {}
mounted() { }
this.UIndexList.children.push(this); }
this.UIndexList.updateData(); },
}, data() {
computed: { return {
customAnchorStyle() { active: false,
return Object.assign(this.anchorStyle, this.customStyle); wrapperStyle: {},
} anchorStyle: {}
} }
} },
</script> inject: ['UIndexList'],
mounted() {
<style lang="scss" scoped> this.UIndexList.children.push(this);
.u-index-anchor { this.UIndexList.updateData();
box-sizing: border-box; },
padding: 14rpx 24rpx; computed: {
color: #606266; customAnchorStyle() {
width: 100%; return Object.assign(this.anchorStyle, this.customStyle);
font-weight: 500; }
font-size: 28rpx; }
line-height: 1.2; }
background-color: rgb(245, 245, 245); </script>
}
<style lang="scss" scoped>
.u-index-anchor--active { .u-index-anchor {
right: 0; box-sizing: border-box;
left: 0; padding: 14rpx 24rpx;
color: #2979ff; color: #606266;
background-color: #fff; width: 100%;
} font-weight: 500;
font-size: 28rpx;
line-height: 1.2;
background-color: rgb(245, 245, 245);
}
.u-index-anchor--active {
right: 0;
left: 0;
color: #2979ff;
background-color: #fff;
}
</style> </style>
<template> <template>
<view class="u-index-bar"> <view class="u-index-bar">
<slot /> <slot />
<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove" @touchend.stop.prevent="onTouchStop" <view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
@touchcancel.stop.prevent="onTouchStop"> @touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}" <view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
:data-index="index"> :data-index="index">
{{ item }} {{ item }}
</view> </view>
</view> </view>
<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{ <view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{
zIndex: alertZIndex zIndex: alertZIndex
}"> }">
<text>{{indexList[touchmoveIndex]}}</text> <text>{{indexList[touchmoveIndex]}}</text>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
var indexList = function() { /**
var indexList = []; * alertTips 提示
var charCodeOfA = 'A'.charCodeAt(0); * @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
for (var i = 0; i < 26; i++) { * @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
indexList.push(String.fromCharCode(charCodeOfA + i)); * @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入
} * @property {Array[string number]} index-list 索引字符列表,数组(默认A-Z)
return indexList; * @property {Number String} z-index 锚点吸顶时的层级(默认965)
}; * @property {Boolean} sticky 是否开启锚点自动吸顶(默认true)
export default { * @property {Number String} offset-top 锚点自动吸顶时与顶部的距离(默认0)
props: { * @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff)
sticky: { * @event {Function} select 选中右边索引字符时触发
type: Boolean, * @example <u-index-list :scrollTop="scrollTop"></u-index-list>
default: true */
}, var indexList = function() {
zIndex: { var indexList = [];
type: [Number, String], var charCodeOfA = 'A'.charCodeAt(0);
default: '' for (var i = 0; i < 26; i++) {
}, indexList.push(String.fromCharCode(charCodeOfA + i));
scrollTop: { }
type: [Number, String], return indexList;
default: 0, };
}, export default {
offsetTop: { name: "u-index-list",
type: [Number, String], props: {
default: 0 sticky: {
}, type: Boolean,
indexList: { default: true
type: Array, },
default () { zIndex: {
return indexList() type: [Number, String],
} default: ''
}, },
activeColor: { scrollTop: {
type: String, type: [Number, String],
default: '#2979ff' default: 0,
} },
}, offsetTop: {
created() { type: [Number, String],
// #ifdef H5 default: 0
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44; },
// #endif indexList: {
// #ifndef H5 type: Array,
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0; default () {
// #endif return indexList()
}, }
provide() { },
return { activeColor: {
UIndexList: this type: String,
} default: '#2979ff'
}, }
data() { },
return { created() {
activeAnchorIndex: 0, // #ifdef H5
showSidebar: true, this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
children: [], // #endif
touchmove: false, // #ifndef H5
touchmoveIndex: 0, this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
} // #endif
}, },
watch: { provide() {
scrollTop() { return {
this.updateData() UIndexList: this
} }
}, },
computed: { data() {
// 弹出toast的z-index值 return {
alertZIndex() { activeAnchorIndex: 0,
return this.$u.zIndex.toast; showSidebar: true,
} children: [],
}, touchmove: false,
methods: { touchmoveIndex: 0,
updateData() { }
this.timer && clearTimeout(this.timer); },
this.timer = setTimeout(() => { watch: {
this.showSidebar = !!this.children.length; scrollTop() {
this.setRect().then(() => { this.updateData()
this.onScroll(); }
}); },
}, 0); computed: {
}, // 弹出toast的z-index值
setRect() { alertZIndex() {
return Promise.all([ return this.$u.zIndex.toast;
this.setAnchorsRect(), }
this.setListRect(), },
this.setSiderbarRect() methods: {
]); updateData() {
}, this.timer && clearTimeout(this.timer);
setAnchorsRect() { this.timer = setTimeout(() => {
return Promise.all(this.children.map((anchor, index) => anchor this.showSidebar = !!this.children.length;
.$uGetRect('.u-index-anchor-wrapper') this.setRect().then(() => {
.then((rect) => { this.onScroll();
Object.assign(anchor, { });
height: rect.height, }, 0);
top: rect.top },
}); setRect() {
}))); return Promise.all([
}, this.setAnchorsRect(),
setListRect() { this.setListRect(),
return this.$uGetRect('.u-index-bar').then((rect) => { this.setSiderbarRect()
Object.assign(this, { ]);
height: rect.height, },
top: rect.top + this.scrollTop setAnchorsRect() {
}); return Promise.all(this.children.map((anchor, index) => anchor
}); .$uGetRect('.u-index-anchor-wrapper')
}, .then((rect) => {
setSiderbarRect() { Object.assign(anchor, {
return this.$uGetRect('.u-index-bar__sidebar').then(rect => { height: rect.height,
this.sidebar = { top: rect.top
height: rect.height, });
top: rect.top })));
}; },
}); setListRect() {
}, return this.$uGetRect('.u-index-bar').then((rect) => {
getActiveAnchorIndex() { Object.assign(this, {
const { height: rect.height,
children top: rect.top + this.scrollTop
} = this; });
const { });
sticky },
} = this; setSiderbarRect() {
for (let i = this.children.length - 1; i >= 0; i--) { return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
const preAnchorHeight = i > 0 ? children[i - 1].height : 0; this.sidebar = {
const reachTop = sticky ? preAnchorHeight : 0; height: rect.height,
if (reachTop >= children[i].top) { top: rect.top
return i; };
} });
} },
return -1; getActiveAnchorIndex() {
}, const {
onScroll() { children
const { } = this;
children = [] const {
} = this; sticky
if (!children.length) { } = this;
return; for (let i = this.children.length - 1; i >= 0; i--) {
} const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
const { const reachTop = sticky ? preAnchorHeight : 0;
sticky, if (reachTop >= children[i].top) {
stickyOffsetTop, return i;
zIndex, }
scrollTop, }
activeColor return -1;
} = this; },
const active = this.getActiveAnchorIndex(); onScroll() {
this.activeAnchorIndex = active; const {
if (sticky) { children = []
let isActiveAnchorSticky = false; } = this;
if (active !== -1) { if (!children.length) {
isActiveAnchorSticky = return;
children[active].top <= 0; }
} const {
children.forEach((item, index) => { sticky,
if (index === active) { stickyOffsetTop,
let wrapperStyle = ''; zIndex,
let anchorStyle = { scrollTop,
color: `${activeColor}` activeColor
}; } = this;
if (isActiveAnchorSticky) { const active = this.getActiveAnchorIndex();
wrapperStyle = { this.activeAnchorIndex = active;
height: `${children[index].height}px` if (sticky) {
}; let isActiveAnchorSticky = false;
anchorStyle = { if (active !== -1) {
position: 'fixed', isActiveAnchorSticky =
top: `${stickyOffsetTop}px`, children[active].top <= 0;
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`, }
color: `${activeColor}` children.forEach((item, index) => {
}; if (index === active) {
} let wrapperStyle = '';
item.active = active; let anchorStyle = {
item.wrapperStyle = wrapperStyle; color: `${activeColor}`
item.anchorStyle = anchorStyle; };
} else if (index === active - 1) { if (isActiveAnchorSticky) {
const currentAnchor = children[index]; wrapperStyle = {
const currentOffsetTop = currentAnchor.top; height: `${children[index].height}px`
const targetOffsetTop = index === children.length - 1 ? };
this.top : anchorStyle = {
children[index + 1].top; position: 'fixed',
const parentOffsetHeight = targetOffsetTop - currentOffsetTop; top: `${stickyOffsetTop}px`,
const translateY = parentOffsetHeight - currentAnchor.height; zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
const anchorStyle = { color: `${activeColor}`
position: 'relative', };
transform: `translate3d(0, ${translateY}px, 0)`, }
zIndex: `${zIndex}`, item.active = active;
color: `${activeColor}` item.wrapperStyle = wrapperStyle;
}; item.anchorStyle = anchorStyle;
item.active = active; } else if (index === active - 1) {
item.anchorStyle = anchorStyle; const currentAnchor = children[index];
} else { const currentOffsetTop = currentAnchor.top;
item.active = false; const targetOffsetTop = index === children.length - 1 ?
item.anchorStyle = ''; this.top :
item.wrapperStyle = ''; children[index + 1].top;
} const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
}); const translateY = parentOffsetHeight - currentAnchor.height;
} const anchorStyle = {
}, position: 'relative',
onTouchMove(event) { transform: `translate3d(0, ${translateY}px, 0)`,
this.touchmove = true; zIndex: `${zIndex}`,
const sidebarLength = this.children.length; color: `${activeColor}`
const touch = event.touches[0]; };
const itemHeight = this.sidebar.height / sidebarLength; item.active = active;
let clientY = 0; item.anchorStyle = anchorStyle;
clientY = touch.clientY; } else {
let index = Math.floor((clientY - this.sidebar.top) / itemHeight); item.active = false;
if (index < 0) { item.anchorStyle = '';
index = 0; item.wrapperStyle = '';
} else if (index > sidebarLength - 1) { }
index = sidebarLength - 1; });
} }
this.touchmoveIndex = index; },
this.scrollToAnchor(index); onTouchMove(event) {
}, this.touchmove = true;
onTouchStop() { const sidebarLength = this.children.length;
this.touchmove = false; const touch = event.touches[0];
this.scrollToAnchorIndex = null; const itemHeight = this.sidebar.height / sidebarLength;
}, let clientY = 0;
scrollToAnchor(index) { clientY = touch.clientY;
if (this.scrollToAnchorIndex === index) { let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
return; if (index < 0) {
} index = 0;
this.scrollToAnchorIndex = index; } else if (index > sidebarLength - 1) {
const anchor = this.children.find((item) => item.index === this.indexList[index]); index = sidebarLength - 1;
if (anchor) { }
this.$emit('select', anchor.index); this.touchmoveIndex = index;
uni.pageScrollTo({ this.scrollToAnchor(index);
duration: 0, },
scrollTop: anchor.top + this.scrollTop onTouchStop() {
}); this.touchmove = false;
} this.scrollToAnchorIndex = null;
} },
} scrollToAnchor(index) {
}; if (this.scrollToAnchorIndex === index) {
</script> return;
}
<style lang="scss" scoped> this.scrollToAnchorIndex = index;
.u-index-bar { const anchor = this.children.find((item) => item.index === this.indexList[index]);
position: relative if (anchor) {
} this.$emit('select', anchor.index);
uni.pageScrollTo({
.u-index-bar__sidebar { duration: 0,
position: fixed; scrollTop: anchor.top + this.scrollTop
top: 50%; });
right: 0; }
display: flex; }
flex-direction: column; }
text-align: center; };
transform: translateY(-50%); </script>
user-select: none;
z-index: 99; <style lang="scss" scoped>
} .u-index-bar {
position: relative
.u-index-bar__index { }
font-weight: 500;
padding: 8rpx 18rpx; .u-index-bar__sidebar {
font-size: 22rpx; position: fixed;
line-height: 1 top: 50%;
} right: 0;
display: flex;
.u-indexed-list-alert { flex-direction: column;
position: fixed; text-align: center;
width: 120rpx; transform: translateY(-50%);
height: 120rpx; user-select: none;
right: 90rpx; z-index: 99;
top: 50%; }
margin-top: -60rpx;
border-radius: 24rpx; .u-index-bar__index {
font-size: 50rpx; font-weight: 500;
color: #fff; padding: 8rpx 18rpx;
background-color: rgba(0, 0, 0, 0.65); font-size: 22rpx;
display: flex; line-height: 1
justify-content: center; }
align-items: center;
padding: 0; .u-indexed-list-alert {
z-index: 9999999; position: fixed;
} width: 120rpx;
height: 120rpx;
.u-indexed-list-alert text { right: 90rpx;
line-height: 50rpx; top: 50%;
} margin-top: -60rpx;
border-radius: 24rpx;
font-size: 50rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.65);
display: flex;
justify-content: center;
align-items: center;
padding: 0;
z-index: 9999999;
}
.u-indexed-list-alert text {
line-height: 50rpx;
}
</style> </style>
<template> <template>
<u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose"> <u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
<slot /> :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose">
<view class="u-tooltip" v-if="tooltip"> <slot />
<view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel"> <view class="u-tooltip" v-if="tooltip">
{{cancelBtn ? '取消' : ''}} <view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel">
</view> {{cancelBtn ? '取消' : ''}}
<view v-if="tips" class="u-tooltip-item u-tooltip-tips"> </view>
{{tips && mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}} <view v-if="tips" class="u-tooltip-item u-tooltip-tips">
</view> {{tips && mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}
<view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover"> </view>
{{confirmBtn ? '完成' : ''}} <view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover">
</view> {{confirmBtn ? '完成' : ''}}
</view> </view>
<block v-if="mode == 'number' || mode == 'card'"> </view>
<u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard> <block v-if="mode == 'number' || mode == 'card'">
</block> <u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard>
<block v-else> </block>
<u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard> <block v-else>
</block> <u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard>
</u-popup> </block>
</template> </u-popup>
</template>
<script>
export default { <script>
props: { /**
// 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘 * alertTips 提示
mode: { * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。
type: String, * @tutorial https://www.uviewui.com/components/keyboard.html
default: 'number' * @property {String} mode 键盘类型,见官网基本使用的说明(默认number)
}, * @property {Boolean} dot-enabled 是否显示"."按键,只在mode=number时有效(默认true)
// 是否显示键盘的"."符号 * @property {Boolean} tooltip 是否显示键盘顶部工具条(默认true)
dotEnabled: { * @property {String} tips 工具条中间的提示文字,见上方基本使用的说明,如不需要,请传""空字符
type: Boolean, * @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮(默认true)
default: true * @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮(默认true)
}, * @property {Boolean} mask 是否显示遮罩(默认true)
// 是否显示顶部工具条 * @property {Number String} z-index 弹出键盘的z-index值(默认1075)
tooltip: { * @property {Boolean} random 是否打乱键盘按键的顺序(默认false)
type: Boolean, * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
default: true * @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘(默认true)
}, * @event {Function} change 按键被点击(不包含退格键被点击)
// 是否显示工具条中间的提示 * @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击
tips: { * @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击
type: Boolean, * @event {Function} backspace 键盘退格键被点击
default: true * @example <u-keyboard mode="number" v-model="show"></u-keyboard>
}, */
// 是否显示工具条左边的"取消"按钮 export default {
cancelBtn: { name: "u-keyboard",
type: Boolean, props: {
default: true // 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘
}, mode: {
// 是否显示工具条右边的"完成"按钮 type: String,
confirmBtn: { default: 'number'
type: Boolean, },
default: true // 是否显示键盘的"."符号
}, dotEnabled: {
// 是否打乱键盘按键的顺序 type: Boolean,
random: { default: true
type: Boolean, },
default: false // 是否显示顶部工具条
}, tooltip: {
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 type: Boolean,
safeAreaInsetBottom: { default: true
type: Boolean, },
default: false // 是否显示工具条中间的提示
}, tips: {
// 是否允许通过点击遮罩关闭键盘 type: Boolean,
maskCloseAble: { default: true
type: Boolean, },
default: true // 是否显示工具条左边的"取消"按钮
}, cancelBtn: {
// 通过双向绑定控制键盘的弹出与收起 type: Boolean,
value: { default: true
type: Boolean, },
default: false // 是否显示工具条右边的"完成"按钮
}, confirmBtn: {
// 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩 type: Boolean,
mask: { default: true
type: Boolean, },
default: true // 是否打乱键盘按键的顺序
}, random: {
// z-index值 type: Boolean,
zIndex: { default: false
type: [Number, String], },
default: '' // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
} safeAreaInsetBottom: {
}, type: Boolean,
data() { default: false
return { },
//show: false // 是否允许通过点击遮罩关闭键盘
} maskCloseAble: {
}, type: Boolean,
computed: { default: true
uZIndex() { },
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; // 通过双向绑定控制键盘的弹出与收起
} value: {
}, type: Boolean,
methods: { default: false
change(e) { },
this.$emit('change', e); // 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩
}, mask: {
// 键盘关闭 type: Boolean,
popupClose() { default: true
// 通过发送input这个特殊的事件名,可以修改父组件传给props的value的变量,也即双向绑定 },
this.$emit('input', false); // z-index值
}, zIndex: {
// 输入完成 type: [Number, String],
onConfirm() { default: ''
this.popupClose(); }
this.$emit('confirm'); },
}, data() {
// 取消输入 return {
onCancel() { //show: false
this.popupClose(); }
this.$emit('cancel'); },
}, computed: {
// 退格键 uZIndex() {
backspace() { return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
this.$emit('backspace'); }
}, },
// 关闭键盘 methods: {
// close() { change(e) {
// this.show = false; this.$emit('change', e);
// }, },
// // 打开键盘 // 键盘关闭
// open() { popupClose() {
// this.show = true; // 通过发送input这个特殊的事件名,可以修改父组件传给props的value的变量,也即双向绑定
// } this.$emit('input', false);
} },
} // 输入完成
</script> onConfirm() {
this.popupClose();
<style lang="scss" scoped> this.$emit('confirm');
.u-keyboard { },
position: relative; // 取消输入
z-index: 1003; onCancel() {
} this.popupClose();
this.$emit('cancel');
.u-tooltip { },
display: flex; // 退格键
justify-content: space-between; backspace() {
} this.$emit('backspace');
},
.u-tooltip-item { // 关闭键盘
color: #333333; // close() {
flex: 0 0 33.333333%; // this.show = false;
text-align: center; // },
padding: 20rpx 10rpx; // // 打开键盘
font-size: 28rpx; // open() {
} // this.show = true;
// }
.u-tooltips-submit { }
text-align: right; }
flex-grow: 1; </script>
flex-wrap: 0;
padding-right: 40rpx; <style lang="scss" scoped>
color: $u-type-primary; .u-keyboard {
} position: relative;
z-index: 1003;
.u-tooltip-cancel { }
text-align: left;
flex-grow: 1; .u-tooltip {
flex-wrap: 0; display: flex;
padding-left: 40rpx; justify-content: space-between;
color: #888888; }
}
.u-tooltip-item {
.u-tooltips-submit-hover { color: #333333;
color: $u-type-success; flex: 0 0 33.333333%;
} text-align: center;
padding: 20rpx 10rpx;
.u-tooltip-cancel-hover { font-size: 28rpx;
color: #333333; }
}
.u-tooltips-submit {
text-align: right;
flex-grow: 1;
flex-wrap: 0;
padding-right: 40rpx;
color: $u-type-primary;
}
.u-tooltip-cancel {
text-align: left;
flex-grow: 1;
flex-wrap: 0;
padding-left: 40rpx;
color: #888888;
}
.u-tooltips-submit-hover {
color: $u-type-success;
}
.u-tooltip-cancel-hover {
color: #333333;
}
</style> </style>
<template> <template>
<view class="u-progress" :style="{ <view class="u-progress" :style="{
borderRadius: round ? '100rpx' : 0, borderRadius: round ? '100rpx' : 0,
height: height + 'rpx', height: height + 'rpx',
backgroundColor: inactiveColor backgroundColor: inactiveColor
}"> }">
<view :class="{'u-striped': striped, 'u-striped-active': striped && stripedActive}" class="u-active" :style="[progressStyle]">{{showPercent ? percent + '%' : ''}}</view> <view :class="{'u-striped': striped, 'u-striped-active': striped && stripedActive}" class="u-active" :style="[progressStyle]">{{showPercent ? percent + '%' : ''}}</view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 两端是否显示半圆形 * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
round: { * @tutorial https://www.uviewui.com/components/lineProgress.html
type: Boolean, * @property {String Number} percent 进度条百分比值,为数值类型,0-100
default: true * @property {Boolean} round 进度条两端是否为半圆(默认true)
}, * @property {String} type 如设置,active-color值将会失效
// 主题颜色 * @property {String} active-color 进度条激活部分的颜色(默认#19be6b)
type: { * @property {String} inactive-color 进度条的底色(默认#ececec)
type: String, * @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true)
default: '' * @property {String Number} height 进度条的高度,单位rpx(默认28)
}, * @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false)
// 激活部分的颜色 * @property {Boolean} striped-active 条纹是否具有动态效果(默认false)
activeColor: { * @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
type: String, */
default: '#19be6b' export default {
}, name: "u-line-progress",
inactiveColor: { props: {
type: String, // 两端是否显示半圆形
default: '#ececec' round: {
}, type: Boolean,
// 进度百分比,数值 default: true
percent: { },
type: Number, // 主题颜色
default: 0 type: {
}, type: String,
// 是否在进度条内部显示百分比的值 default: ''
showPercent: { },
type: Boolean, // 激活部分的颜色
default: true activeColor: {
}, type: String,
// 进度条的高度,单位rpx default: '#19be6b'
height: { },
type: [Number, String], inactiveColor: {
default: 28 type: String,
}, default: '#ececec'
// 是否显示条纹 },
striped: { // 进度百分比,数值
type: Boolean, percent: {
default: false type: Number,
}, default: 0
// 条纹是否显示活动状态 },
stripedActive: { // 是否在进度条内部显示百分比的值
type: Boolean, showPercent: {
default: false type: Boolean,
} default: true
}, },
data() { // 进度条的高度,单位rpx
return { height: {
type: [Number, String],
} default: 28
}, },
computed: { // 是否显示条纹
progressStyle() { striped: {
let style = {}; type: Boolean,
style.width = this.percent + '%'; default: false
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) style.backgroundColor = this.$u.color[ },
this.type]; // 条纹是否显示活动状态
else style.backgroundColor = this.activeColor; stripedActive: {
return style; type: Boolean,
} default: false
}, }
methods: { },
data() {
} return {
}
</script> }
},
<style lang="scss" scoped> computed: {
.u-progress { progressStyle() {
overflow: hidden; let style = {};
height: 15px; style.width = this.percent + '%';
display: inline-flex; if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) style.backgroundColor = this.$u.color[
align-items: center; this.type];
width: 100%; else style.backgroundColor = this.activeColor;
border-radius: 100rpx; return style;
} }
},
.u-active { methods: {
width: 0;
height: 100%; }
align-items: center; }
display: flex; </script>
justify-items: flex-end;
justify-content: space-around; <style lang="scss" scoped>
font-size: 20rpx; .u-progress {
color: #ffffff; overflow: hidden;
transition: all 0.4s ease; height: 15px;
} display: inline-flex;
align-items: center;
.u-striped{ width: 100%;
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); border-radius: 100rpx;
background-size: 39px 39px; }
}
.u-active {
.u-striped-active { width: 0;
animation: progress-stripes 2s linear infinite; height: 100%;
} align-items: center;
display: flex;
@keyframes progress-stripes { justify-items: flex-end;
0% { justify-content: space-around;
background-position: 0 0; font-size: 20rpx;
} color: #ffffff;
100% { transition: all 0.4s ease;
background-position: 39px 0; }
}
} .u-striped {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-size: 39px 39px;
}
.u-striped-active {
animation: progress-stripes 2s linear infinite;
}
@keyframes progress-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 39px 0;
}
}
</style> </style>
<template> <template>
<text class="u-link" @tap.stop="openLink" :style="{ <text class="u-link" @tap.stop="openLink" :style="{
color: color, color: color,
fontSize: fontSize + 'rpx', fontSize: fontSize + 'rpx',
borderBottom: underLine ? `1px solid ${lineColor ? lineColor : color}` : 'none', borderBottom: underLine ? `1px solid ${lineColor ? lineColor : color}` : 'none',
paddingBottom: underLine ? '0rpx' : '0' paddingBottom: underLine ? '0rpx' : '0'
}"> }">
<slot></slot> <slot></slot>
</text> </text>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 文字颜色 * @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。
color: { * @tutorial https://www.uviewui.com/components/link.html
type: String, * @property {String} color 文字颜色(默认#606266)
default: '#2979ff' * @property {String Number} font-size 字体大小,单位rpx(默认28)
}, * @property {Boolean} under-line 是否显示下划线(默认false)
// 字体大小,单位rpx * @property {String} href 跳转的链接,要带上http(s)
fontSize: { * @property {String} line-color 下划线颜色,默认同color参数颜色
type: [String, Number], * @property {String} mp-tips 各个小程序平台把链接复制到粘贴板后的提示语(默认“链接已复制,请在浏览器打开”)
default: 28 * @example <u-link href="http://www.uviewui.com">蜀道难,难于上青天</u-link>
}, */
// 是否显示下划线 export default {
underLine: { name: "u-link",
type: Boolean, props: {
default: false // 文字颜色
}, color: {
// 要跳转的链接 type: String,
href: { default: '#2979ff'
type: String, },
default: '' // 字体大小,单位rpx
}, fontSize: {
// 小程序中复制到粘贴板的提示语 type: [String, Number],
mpTips: { default: 28
type: String, },
default: '链接已复制,请在浏览器打开' // 是否显示下划线
}, underLine: {
// 下划线颜色 type: Boolean,
lineColor: { default: false
type: String, },
default: '' // 要跳转的链接
} href: {
}, type: String,
methods: { default: ''
openLink() { },
// #ifdef APP-PLUS // 小程序中复制到粘贴板的提示语
plus.runtime.openURL(this.href) mpTips: {
// #endif type: String,
// #ifdef H5 default: '链接已复制,请在浏览器打开'
window.open(this.href) },
// #endif // 下划线颜色
// #ifdef MP lineColor: {
uni.setClipboardData({ type: String,
data: this.href default: ''
}); }
uni.showToast({ },
title: this.mpTips, methods: {
icon: 'none' openLink() {
}) // #ifdef APP-PLUS
// #endif plus.runtime.openURL(this.href)
} // #endif
} // #ifdef H5
} window.open(this.href)
</script> // #endif
// #ifdef MP
<style lang="scss" scoped> uni.setClipboardData({
.u-link { data: this.href
line-height: 1; });
} uni.showToast({
title: this.mpTips,
icon: 'none'
})
// #endif
}
}
}
</script>
<style lang="scss" scoped>
.u-link {
line-height: 1;
}
</style> </style>
<template> <template>
<view <view v-if="show" class="u-loading" :class="mode == 'circle' ? 'u-loading-circle' : 'u-loading-flower'" :style="[cricleStyle]">
v-if="show" </view>
class="u-loading" </template>
:class="mode == 'circle' ? 'u-loading-circle' : 'u-loading-flower'"
:style="[cricleStyle]" <script>
> /**
</view> * alertTips 提示
</template> * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
* @tutorial https://www.uviewui.com/components/loading.html
<script> * @property {String} mode 模式选择,见官网说明(默认circle)
export default { * @property {String} color 动画活动区域的颜色,只对 mode = flower 模式有效(默认#c7c7c7)
props: { * @property {String Number} size 加载图标的大小,单位rpx(默认34)
// 动画的类型 * @property {Boolean} show 是否显示动画(默认true)
mode: { * @example <u-loading mode="circle"></u-loading>
type: String, */
default: 'circle' export default {
}, name: "u-loading",
// 动画的颜色 props: {
color: { // 动画的类型
type: String, mode: {
default: '#c7c7c7' type: String,
}, default: 'circle'
// 加载图标的大小,单位rpx },
size: { // 动画的颜色
type: [String, Number], color: {
default: '34' type: String,
}, default: '#c7c7c7'
// 是否显示动画 },
show: { // 加载图标的大小,单位rpx
type: Boolean, size: {
default: true type: [String, Number],
} default: '34'
}, },
computed: { // 是否显示动画
// 加载中圆圈动画的样式 show: {
cricleStyle() { type: Boolean,
let style = {}; default: true
style.width = this.size + 'rpx'; }
style.height = this.size + 'rpx'; },
if(this.mode == 'circle') style.borderColor = `#e4e4e4 #e4e4e4 #e4e4e4 ${this.color ? this.color : '#c7c7c7'}`; computed: {
return style; // 加载中圆圈动画的样式
}, cricleStyle() {
} let style = {};
} style.width = this.size + 'rpx';
</script> style.height = this.size + 'rpx';
if (this.mode == 'circle') style.borderColor = `#e4e4e4 #e4e4e4 #e4e4e4 ${this.color ? this.color : '#c7c7c7'}`;
<style lang="scss" scoped> return style;
.u-loading-circle { },
display: inline-block; }
vertical-align: middle; }
width: 28rpx; </script>
height: 28rpx;
background: 0 0; <style lang="scss" scoped>
border-radius: 50%; .u-loading-circle {
border: 2px solid; display: inline-block;
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e; vertical-align: middle;
animation: u-circle 1s linear infinite; width: 28rpx;
} height: 28rpx;
background: 0 0;
.u-loading-flower { border-radius: 50%;
width: 20px; border: 2px solid;
height: 20px; border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
display: inline-block; animation: u-circle 1s linear infinite;
vertical-align: middle; }
-webkit-animation: a 1s steps(12) infinite;
animation: u-flower 1s steps(12) infinite; .u-loading-flower {
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat; width: 20px;
background-size: 100%; height: 20px;
} display: inline-block;
vertical-align: middle;
@keyframes u-flower { -webkit-animation: a 1s steps(12) infinite;
0% { animation: u-flower 1s steps(12) infinite;
-webkit-transform: rotate(0deg); background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
transform: rotate(0deg); background-size: 100%;
} }
to { @keyframes u-flower {
-webkit-transform: rotate(1turn); 0% {
transform: rotate(1turn); -webkit-transform: rotate(0deg);
} transform: rotate(0deg);
} }
@-webkit-keyframes u-circle { to {
0% { -webkit-transform: rotate(1turn);
transform: rotate(0); transform: rotate(1turn);
} }
}
100% {
transform: rotate(360deg); @-webkit-keyframes u-circle {
} 0% {
} transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style> </style>
<template> <template>
<view class="u-mask" :style="[maskStyle]" :class="[show ? 'u-mask-show' : '']" @tap="click" @touchmove.stop.prevent> <view class="u-mask" :style="[maskStyle]" :class="[show ? 'u-mask-show' : '']" @tap="click" @touchmove.stop.prevent>
<slot /> <slot />
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 是否显示遮罩 * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
show: { * @tutorial https://www.uviewui.com/components/mask.html
type: Boolean, * @property {Boolean} show 是否显示遮罩(默认false)
default: false * @property {String Number} z-index z-index 层级(默认1070)
}, * @property {Object} custom-style 自定义样式对象,见上方说明
// 层级z-index * @property {String Number} duration 动画时长,单位毫秒(默认300)
zIndex: { * @property {Boolean} zoom 是否使用scale对这招进行缩放(默认true)
type: [Number, String], * @property {Boolean} mask-click-able 遮罩是否可点击,为false时点击不会发送click事件(默认true)
default: '' * @event {Function} click mask-click-able为true时,点击遮罩发送此事件
}, * @example <u-mask :show="show" @click="show = false"></u-mask>
// 用户自定义样式 */
customStyle: { export default {
type: Object, name: "u-mask",
default () { props: {
return {} // 是否显示遮罩
} show: {
}, type: Boolean,
// 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放 default: false
zoom: { },
type: Boolean, // 层级z-index
default: true zIndex: {
}, type: [Number, String],
// 遮罩的过渡时间,单位为ms default: ''
duration: { },
type: [Number, String], // 用户自定义样式
default: 300 customStyle: {
}, type: Object,
// 是否可以通过点击遮罩进行关闭 default () {
maskClickAble: { return {}
type: Boolean, }
default: true },
} // 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放
}, zoom: {
computed: { type: Boolean,
maskStyle() { default: true
let style = {}; },
style.backgroundColor = "rgba(0, 0, 0, 0.6)"; // 遮罩的过渡时间,单位为ms
style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask; duration: {
style.transition = `all ${this.duration / 1000}s ease-in-out`; type: [Number, String],
// 缩放 default: 300
if (this.zoom == true) style.transform = 'scale(1.2, 1.2)'; },
// 判断用户传递的对象是否为空 // 是否可以通过点击遮罩进行关闭
if (Object.keys(this.customStyle).length) style = { ...style, maskClickAble: {
...this.customStyle type: Boolean,
}; default: true
// 合并自定义的样式 }
//Object.assign(style, customStyle); },
return style; computed: {
} maskStyle() {
}, let style = {};
methods: { style.backgroundColor = "rgba(0, 0, 0, 0.6)";
click() { style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask;
if(!this.maskClickAble) return ; style.transition = `all ${this.duration / 1000}s ease-in-out`;
this.$emit('click'); // 缩放
} if (this.zoom == true) style.transform = 'scale(1.2, 1.2)';
} // 判断用户传递的对象是否为空
} if (Object.keys(this.customStyle).length) style = { ...style,
</script> ...this.customStyle
};
<style lang="scss" scoped> // 合并自定义的样式
.u-mask { //Object.assign(style, customStyle);
position: fixed; return style;
top: 0; }
left: 0; },
right: 0; methods: {
bottom: 0; click() {
opacity: 0; if (!this.maskClickAble) return;
visibility: hidden; this.$emit('click');
} }
}
.u-mask-show { }
opacity: 1; </script>
visibility: visible;
transform: scale(1); <style lang="scss" scoped>
} .u-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
visibility: hidden;
}
.u-mask-show {
opacity: 1;
visibility: visible;
transform: scale(1);
}
</style> </style>
<template> <template>
<view class="u-no-network" v-if="!isConnected" :style="{'z-index': uZIndex}" @touchmove.stop.prevent=""> <view class="u-no-network" v-if="!isConnected" :style="{'z-index': uZIndex}" @touchmove.stop.prevent="">
<view class="u-inner"> <view class="u-inner">
<image class="u-error-icon" :src="image" mode="widthFix"></image> <image class="u-error-icon" :src="image" mode="widthFix"></image>
<view class="u-tips"> <view class="u-tips">
{{tips}} {{tips}}
</view> </view>
<!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 --> <!-- 只有APP平台,才能跳转设置页,因为需要调用plus环境 -->
<!-- #ifdef APP-PLUS --> <!-- #ifdef APP-PLUS -->
<view class="u-to-setting"> <view class="u-to-setting">
请检查网络,或前往<text class="u-setting-btn" @tap="openSettings">设置</text> 请检查网络,或前往<text class="u-setting-btn" @tap="openSettings">设置</text>
</view> </view>
<!-- #endif --> <!-- #endif -->
<view class="u-retry" :hover-stay-time="150" @tap="retry" hover-class="u-retry-hover"> <view class="u-retry" :hover-stay-time="150" @tap="retry" hover-class="u-retry-hover">
重试 重试
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 页面文字提示 * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。
tips: { * @tutorial https://www.uviewui.com/components/noNetwork.html
type: String, * @property {String} tips 没有网络时的提示语(默认哎呀,网络信号丢失)
default: '哎呀,网络信号丢失' * @property {String Number} zIndex 组件的z-index值(默认1080)
}, * @property {String} image 无网络的图片提示,可用的src地址或base64图片
// 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖 * @event {Function} retry 用户点击页面的"重试"按钮时触发
zIndex: { * @example <u-no-network></u-no-network>
type: [Number, String], */
default: '' export default {
}, name: "u-no-network",
// image 没有网络的图片提示 props: {
image: { // 页面文字提示
type: String, tips: {
default: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEYCAMAAABFglBLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0U3MjVFMzQwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0U3MjVFMzUwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozRTcyNUUzMjA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozRTcyNUUzMzA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkHIU9QAAAMAUExURdHW2OWiou7u7tve4dnc3/vw8N3g4sPCwvjn5+jo6M7Q0u6+vtyEhPXY2Li+wuikpPXW1uXo6dba3Pbg4Na5u+qurqqyt/HJydjb3fjo6LrAxO7Bwey1td6MjOrs7fbc3OTn6Maytf7+/vz19eqqqrzCxvzz87a8wLO6vuqxsf78/PDFxenr7L/FyNTY26+2u/z09MjMzqy0udnZ2dvb27G4vMjN0O7v8P339+Ll5u3v8NDS1ODj5frt7bC3vP76+u24uOKamtTW2MTIy9zc3N7e3vPQ0OCTk8DGyfDDw9LR0c7S1LussNbY2fPOztLU1cLHyrK6vvji4vrv78bKzPX29/np6crP0vjk5Ozu78bLzuepqczR1MHFyMDEyOq1tfTS0vP09cLIy9DU173Ex8bGxvHy88/U18vO0LK4vM7OzsTKzcPJzPLMzM/T1sbMz8HJy+7FxbW8wNTW18XKzvb3+MjLzczP0d3e3+Dh4r3Dxtna29zd3rK5vdPU1cfM0Prq6uOenuPm58HGyfHz8/ro6Lq/w8nO0bzCxcrKytjZ2uXm5vTU1LvBxbW7v+rp6eefn/39/eLi4u3t7Ozs7Pj4+Pr6+vX19fHx8erq6vPz8+Xl5ff39+fn5/v7+/z8/PLx8fX09Pb29ri4uOTk5PHw8OPj4/Py8ubm5vn5+e3s7Pb19fTz8/f29tfX1+7t7cvQ0+zr6/Ly8u3t7fr5+efm5quzuOvq6vT19uvr6/j5+a61uuy6uvb39/T09Ojn5+jq6621uvn6+t/i5KyzuPn4+Le+wvv6+s3S1fj4+fDw8OHk5vv7/Pr7++Xk5LS7v7y8vObl5fz9/ff4+OTj48DEx8XJy+Hk5euysu/x8re9wfn5+v38/Pv8/Pr6++vt7uLi4+Pi4ubl5uXk5dPV1tbV1fHMzNPX2e7u7ejn6PT19fX19PDw7/b29f39/vnr6+Hh4a+3u+7t7q61uePi48PHyf35+enp6fLz9PPz8qmxtuDg4N/f3/Dv7////////1cfN/UAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAlqElEQVR42uydCXwUVZrAk5CEJBAQshACCGyjIRwBJR0It6JBGZZBkRtm5PBgFXAXHBghqAMz6LjGTrd9n7lvwn0JQhARxYNxVJTMwcwOM+7uzK6Z3VXcrnTXVr2q6q7urrvfq26c+n4egXTSVfXv993veym4JkklKdoj0IBoogHRgGiiAdGAaKIB0YBoogHRgGiiAdFEA6IBUVuwx+vqet/v1YAkhxwmcNT9gvjnsAYkGcT7eN1TtThe+1TdL2o1IEkg+rrvUV90MF9oQBIqv6ijF4a3d51XA5J4jVVXx3z587ojGpDEm/QwkO9pQJJB2CvksAYk8fJUXT31xZG63ppRTwI5QgcgR35Rd1MDkgxSX1f31P33P1V3i3u936HUyc3edWSsjuEakKRRW3r9kVv+JrRsrwbklpKTnXs+z0wfONOtAUm8rJu55oqfkZ0TOzUgCZS3Zh7s54+S5RqQhNGYMsnPISkakCSiQcjkNzUgasvUDaVsBF1fb9GXd//B0ziliPzjEA2IujLqGRaMST+cawgLNpn4q30aEDV1Va+dYRqzNy00RMoYUmdpQNTzcb8sCtuKH8w9Z4iWLuIbVzQgapmOBedDOO6c0WjgENK2vKwBUUUmvBSiUbrqxwZuOU18d5MGhEe/dI6a2WvIxA0HX1hfvH7V8gUpA9Mz3x/VuVTRLxvwdAhH5aJjBj4hv39QAxIl743ITHm6azBPnOCfVLwlc6pR3upYFbLjvIuDlEUqRYZo38JYS4gVt5rNZhMQO46byksoMeG4w8QIjtvMpJA/wP1EdZ0bKvkitkh555mBA3zSLnBlSFnNnlJvEJJ95ItW3pJAamvtZhy3m/T0g7fhthJGiG+EviYgmEJ/wHFP6GsbnlZSoicoucxmGwHVguPTBz543C9LHky/Jnql1w5KxWGo8KsThsAFYneYMOb5mlnP11ZCrwOPjfgGI8RqsZuZtUOsFmbllFhZP+zEbc8vGcqzMibv7qos3rd7Mg+TrQv2vCVkiLYc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" type: String,
} default: '哎呀,网络信号丢失'
}, },
data() { // 一个z-index值,用于设置没有网络这个组件的层次,因为页面可能会有其他定位的元素层级过高,导致此组件被覆盖
return { zIndex: {
isConnected: true, // 是否有网络连接 type: [Number, String],
networkType: "none", // 网络类型 default: ''
} },
}, // image 没有网络的图片提示
computed: { image: {
uZIndex() { type: String,
return this.zIndex ? this.zIndex : this.$u.zIndex.noNetwork; default: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAEYCAMAAABFglBLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0U3MjVFMzQwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0U3MjVFMzUwNEY1MTFFQUE4MTNDOUEzMTVBREMxQjIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozRTcyNUUzMjA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozRTcyNUUzMzA0RjUxMUVBQTgxM0M5QTMxNUFEQzFCMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkHIU9QAAAMAUExURdHW2OWiou7u7tve4dnc3/vw8N3g4sPCwvjn5+jo6M7Q0u6+vtyEhPXY2Li+wuikpPXW1uXo6dba3Pbg4Na5u+qurqqyt/HJydjb3fjo6LrAxO7Bwey1td6MjOrs7fbc3OTn6Maytf7+/vz19eqqqrzCxvzz87a8wLO6vuqxsf78/PDFxenr7L/FyNTY26+2u/z09MjMzqy0udnZ2dvb27G4vMjN0O7v8P339+Ll5u3v8NDS1ODj5frt7bC3vP76+u24uOKamtTW2MTIy9zc3N7e3vPQ0OCTk8DGyfDDw9LR0c7S1LussNbY2fPOztLU1cLHyrK6vvji4vrv78bKzPX29/np6crP0vjk5Ozu78bLzuepqczR1MHFyMDEyOq1tfTS0vP09cLIy9DU173Ex8bGxvHy88/U18vO0LK4vM7OzsTKzcPJzPLMzM/T1sbMz8HJy+7FxbW8wNTW18XKzvb3+MjLzczP0d3e3+Dh4r3Dxtna29zd3rK5vdPU1cfM0Prq6uOenuPm58HGyfHz8/ro6Lq/w8nO0bzCxcrKytjZ2uXm5vTU1LvBxbW7v+rp6eefn/39/eLi4u3t7Ozs7Pj4+Pr6+vX19fHx8erq6vPz8+Xl5ff39+fn5/v7+/z8/PLx8fX09Pb29ri4uOTk5PHw8OPj4/Py8ubm5vn5+e3s7Pb19fTz8/f29tfX1+7t7cvQ0+zr6/Ly8u3t7fr5+efm5quzuOvq6vT19uvr6/j5+a61uuy6uvb39/T09Ojn5+jq6621uvn6+t/i5KyzuPn4+Le+wvv6+s3S1fj4+fDw8OHk5vv7/Pr7++Xk5LS7v7y8vObl5fz9/ff4+OTj48DEx8XJy+Hk5euysu/x8re9wfn5+v38/Pv8/Pr6++vt7uLi4+Pi4ubl5uXk5dPV1tbV1fHMzNPX2e7u7ejn6PT19fX19PDw7/b29f39/vnr6+Hh4a+3u+7t7q61uePi48PHyf35+enp6fLz9PPz8qmxtuDg4N/f3/Dv7////////1cfN/UAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAlqElEQVR42uydCXwUVZrAk5CEJBAQshACCGyjIRwBJR0It6JBGZZBkRtm5PBgFXAXHBghqAMz6LjGTrd9n7lvwn0JQhARxYNxVJTMwcwOM+7uzK6Z3VXcrnTXVr2q6q7urrvfq26c+n4egXTSVfXv993veym4JkklKdoj0IBoogHRgGiiAdGAaKIB0YBoogHRgGiiAdFEA6IBUVuwx+vqet/v1YAkhxwmcNT9gvjnsAYkGcT7eN1TtThe+1TdL2o1IEkg+rrvUV90MF9oQBIqv6ijF4a3d51XA5J4jVVXx3z587ojGpDEm/QwkO9pQJJB2CvksAYk8fJUXT31xZG63ppRTwI5QgcgR35Rd1MDkgxSX1f31P33P1V3i3u936HUyc3edWSsjuEakKRRW3r9kVv+JrRsrwbklpKTnXs+z0wfONOtAUm8rJu55oqfkZ0TOzUgCZS3Zh7s54+S5RqQhNGYMsnPISkakCSiQcjkNzUgasvUDaVsBF1fb9GXd//B0ziliPzjEA2IujLqGRaMST+cawgLNpn4q30aEDV1Va+dYRqzNy00RMoYUmdpQNTzcb8sCtuKH8w9Z4iWLuIbVzQgapmOBedDOO6c0WjgENK2vKwBUUUmvBSiUbrqxwZuOU18d5MGhEe/dI6a2WvIxA0HX1hfvH7V8gUpA9Mz3x/VuVTRLxvwdAhH5aJjBj4hv39QAxIl743ITHm6azBPnOCfVLwlc6pR3upYFbLjvIuDlEUqRYZo38JYS4gVt5rNZhMQO46byksoMeG4w8QIjtvMpJA/wP1EdZ0bKvkitkh555mBA3zSLnBlSFnNnlJvEJJ95ItW3pJAamvtZhy3m/T0g7fhthJGiG+EviYgmEJ/wHFP6GsbnlZSoicoucxmGwHVguPTBz543C9LHky/Jnql1w5KxWGo8KsThsAFYneYMOb5mlnP11ZCrwOPjfgGI8RqsZuZtUOsFmbllFhZP+zEbc8vGcqzMibv7qos3rd7Mg+TrQv2vCVkiLYc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"
} }
}, },
mounted() { data() {
this.isIOS = (uni.getSystemInfoSync().platform === 'ios'); return {
uni.onNetworkStatusChange((res) => { isConnected: true, // 是否有网络连接
this.isConnected = res.isConnected; networkType: "none", // 网络类型
this.networkType = res.networkType; }
}); },
uni.getNetworkType({ computed: {
success: (res) => { uZIndex() {
this.networkType = res.networkType; return this.zIndex ? this.zIndex : this.$u.zIndex.noNetwork;
if (res.networkType == 'none') { }
this.isConnected = false; },
} else { mounted() {
this.isConnected = true; this.isIOS = (uni.getSystemInfoSync().platform === 'ios');
} uni.onNetworkStatusChange((res) => {
} this.isConnected = res.isConnected;
}); this.networkType = res.networkType;
}, });
methods: { uni.getNetworkType({
retry() { success: (res) => {
// 重新检查网络 this.networkType = res.networkType;
uni.getNetworkType({ if (res.networkType == 'none') {
success: (res) => { this.isConnected = false;
this.networkType = res.networkType; } else {
if (res.networkType == 'none') { this.isConnected = true;
uni.showToast({ }
title: '无网络连接', }
icon: 'none', });
position: 'top' },
}) methods: {
this.isConnected = false; retry() {
} else { // 重新检查网络
uni.showToast({ uni.getNetworkType({
title: '网络已连接', success: (res) => {
icon: 'none', this.networkType = res.networkType;
position: 'top' if (res.networkType == 'none') {
}) uni.showToast({
this.isConnected = true; title: '无网络连接',
} icon: 'none',
} position: 'top'
}); })
this.$emit('retry'); this.isConnected = false;
}, } else {
async openSettings() { uni.showToast({
if (this.networkType == "none") { title: '网络已连接',
this.openSystemSettings(); icon: 'none',
return; position: 'top'
} })
}, this.isConnected = true;
openAppSettings() { }
this.gotoAppSetting(); }
}, });
openSystemSettings() { this.$emit('retry');
// 以下方法来自5+范畴,如需深究,请自行查阅相关文档 },
// https://ask.dcloud.net.cn/docs/ async openSettings() {
if (this.isIOS) { if (this.networkType == "none") {
this.gotoiOSSetting(); this.openSystemSettings();
} else { return;
this.gotoAndroidSetting(); }
} },
}, openAppSettings() {
network() { this.gotoAppSetting();
var result = null; },
var cellularData = plus.ios.newObject("CTCellularData"); openSystemSettings() {
var state = cellularData.plusGetAttribute("restrictedState"); // 以下方法来自5+范畴,如需深究,请自行查阅相关文档
if (state == 0) { // https://ask.dcloud.net.cn/docs/
result = null; if (this.isIOS) {
} else if (state == 2) { this.gotoiOSSetting();
result = 1; } else {
} else if (state == 1) { this.gotoAndroidSetting();
result = 2; }
} },
plus.ios.deleteObject(cellularData); network() {
return result; var result = null;
}, var cellularData = plus.ios.newObject("CTCellularData");
gotoAppSetting() { var state = cellularData.plusGetAttribute("restrictedState");
if (this.isIOS) { if (state == 0) {
var UIApplication = plus.ios.import("UIApplication"); result = null;
var application2 = UIApplication.sharedApplication(); } else if (state == 2) {
var NSURL2 = plus.ios.import("NSURL"); result = 1;
var setting2 = NSURL2.URLWithString("app-settings:"); } else if (state == 1) {
application2.openURL(setting2); result = 2;
plus.ios.deleteObject(setting2); }
plus.ios.deleteObject(NSURL2); plus.ios.deleteObject(cellularData);
plus.ios.deleteObject(application2); return result;
} else { },
var Intent = plus.android.importClass("android.content.Intent"); gotoAppSetting() {
var Settings = plus.android.importClass("android.provider.Settings"); if (this.isIOS) {
var Uri = plus.android.importClass("android.net.Uri"); var UIApplication = plus.ios.import("UIApplication");
var mainActivity = plus.android.runtimeMainActivity(); var application2 = UIApplication.sharedApplication();
var intent = new Intent(); var NSURL2 = plus.ios.import("NSURL");
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); var setting2 = NSURL2.URLWithString("app-settings:");
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null); application2.openURL(setting2);
intent.setData(uri); plus.ios.deleteObject(setting2);
mainActivity.startActivity(intent); plus.ios.deleteObject(NSURL2);
} plus.ios.deleteObject(application2);
}, } else {
gotoiOSSetting() { var Intent = plus.android.importClass("android.content.Intent");
var UIApplication = plus.ios.import("UIApplication"); var Settings = plus.android.importClass("android.provider.Settings");
var application2 = UIApplication.sharedApplication(); var Uri = plus.android.importClass("android.net.Uri");
var NSURL2 = plus.ios.import("NSURL"); var mainActivity = plus.android.runtimeMainActivity();
var setting2 = NSURL2.URLWithString("App-prefs:root=General"); var intent = new Intent();
application2.openURL(setting2); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
plus.ios.deleteObject(setting2); var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
plus.ios.deleteObject(NSURL2); intent.setData(uri);
plus.ios.deleteObject(application2); mainActivity.startActivity(intent);
}, }
gotoAndroidSetting() { },
var Intent = plus.android.importClass("android.content.Intent"); gotoiOSSetting() {
var Settings = plus.android.importClass("android.provider.Settings"); var UIApplication = plus.ios.import("UIApplication");
var mainActivity = plus.android.runtimeMainActivity(); var application2 = UIApplication.sharedApplication();
var intent = new Intent(Settings.ACTION_SETTINGS); var NSURL2 = plus.ios.import("NSURL");
mainActivity.startActivity(intent); var setting2 = NSURL2.URLWithString("App-prefs:root=General");
} application2.openURL(setting2);
} plus.ios.deleteObject(setting2);
} plus.ios.deleteObject(NSURL2);
</script> plus.ios.deleteObject(application2);
},
<style lang="scss" scoped> gotoAndroidSetting() {
.u-no-network { var Intent = plus.android.importClass("android.content.Intent");
background-color: #fff; var Settings = plus.android.importClass("android.provider.Settings");
position: fixed; var mainActivity = plus.android.runtimeMainActivity();
top: 0; var intent = new Intent(Settings.ACTION_SETTINGS);
left: 0; mainActivity.startActivity(intent);
right: 0; }
bottom: 0; }
} }
</script>
.u-inner {
padding-top: 200rpx; <style lang="scss" scoped>
display: flex; .u-no-network {
flex-direction: column; background-color: #fff;
align-items: center; position: fixed;
} top: 0;
left: 0;
.u-tips { right: 0;
color: $u-tips-color; bottom: 0;
font-size: 28rpx; }
padding: 30rpx 0;
} .u-inner {
padding-top: 200rpx;
.u-error-icon { display: flex;
width: 300rpx; flex-direction: column;
} align-items: center;
}
.u-to-setting {
color: $u-light-color; .u-tips {
font-size: 26rpx; color: $u-tips-color;
} font-size: 28rpx;
padding: 30rpx 0;
.u-setting-btn { }
font-size: 26rpx;
color: $u-type-primary; .u-error-icon {
} width: 300rpx;
}
.u-retry {
margin-top: 30rpx; .u-to-setting {
border: 1px solid $u-tips-color; color: $u-light-color;
color: $u-tips-color; font-size: 26rpx;
font-size: 28rpx; }
padding: 6rpx 30rpx;
border-radius: 3px; .u-setting-btn {
} font-size: 26rpx;
color: $u-type-primary;
.u-retry-hover { }
color: #fff;
background-color: $u-tips-color; .u-retry {
} margin-top: 30rpx;
border: 1px solid $u-tips-color;
color: $u-tips-color;
font-size: 28rpx;
padding: 6rpx 30rpx;
border-radius: 3px;
}
.u-retry-hover {
color: #fff;
background-color: $u-tips-color;
}
</style> </style>
<template> <template>
<view class="u-numberbox"> <view class="u-numberbox">
<view <view class="u-icon-minus" @tap="minus" :class="{ 'u-icon-disabled': disabled || inputVal <= min }" :style="{
class="u-icon-minus" background: bgColor,
@tap="minus" height: inputHeight + 'rpx',
:class="{ 'u-icon-disabled': disabled || inputVal <= min }" color: color
:style="{ }">
background: bgColor, <u-icon name="minus" :size="size"></u-icon>
height: inputHeight + 'rpx', </view>
color: color <input :disabled="disabled" :class="{ 'u-input-disabled': disabled }" v-model="inputVal" class="u-number-input" @blur="onBlur"
}" type="number" :style="{
> color: color,
<u-icon name="minus" :size="size"></u-icon> fontSize: size + 'rpx',
</view> background: bgColor,
<input height: inputHeight + 'rpx',
:disabled="disabled" width: inputWidth + 'rpx'
:class="{ 'u-input-disabled': disabled }" }" />
v-model="inputVal" <view class="u-icon-plus" @tap="plus" :class="{ 'u-icon-disabled': disabled || inputVal >= max }" :style="{
class="u-number-input" background: bgColor,
@blur="onBlur" height: inputHeight + 'rpx',
type="number" color: color
:style="{ }">
color: color, <u-icon name="plus" :size="size"></u-icon>
fontSize: size + 'rpx', </view>
background: bgColor, </view>
height: inputHeight + 'rpx', </template>
width: inputWidth + 'rpx'
}" <script>
/> /**
<view * alertTips 提示
class="u-icon-plus" * @description 该组件一般用于商城购物选择物品数量的场景。注意:该输入框只能输入大于或等于0的整数,不支持小数输入
@tap="plus" * @tutorial https://www.uviewui.com/components/numberBox.html
:class="{ 'u-icon-disabled': disabled || inputVal >= max }" * @property {Number} value 输入框初始值(默认1)
:style="{ * @property {String} bg-color 输入框和按钮的背景颜色(默认#F2F3F5)
background: bgColor, * @property {Number} min 用户可输入的最小值(默认0)
height: inputHeight + 'rpx', * @property {Number} max 用户可输入的最大值(默认99999)
color: color * @property {Number} step 步长,每次加或减的值(默认1)
}" * @property {Boolean} disabled 是否禁用操作,禁用后无法加减或手动修改输入框的值(默认false)
> * @property {String Number} size 输入框文字和按钮字体大小,单位rpx(默认26)
<u-icon name="plus" :size="size"></u-icon> * @property {String} color 输入框文字和加减按钮图标的颜色(默认#323233)
</view> * @property {String Number} input-width 输入框宽度,单位rpx(默认80)
</view> * @property {String Number} input-height 输入框和按钮的高度,单位rpx(默认50)
</template> * @property {String Number} index 事件回调时用以区分当前发生变化的是哪个输入框
* @event {Function} change 输入框内容发生变化时触发,对象形式
<script> * @event {Function} blur 输入框失去焦点时触发,对象形式
export default { * @event {Function} minus 点击减少按钮时触发(按钮可点击情况下),对象形式
props: { * @event {Function} plus 点击增加按钮时触发(按钮可点击情况下),对象形式
// 预显示的数字 * @example <u-number-box :min="1" :max="100"></u-number-box>
value: { */
type: Number, export default {
default: 1 name: "u-number-box",
}, props: {
// 背景颜色 // 预显示的数字
bgColor: { value: {
type: String, type: Number,
default: '#F2F3F5' default: 1
}, },
// 最小值 // 背景颜色
min: { bgColor: {
type: Number, type: String,
default: 0 default: '#F2F3F5'
}, },
// 最大值 // 最小值
max: { min: {
type: Number, type: Number,
default: 99999 default: 0
}, },
// 步进值,每次加或减的值 // 最大值
step: { max: {
type: Number, type: Number,
default: 1 default: 99999
}, },
// 是否禁用加减操作 // 步进值,每次加或减的值
disabled: { step: {
type: Boolean, type: Number,
default: false default: 1
}, },
// input的字体大小,单位rpx // 是否禁用加减操作
size: { disabled: {
type: [Number, String], type: Boolean,
default: 26 default: false
}, },
// 加减图标的颜色 // input的字体大小,单位rpx
color: { size: {
type: String, type: [Number, String],
default: '#323233' default: 26
}, },
// input宽度,单位rpx // 加减图标的颜色
inputWidth: { color: {
type: [Number, String], type: String,
default: 80 default: '#323233'
}, },
// input高度,单位rpx // input宽度,单位rpx
inputHeight: { inputWidth: {
type: [Number, String], type: [Number, String],
default: 50 default: 80
}, },
// index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可 // input高度,单位rpx
index: { inputHeight: {
type: [Number, String], type: [Number, String],
default: '' default: 50
} },
}, // index索引,用于列表中使用,让用户知道是哪个numberbox发生了变化,一般使用for循环出来的index值即可
watch: { index: {
value(val) { type: [Number, String],
this.inputVal = +val; default: ''
}, }
inputVal(v1, v2) { },
// 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串 watch: {
if(v1 == '') return ; value(val) {
let value = 0; this.inputVal = +val;
// 首先判断是否正整数,并且在min和max之间,如果不是,使用原来值 },
let tmp = /(^\d+$)/.test(v1) && value[0] != 0; inputVal(v1, v2) {
if(tmp && v1 >= this.min && v1 <= this.max) value = v1; // 为了让用户能够删除所有输入值,重新输入内容,删除所有值后,内容为空字符串
else value = v2; if (v1 == '') return;
this.handleChange(value, 'change'); let value = 0;
this.$nextTick(() => { // 首先判断是否正整数,并且在min和max之间,如果不是,使用原来值
this.inputVal = value; let tmp = /(^\d+$)/.test(v1) && value[0] != 0;
}) if (tmp && v1 >= this.min && v1 <= this.max) value = v1;
} else value = v2;
}, this.handleChange(value, 'change');
data() { this.$nextTick(() => {
return { this.inputVal = value;
inputVal: 0 // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态 })
}; }
}, },
created() { data() {
this.inputVal = +this.value; return {
}, inputVal: 0 // 输入框中的值,不能直接使用props中的value,因为应该改变props的状态
methods: { };
minus() { },
this.computeVal('minus'); created() {
}, this.inputVal = +this.value;
plus() { },
this.computeVal('plus'); methods: {
}, minus() {
// 为了保证小数相加减出现精度溢出的问题 this.computeVal('minus');
calcPlus(num1, num2) { },
let baseNum, baseNum1, baseNum2; plus() {
try { this.computeVal('plus');
baseNum1 = num1.toString().split('.')[1].length; },
} catch (e) { // 为了保证小数相加减出现精度溢出的问题
baseNum1 = 0; calcPlus(num1, num2) {
} let baseNum, baseNum1, baseNum2;
try { try {
baseNum2 = num2.toString().split('.')[1].length; baseNum1 = num1.toString().split('.')[1].length;
} catch (e) { } catch (e) {
baseNum2 = 0; baseNum1 = 0;
} }
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); try {
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度 baseNum2 = num2.toString().split('.')[1].length;
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision); } catch (e) {
}, baseNum2 = 0;
// 为了保证小数相加减出现精度溢出的问题 }
calcMinus(num1, num2) { baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
let baseNum, baseNum1, baseNum2; let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //精度
try { return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision);
baseNum1 = num1.toString().split('.')[1].length; },
} catch (e) { // 为了保证小数相加减出现精度溢出的问题
baseNum1 = 0; calcMinus(num1, num2) {
} let baseNum, baseNum1, baseNum2;
try { try {
baseNum2 = num2.toString().split('.')[1].length; baseNum1 = num1.toString().split('.')[1].length;
} catch (e) { } catch (e) {
baseNum2 = 0; baseNum1 = 0;
} }
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2)); try {
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; baseNum2 = num2.toString().split('.')[1].length;
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision); } catch (e) {
}, baseNum2 = 0;
computeVal(type) { }
uni.hideKeyboard(); baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
if (this.disabled) return; let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
let value = 0; return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
// 减 },
if (type === 'minus') { computeVal(type) {
value = this.calcMinus(this.inputVal, this.step); uni.hideKeyboard();
} else if (type === 'plus') { if (this.disabled) return;
value = this.calcPlus(this.inputVal, this.step); let value = 0;
} // 减
// 判断是否小于最小值和大于最大值 if (type === 'minus') {
if (value < this.min || value > this.max) { value = this.calcMinus(this.inputVal, this.step);
return; } else if (type === 'plus') {
} value = this.calcPlus(this.inputVal, this.step);
this.inputVal = value; }
this.handleChange(value, type); // 判断是否小于最小值和大于最大值
}, if (value < this.min || value > this.max) {
// 处理用户手动输入的情况 return;
onBlur(event) { }
let val = 0; this.inputVal = value;
let value = event.detail.value; this.handleChange(value, type);
// 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值 },
// 这里不直接判断是否正整数,是因为用户传递的props min值可能为0 // 处理用户手动输入的情况
if(!/(^\d+$)/.test(value) || value[0] == 0) val = this.min; onBlur(event) {
val = +value; let val = 0;
if (val > this.max) { let value = event.detail.value;
val = this.max; // 如果为非0-9数字组成,或者其第一位数值为0,直接让其等于min值
} else if (val < this.min) { // 这里不直接判断是否正整数,是因为用户传递的props min值可能为0
val = this.min; if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min;
} val = +value;
this.$nextTick(() => { if (val > this.max) {
this.inputVal = val; val = this.max;
}) } else if (val < this.min) {
this.handleChange(val, "blur"); val = this.min;
}, }
handleChange(value, type) { this.$nextTick(() => {
if (this.disabled) return; this.inputVal = val;
this.$emit(type, { })
value: value, this.handleChange(val, "blur");
index: this.index },
}) handleChange(value, type) {
} if (this.disabled) return;
} this.$emit(type, {
}; value: value,
</script> index: this.index
})
<style lang="scss" scoped> }
.u-numberbox { }
display: inline-flex; };
align-items: center; </script>
}
<style lang="scss" scoped>
.u-number-input { .u-numberbox {
position: relative; display: inline-flex;
text-align: center; align-items: center;
padding: 0; }
margin: 0 6rpx;
display: flex; .u-number-input {
align-items: center; position: relative;
justify-content: center; text-align: center;
} padding: 0;
margin: 0 6rpx;
.u-icon-plus, display: flex;
.u-icon-minus { align-items: center;
width: 60rpx; justify-content: center;
display: flex; }
justify-content: center;
align-items: center; .u-icon-plus,
} .u-icon-minus {
width: 60rpx;
.u-icon-plus { display: flex;
border-radius: 0 8rpx 8rpx 0; justify-content: center;
} align-items: center;
}
.u-icon-minus {
border-radius: 8rpx 0 0 8rpx; .u-icon-plus {
} border-radius: 0 8rpx 8rpx 0;
}
.u-icon-disabled {
color: #c8c9cc !important; .u-icon-minus {
background: #f7f8fa !important; border-radius: 8rpx 0 0 8rpx;
} }
.u-input-disabled { .u-icon-disabled {
color: #c8c9cc !important; color: #c8c9cc !important;
background-color: #f2f3f5 !important; background: #f7f8fa !important;
} }
.u-input-disabled {
color: #c8c9cc !important;
background-color: #f2f3f5 !important;
}
</style> </style>
<template> <template>
<u-popup :maskCloseAble="maskCloseAble" mode="bottom" <u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
:popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
:safeAreaInsetBottom="safeAreaInsetBottom" <view class="u-datetime-picker" @tap.stop>
@close="close" :z-index="uZIndex" <view class="u-picker-header" @touchmove.stop.prevent="stop" catchtouchmove="stop">
> <view class="u-btn-picker u-tips-color" :style="{ color: cancelColor }" hover-class="u-opacity" :hover-stay-time="150"
<view class="u-datetime-picker" @tap.stop> @tap="getResult('cancel')">取消</view>
<view class="u-picker-header" @touchmove.stop.prevent="stop" catchtouchmove="stop"> <view class="u-btn-picker u-type-primary" :style="{ color: confirmColor }" hover-class="u-opacity" :hover-stay-time="150"
<view class="u-btn-picker u-tips-color" :style="{ color: cancelColor }" hover-class="u-opacity" :hover-stay-time="150" @touchmove.stop="" @tap.stop="getResult('confirm')">确定</view>
@tap="getResult('cancel')">取消</view> </view>
<view class="u-btn-picker u-type-primary" :style="{ color: confirmColor }" hover-class="u-opacity" <view class="u-picker-body">
:hover-stay-time="150" @touchmove.stop="" @tap.stop="getResult('confirm')">确定</view> <picker-view v-if="mode == 'region'" :value="valueArr" @change="change" class="u-picker-view">
</view> <picker-view-column v-if="params.province">
<view class="u-picker-body"> <view class="u-column-item" v-for="(item,index) in provinces" :key="index">
<picker-view v-if="mode == 'region'" :value="valueArr" @change="change" class="u-picker-view"> <view class="u-line-1">
<picker-view-column v-if="params.province"> {{item.label}}
<view class="u-column-item" v-for="(item,index) in provinces" :key="index"> </view>
<view class="u-line-1"> </view>
{{item.label}} </picker-view-column>
</view> <picker-view-column v-if="params.city">
</view> <view class="u-column-item" v-for="(item,index) in citys" :key="index">
</picker-view-column> <view class="u-line-1">
<picker-view-column v-if="params.city"> {{item.label}}
<view class="u-column-item" v-for="(item,index) in citys" :key="index"> </view>
<view class="u-line-1"> </view>
{{item.label}} </picker-view-column>
</view> <picker-view-column v-if="params.area">
</view> <view class="u-column-item" v-for="(item,index) in areas" :key="index">
</picker-view-column> <view class="u-line-1">
<picker-view-column v-if="params.area"> {{item.label}}
<view class="u-column-item" v-for="(item,index) in areas" :key="index"> </view>
<view class="u-line-1"> </view>
{{item.label}} </picker-view-column>
</view> </picker-view>
</view> <picker-view v-else :value="valueArr" @change="change" class="u-picker-view">
</picker-view-column> <picker-view-column v-if="!reset && params.year">
</picker-view> <view class="u-column-item" v-for="(item,index) in years" :key="index">
<picker-view v-else :value="valueArr" @change="change" class="u-picker-view"> {{ item }}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.year"> </view>
<view class="u-column-item" v-for="(item,index) in years" :key="index"> </picker-view-column>
{{ item }}<text class="u-text"></text> <picker-view-column v-if="!reset && params.month">
</view> <view class="u-column-item" v-for="(item,index) in months" :key="index">
</picker-view-column> {{ formatNumber(item)}}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.month"> </view>
<view class="u-column-item" v-for="(item,index) in months" :key="index"> </picker-view-column>
{{ formatNumber(item)}}<text class="u-text"></text> <picker-view-column v-if="!reset && params.day">
</view> <view class="u-column-item" v-for="(item,index) in days" :key="index">
</picker-view-column> {{ formatNumber(item) }}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.day"> </view>
<view class="u-column-item" v-for="(item,index) in days" :key="index"> </picker-view-column>
{{ formatNumber(item) }}<text class="u-text"></text> <picker-view-column v-if="!reset && params.hour">
</view> <view class="u-column-item" v-for="(item,index) in hours" :key="index">
</picker-view-column> {{ formatNumber(item) }}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.hour"> </view>
<view class="u-column-item" v-for="(item,index) in hours" :key="index"> </picker-view-column>
{{ formatNumber(item) }}<text class="u-text"></text> <picker-view-column v-if="!reset && params.minute">
</view> <view class="u-column-item" v-for="(item,index) in minutes" :key="index">
</picker-view-column> {{ formatNumber(item) }}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.minute"> </view>
<view class="u-column-item" v-for="(item,index) in minutes" :key="index"> </picker-view-column>
{{ formatNumber(item) }}<text class="u-text"></text> <picker-view-column v-if="!reset && params.second">
</view> <view class="u-column-item" v-for="(item,index) in seconds" :key="index">
</picker-view-column> {{ formatNumber(item) }}<text class="u-text"></text>
<picker-view-column v-if="!reset && params.second"> </view>
<view class="u-column-item" v-for="(item,index) in seconds" :key="index"> </picker-view-column>
{{ formatNumber(item) }}<text class="u-text"></text> </picker-view>
</view> </view>
</picker-view-column> </view>
</picker-view> </u-popup>
</view> </template>
</view>
</u-popup> <script>
</template> /**
* alertTips 提示
<script> * @description 此选择器有两种弹出模式:一是时间模式,可以配置年,日,月,时,分,秒参数 二是地区模式,可以配置省,市,区参数
import provinces from '@/uview/libs/util/province.js'; * @tutorial https://www.uviewui.com/components/picker.html
import citys from '@/uview/libs/util/city.js'; * @property {Object} params 需要显示的参数,见官网说明
import areas from '@/uview/libs/util/area.js'; * @property {String} mode 模式选择,region-地区类型,time-时间类型(默认time)
export default { * @property {String Number} start-year 可选的开始年份,mode=time时有效(默认1950)
props: { * @property {String Number} end-year 可选的结束年份,mode=time时有效(默认2050)
// picker中需要显示的参数 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
params: { * @property {String} cancel-color 取消按钮的颜色(默认#606266)
type: Object, * @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
default () { * @property {String} default-time 默认选中的时间,mode=time时有效
return { * @property {String} default-region 默认选中的地区,中文形式,mode=region时有效
year: true, * @property {String} default-code 默认选中的地区,编号形式,mode=region时有效
month: true, * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
day: true, * @property {String Number} z-index 弹出时的z-index值(默认1075)
hour: false, * @event {Function} confirm 点击确定按钮,返回当前选择的值
minute: false, * @event {Function} cancel 点击取消按钮,返回当前选择的值
second: false, * @example <u-picker v-model="show" mode="time"></u-picker>
province: true, */
city: true,
area: true import provinces from '@/uview/libs/util/province.js';
} import citys from '@/uview/libs/util/city.js';
} import areas from '@/uview/libs/util/area.js';
}, export default {
// 模式选择,region-地区类型,time-时间类型 name: "u-picker",
mode: { props: {
type: String, // picker中需要显示的参数
default: 'time' params: {
}, type: Object,
// 年份开始时间 default () {
startYear: { return {
type: [String, Number], year: true,
default: 1950 month: true,
}, day: true,
// 年份结束时间 hour: false,
endYear: { minute: false,
type: [String, Number], second: false,
default: 2050 province: true,
}, city: true,
// "取消"按钮的颜色 area: true
cancelColor: { }
type: String, }
default: '#606266' },
}, // 模式选择,region-地区类型,time-时间类型
// "确定"按钮的颜色 mode: {
confirmColor: { type: String,
type: String, default: 'time'
default: '#2979ff' },
}, // 年份开始时间
// 默认显示的时间,2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02 startYear: {
defaultTime: { type: [String, Number],
type: String, default: 1950
default: '' },
}, // 年份结束时间
// 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"] endYear: {
defaultRegion: { type: [String, Number],
type: Array, default: 2050
default () { },
return []; // "取消"按钮的颜色
} cancelColor: {
}, type: String,
// 默认显示地区的编码,defaultRegion和areaCode同时存在,areaCode优先,可传类似["13", "1303", "130304"] default: '#606266'
areaCode: { },
type: Array, // "确定"按钮的颜色
default () { confirmColor: {
return []; type: String,
} default: '#2979ff'
}, },
safeAreaInsetBottom: { // 默认显示的时间,2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02
type: Boolean, defaultTime: {
default: false type: String,
}, default: ''
// 是否允许通过点击遮罩关闭Picker },
maskCloseAble: { // 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"]
type: Boolean, defaultRegion: {
default: true type: Array,
}, default () {
// 通过双向绑定控制组件的弹出与收起 return [];
value: { }
type: Boolean, },
default: false // 默认显示地区的编码,defaultRegion和areaCode同时存在,areaCode优先,可传类似["13", "1303", "130304"]
}, areaCode: {
// 弹出的z-index值 type: Array,
zIndex: { default () {
type: [String, Number], return [];
default: 0 }
} },
}, safeAreaInsetBottom: {
data() { type: Boolean,
return { default: false
years: [], },
months: [], // 是否允许通过点击遮罩关闭Picker
days: [], maskCloseAble: {
hours: [], type: Boolean,
minutes: [], default: true
seconds: [], },
year: 0, // 通过双向绑定控制组件的弹出与收起
month: 0, value: {
day: 0, type: Boolean,
hour: 0, default: false
minute: 0, },
second: 0, // 弹出的z-index值
startDate: "", zIndex: {
endDate: "", type: [String, Number],
valueArr: [], default: 0
reset: false, }
provinces: provinces, },
citys: citys[0], data() {
areas: areas[0][0], return {
province: 0, years: [],
city: 0, months: [],
area: 0 days: [],
} hours: [],
}, minutes: [],
mounted() { seconds: [],
this.init(); year: 0,
}, month: 0,
computed: { day: 0,
propsChange() { hour: 0,
// 引用这几个变量,是为了监听其变化 minute: 0,
return `${this.mode}-${this.defaultTime}-${this.startYear}-${this.endYear}-${this.defaultRegion}-${this.defaultCode}`; second: 0,
}, startDate: "",
regionChange() { endDate: "",
// 引用这几个变量,是为了监听其变化 valueArr: [],
return `${this.province}-${this.city}`; reset: false,
}, provinces: provinces,
yearAndMonth() { citys: citys[0],
return `${this.year}-${this.month}`; areas: areas[0][0],
}, province: 0,
uZIndex() { city: 0,
// 如果用户有传递z-index值,优先使用 area: 0
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; }
} },
}, mounted() {
watch: { this.init();
propsChange() { },
this.reset = true; computed: {
setTimeout(() => this.init(), 10); propsChange() {
}, // 引用这几个变量,是为了监听其变化
// 如果地区发生变化,为了让picker联动起来,必须重置this.citys和this.areas return `${this.mode}-${this.defaultTime}-${this.startYear}-${this.endYear}-${this.defaultRegion}-${this.defaultCode}`;
regionChange(val) { },
this.citys = citys[this.province]; regionChange() {
this.areas = areas[this.province][this.city]; // 引用这几个变量,是为了监听其变化
}, return `${this.province}-${this.city}`;
// watch监听月份的变化,实时变更日的天数,因为不同月份,天数不一样 },
// 一个月可能有30,31天,甚至闰年2月的29天,平年2月28天 yearAndMonth() {
yearAndMonth(val) { return `${this.year}-${this.month}`;
if(this.params.year) this.setDays(); },
}, uZIndex() {
// 微信和QQ小程序由于一些奇怪的原因,需要重新初始化才能显示正确的值 // 如果用户有传递z-index值,优先使用
value(n) { return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
if(n) { }
// #ifdef MP-WEIXIN || MP-QQ },
this.reset = true; watch: {
setTimeout(() => this.init(), 10); propsChange() {
// #endif this.reset = true;
} setTimeout(() => this.init(), 10);
} },
}, // 如果地区发生变化,为了让picker联动起来,必须重置this.citys和this.areas
methods: { regionChange(val) {
// 小于10前面补0,用于月份,日期,时分秒等 this.citys = citys[this.province];
formatNumber(num) { this.areas = areas[this.province][this.city];
return +num < 10 ? '0' + num : String(num); },
}, // watch监听月份的变化,实时变更日的天数,因为不同月份,天数不一样
// 生成递进的数组 // 一个月可能有30,31天,甚至闰年2月的29天,平年2月28天
generateArray: function(start, end) { yearAndMonth(val) {
end = end > start ? end : start; if (this.params.year) this.setDays();
// 生成数组,获取其中的索引,并剪出来 },
return [...Array(end + 1).keys()].slice(start); // 微信和QQ小程序由于一些奇怪的原因,需要重新初始化才能显示正确的值
}, value(n) {
getIndex: function(arr, val) { if (n) {
let index = arr.indexOf(val); // #ifdef MP-WEIXIN || MP-QQ
// 如果index为-1(即找不到index值),~(-1)=-(-1)-1=0,导致条件不成立 this.reset = true;
return ~index ? index : 0; setTimeout(() => this.init(), 10);
}, // #endif
//日期时间处理 }
initTimeValue() { }
// 格式化时间,在IE浏览器(uni不存在此情况),无法识别日期间的"-"间隔符号 },
let fdate = this.defaultTime.replace(/\-/g, '/'); methods: {
fdate = fdate && fdate.indexOf("/") == -1 ? `2020/01/01 ${fdate}` : fdate // 小于10前面补0,用于月份,日期,时分秒等
let time = null; formatNumber(num) {
if (fdate) return +num < 10 ? '0' + num : String(num);
time = new Date(fdate); },
else // 生成递进的数组
time = new Date(); generateArray: function(start, end) {
// 获取年日月时分秒 end = end > start ? end : start;
this.year = time.getFullYear() // 生成数组,获取其中的索引,并剪出来
this.month = Number(time.getMonth()) + 1; return [...Array(end + 1).keys()].slice(start);
this.day = time.getDate(); },
this.hour = time.getHours(); getIndex: function(arr, val) {
this.minute = time.getMinutes(); let index = arr.indexOf(val);
this.second = time.getSeconds(); // 如果index为-1(即找不到index值),~(-1)=-(-1)-1=0,导致条件不成立
}, return ~index ? index : 0;
init() { },
this.valueArr = []; //日期时间处理
this.reset = false; initTimeValue() {
if (this.mode == 'time') { // 格式化时间,在IE浏览器(uni不存在此情况),无法识别日期间的"-"间隔符号
this.initTimeValue(); let fdate = this.defaultTime.replace(/\-/g, '/');
if (this.params.year) { fdate = fdate && fdate.indexOf("/") == -1 ? `2020/01/01 ${fdate}` : fdate
this.valueArr.push(0); let time = null;
this.setYears(); if (fdate)
} time = new Date(fdate);
if (this.params.month) { else
this.valueArr.push(0); time = new Date();
this.setMonths(); // 获取年日月时分秒
} this.year = time.getFullYear()
if (this.params.day) { this.month = Number(time.getMonth()) + 1;
this.valueArr.push(0); this.day = time.getDate();
this.setDays(); this.hour = time.getHours();
} this.minute = time.getMinutes();
if (this.params.hour) { this.second = time.getSeconds();
this.valueArr.push(0); },
this.setHours(); init() {
} this.valueArr = [];
if (this.params.minute) { this.reset = false;
this.valueArr.push(0); if (this.mode == 'time') {
this.setMinutes(); this.initTimeValue();
} if (this.params.year) {
if (this.params.second) { this.valueArr.push(0);
this.valueArr.push(0); this.setYears();
this.setSeconds(); }
} if (this.params.month) {
} else { this.valueArr.push(0);
if (this.params.province) { this.setMonths();
this.valueArr.push(0); }
this.setProvinces(); if (this.params.day) {
} this.valueArr.push(0);
if (this.params.city) { this.setDays();
this.valueArr.push(0); }
this.setCitys(); if (this.params.hour) {
} this.valueArr.push(0);
if (this.params.area) { this.setHours();
this.valueArr.push(0); }
this.setAreas(); if (this.params.minute) {
} this.valueArr.push(0);
} this.setMinutes();
this.$forceUpdate(); }
}, if (this.params.second) {
// 设置picker的某一列值 this.valueArr.push(0);
setYears() { this.setSeconds();
// 获取年份集合 }
this.years = this.generateArray(this.startYear, this.endYear); } else {
// 设置this.valueArr某一项的值,是为了让picker预选中某一个值 if (this.params.province) {
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.years, this.year)); this.valueArr.push(0);
}, this.setProvinces();
setMonths() { }
this.months = this.generateArray(1, 12); if (this.params.city) {
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.months, this.month)); this.valueArr.push(0);
}, this.setCitys();
setDays() { }
let totalDays = new Date(this.year, this.month, 0).getDate(); if (this.params.area) {
this.days = this.generateArray(1, totalDays); this.valueArr.push(0);
let index = 0; this.setAreas();
// 这里不能使用类似setMonths()中的this.valueArr.splice(this.valueArr.length - 1, xxx)做法 }
// 因为this.month和this.year变化时,会触发watch中的this.setDays(),导致this.valueArr.length计算有误 }
if(this.params.year && this.params.month) index = 2; this.$forceUpdate();
else if(this.params.month) index = 1; },
else if(this.params.year) index = 1; // 设置picker的某一列值
else index = 0; setYears() {
this.valueArr.splice(index, 1, this.getIndex(this.days, this.day)); // 获取年份集合
}, this.years = this.generateArray(this.startYear, this.endYear);
setHours() { // 设置this.valueArr某一项的值,是为了让picker预选中某一个值
this.hours = this.generateArray(0, 23); this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.years, this.year));
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour)); },
}, setMonths() {
setMinutes() { this.months = this.generateArray(1, 12);
this.minutes = this.generateArray(0, 59); this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.months, this.month));
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute)); },
}, setDays() {
setSeconds() { let totalDays = new Date(this.year, this.month, 0).getDate();
this.seconds = this.generateArray(0, 59); this.days = this.generateArray(1, totalDays);
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second)); let index = 0;
}, // 这里不能使用类似setMonths()中的this.valueArr.splice(this.valueArr.length - 1, xxx)做法
setProvinces() { // 因为this.month和this.year变化时,会触发watch中的this.setDays(),导致this.valueArr.length计算有误
// 判断是否需要province参数 if (this.params.year && this.params.month) index = 2;
if (!this.params.province) return; else if (this.params.month) index = 1;
let tmp = ''; else if (this.params.year) index = 1;
let useCode = false; else index = 0;
// 如果同时配置了defaultRegion和areaCode,优先使用areaCode参数 this.valueArr.splice(index, 1, this.getIndex(this.days, this.day));
if (this.areaCode.length) { },
tmp = this.areaCode[0]; setHours() {
useCode = true; this.hours = this.generateArray(0, 23);
} else if (this.defaultRegion.length) tmp = this.defaultRegion[0]; this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour));
else tmp = 0; },
// 历遍省份数组匹配 setMinutes() {
provinces.map((v, k) => { this.minutes = this.generateArray(0, 59);
if (useCode ? v.value == tmp : v.label == tmp) { this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute));
tmp = k; },
} setSeconds() {
}) this.seconds = this.generateArray(0, 59);
this.province = tmp; this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second));
this.provinces = provinces; },
// 设置默认省份的值 setProvinces() {
this.valueArr.splice(0, 1, this.province); // 判断是否需要province参数
}, if (!this.params.province) return;
setCitys() { let tmp = '';
if (!this.params.city) return; let useCode = false;
let tmp = ''; // 如果同时配置了defaultRegion和areaCode,优先使用areaCode参数
let useCode = false; if (this.areaCode.length) {
if (this.areaCode.length) { tmp = this.areaCode[0];
tmp = this.areaCode[1]; useCode = true;
useCode = true; } else if (this.defaultRegion.length) tmp = this.defaultRegion[0];
} else if (this.defaultRegion.length) tmp = this.defaultRegion[1]; else tmp = 0;
else tmp = 0; // 历遍省份数组匹配
citys[this.province].map((v, k) => { provinces.map((v, k) => {
if (useCode ? v.value == tmp : v.label == tmp) { if (useCode ? v.value == tmp : v.label == tmp) {
tmp = k; tmp = k;
} }
}) })
this.city = tmp; this.province = tmp;
this.citys = citys[this.province]; this.provinces = provinces;
this.valueArr.splice(1, 1, this.city); // 设置默认省份的值
}, this.valueArr.splice(0, 1, this.province);
setAreas() { },
if (!this.params.area) return; setCitys() {
let tmp = ''; if (!this.params.city) return;
let useCode = false; let tmp = '';
if (this.areaCode.length) { let useCode = false;
tmp = this.areaCode[2]; if (this.areaCode.length) {
useCode = true; tmp = this.areaCode[1];
} else if (this.defaultRegion.length) tmp = this.defaultRegion[2]; useCode = true;
else tmp = 0; } else if (this.defaultRegion.length) tmp = this.defaultRegion[1];
areas[this.province][this.city].map((v, k) => { else tmp = 0;
if (useCode ? v.value == tmp : v.label == tmp) { citys[this.province].map((v, k) => {
tmp = k; if (useCode ? v.value == tmp : v.label == tmp) {
} tmp = k;
}) }
this.area = tmp; })
this.areas = areas[this.province][this.city]; this.city = tmp;
this.valueArr.splice(2, 1, this.area); this.citys = citys[this.province];
}, this.valueArr.splice(1, 1, this.city);
close() { },
this.$emit('input', false); setAreas() {
}, if (!this.params.area) return;
// 用户更改picker的列选项 let tmp = '';
change(e) { let useCode = false;
this.valueArr = e.detail.value; if (this.areaCode.length) {
let i = 0; tmp = this.areaCode[2];
if (this.mode == 'time') { useCode = true;
// 这里使用i++,是因为this.valueArr数组的长度是不确定长度的,它根据this.params的值来配置长度 } else if (this.defaultRegion.length) tmp = this.defaultRegion[2];
// 进入if规则,i会加1,保证了能获取准确的值 else tmp = 0;
if (this.params.year) this.year = this.years[this.valueArr[i++]]; areas[this.province][this.city].map((v, k) => {
if (this.params.month) this.month = this.months[this.valueArr[i++]]; if (useCode ? v.value == tmp : v.label == tmp) {
if (this.params.day) this.day = this.days[this.valueArr[i++]]; tmp = k;
if (this.params.hour) this.hour = this.hours[this.valueArr[i++]]; }
if (this.params.minute) this.minute = this.minutes[this.valueArr[i++]]; })
if (this.params.second) this.second = this.seconds[this.valueArr[i++]]; this.area = tmp;
} else { this.areas = areas[this.province][this.city];
if (this.params.province) this.province = this.valueArr[i++]; this.valueArr.splice(2, 1, this.area);
if (this.params.city) this.city = this.valueArr[i++]; },
if (this.params.area) this.area = this.valueArr[i++]; close() {
} this.$emit('input', false);
}, },
// 用户点击确定按钮 // 用户更改picker的列选项
getResult(event = null) { change(e) {
let result = {}; this.valueArr = e.detail.value;
// 只返回用户在this.params中配置了为true的字段 let i = 0;
if (this.mode == 'time') { if (this.mode == 'time') {
if (this.params.year) result.year = this.formatNumber(this.year || 0);; // 这里使用i++,是因为this.valueArr数组的长度是不确定长度的,它根据this.params的值来配置长度
if (this.params.month) result.month = this.formatNumber(this.month || 0); // 进入if规则,i会加1,保证了能获取准确的值
if (this.params.day) result.day = this.formatNumber(this.day || 0); if (this.params.year) this.year = this.years[this.valueArr[i++]];
if (this.params.hour) result.hour = this.formatNumber(this.hour || 0); if (this.params.month) this.month = this.months[this.valueArr[i++]];
if (this.params.minute) result.minute = this.formatNumber(this.minute || 0); if (this.params.day) this.day = this.days[this.valueArr[i++]];
if (this.params.second) result.second = this.formatNumber(this.second || 0); if (this.params.hour) this.hour = this.hours[this.valueArr[i++]];
} else { if (this.params.minute) this.minute = this.minutes[this.valueArr[i++]];
if (this.params.province) result.province = provinces[this.province]; if (this.params.second) this.second = this.seconds[this.valueArr[i++]];
if (this.params.city) result.city = citys[this.province][this.city]; } else {
if (this.params.area) result.area = areas[this.province][this.city][this.area]; if (this.params.province) this.province = this.valueArr[i++];
} if (this.params.city) this.city = this.valueArr[i++];
if(event) this.$emit(event, result); if (this.params.area) this.area = this.valueArr[i++];
this.close(); }
} },
} // 用户点击确定按钮
} getResult(event = null) {
</script> let result = {};
// 只返回用户在this.params中配置了为true的字段
<style lang="scss" scoped> if (this.mode == 'time') {
.u-datetime-picker { if (this.params.year) result.year = this.formatNumber(this.year || 0);;
position: relative; if (this.params.month) result.month = this.formatNumber(this.month || 0);
z-index: 999; if (this.params.day) result.day = this.formatNumber(this.day || 0);
} if (this.params.hour) result.hour = this.formatNumber(this.hour || 0);
if (this.params.minute) result.minute = this.formatNumber(this.minute || 0);
.u-picker-view { if (this.params.second) result.second = this.formatNumber(this.second || 0);
height: 100%; } else {
box-sizing: border-box; if (this.params.province) result.province = provinces[this.province];
} if (this.params.city) result.city = citys[this.province][this.city];
if (this.params.area) result.area = areas[this.province][this.city][this.area];
.u-picker-header { }
width: 100%; if (event) this.$emit(event, result);
height: 90rpx; this.close();
padding: 0 40rpx; }
display: flex; }
justify-content: space-between; }
align-items: center; </script>
box-sizing: border-box;
font-size: 32rpx; <style lang="scss" scoped>
background: #fff; .u-datetime-picker {
position: relative; position: relative;
} z-index: 999;
}
.u-picker-header::after {
content: ''; .u-picker-view {
position: absolute; height: 100%;
border-bottom: 1rpx solid #eaeef1; box-sizing: border-box;
-webkit-transform: scaleY(0.5); }
transform: scaleY(0.5);
bottom: 0; .u-picker-header {
right: 0; width: 100%;
left: 0; height: 90rpx;
} padding: 0 40rpx;
display: flex;
.u-picker-body { justify-content: space-between;
width: 100%; align-items: center;
height: 500rpx; box-sizing: border-box;
overflow: hidden; font-size: 32rpx;
background-color: #fff; background: #fff;
} position: relative;
}
.u-column-item {
display: flex; .u-picker-header::after {
align-items: center; content: '';
justify-content: center; position: absolute;
font-size: 32rpx; border-bottom: 1rpx solid #eaeef1;
color: $u-main-color; -webkit-transform: scaleY(0.5);
padding: 0 8rpx; transform: scaleY(0.5);
} bottom: 0;
right: 0;
.u-text { left: 0;
font-size: 24rpx; }
padding-left: 8rpx;
} .u-picker-body {
width: 100%;
.u-btn-picker { height: 500rpx;
padding: 16rpx; overflow: hidden;
box-sizing: border-box; background-color: #fff;
text-align: center; }
text-decoration: none;
} .u-column-item {
display: flex;
.u-opacity { align-items: center;
opacity: 0.5; justify-content: center;
} font-size: 32rpx;
color: $u-main-color;
padding: 0 8rpx;
}
.u-text {
font-size: 24rpx;
padding-left: 8rpx;
}
.u-btn-picker {
padding: 16rpx;
box-sizing: border-box;
text-align: center;
text-decoration: none;
}
.u-opacity {
opacity: 0.5;
}
</style> </style>
<template> <template>
<view v-if="visibleSync" :style="[customStyle]" :class="{ 'u-drawer-visible': showDrawer }" class="u-drawer"> <view v-if="visibleSync" :style="[customStyle]" :class="{ 'u-drawer-visible': showDrawer }" class="u-drawer">
<u-mask :maskClickAble="maskCloseAble" :show="showDrawer && mask" @click="maskClick"></u-mask> <u-mask :maskClickAble="maskCloseAble" :show="showDrawer && mask" @click="maskClick"></u-mask>
<view <view class="u-drawer-content" @tap="modeCenterClose(mode)" :class="[
class="u-drawer-content" safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
@tap="modeCenterClose(mode)" 'u-drawer-' + mode,
:class="[ showDrawer ? 'u-drawer-content-visible' : '',
safeAreaInsetBottom ? 'safe-area-inset-bottom' : '', zoom && mode == 'center' ? 'u-animation-zoom' : ''
'u-drawer-' + mode, ]"
showDrawer ? 'u-drawer-content-visible' : '', @touchmove.stop.prevent :style="[style]">
zoom && mode == 'center' ? 'u-animation-zoom' : '' <view class="u-mode-center-box" @touchmove.stop.prevent v-if="mode == 'center'" :style="[centerStyle]">
]" <slot />
@touchmove.stop.prevent </view>
:style="[style]" <block v-else>
> <slot />
<view class="u-mode-center-box" @touchmove.stop.prevent v-if="mode == 'center'" :style="[centerStyle]"><slot /></view> </block>
<block v-else><slot /></block> </view>
</view> </view>
</view> </template>
</template>
<script>
<script> /**
export default { * alertTips 提示
name: 'uDrawer', * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
props: { * @tutorial https://www.uviewui.com/components/popup.html
/** * @property {String} mode 弹出方向(默认left)
* 显示状态 * @property {Boolean} mask 是否显示遮罩(默认true)
*/ * @property {String Number} length mode=left | 见官网说明(默认auto)
show: { * @property {Boolean} zoom 是否开启缩放动画,只在mode为center时有效(默认true)
type: Boolean, * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
default: false * @property {Boolean} mask-close-able 点击遮罩是否可以关闭弹出层(默认true)
}, * @property {Object} custom-style 用户自定义样式
/** * @property {Number String} border-radius 弹窗圆角值(默认0)
* 弹出方向,left|right|top|bottom|center * @property {Number String} z-index 弹出内容的z-index值(默认1075)
*/ * @event {Function} open 弹出层打开
mode: { * @event {Function} close 弹出层收起
type: String, * @example <u-popup v-model="show"><view>出淤泥而不染,濯清涟而不妖</view></u-popup>
default: 'left' */
}, export default {
/** name: 'u-popup',
* 是否显示遮罩 props: {
*/ /**
mask: { * 显示状态
type: Boolean, */
default: true show: {
}, type: Boolean,
// 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto" default: false
// 或者百分比"50%",表示由内容撑开高度或者宽度 },
length: { /**
type: [Number, String], * 弹出方向,left|right|top|bottom|center
default: 'auto' */
}, mode: {
// 是否开启缩放动画,只在mode=center时有效 type: String,
zoom: { default: 'left'
type: Boolean, },
default: true /**
}, * 是否显示遮罩
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 */
safeAreaInsetBottom: { mask: {
type: Boolean, type: Boolean,
default: false default: true
}, },
// 是否可以通过点击遮罩进行关闭 // 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto"
maskCloseAble: { // 或者百分比"50%",表示由内容撑开高度或者宽度
type: Boolean, length: {
default: true type: [Number, String],
}, default: 'auto'
// 用户自定义样式 },
customStyle: { // 是否开启缩放动画,只在mode=center时有效
type: Object, zoom: {
default() { type: Boolean,
return {}; default: true
} },
}, // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
value: { safeAreaInsetBottom: {
type: Boolean, type: Boolean,
default: false default: false
}, },
// 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件 // 是否可以通过点击遮罩进行关闭
// 对v-model双向绑定多层调用造成报错不能修改props值的问题 maskCloseAble: {
popup: { type: Boolean,
type: Boolean, default: true
default: true },
}, // 用户自定义样式
// 显示显示弹窗的圆角,单位rpx customStyle: {
borderRadius: { type: Object,
type: [Number, String], default () {
default: 0 return {};
}, }
zIndex: { },
type: [Number, String], value: {
default: '' type: Boolean,
} default: false
}, },
data() { // 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
return { // 对v-model双向绑定多层调用造成报错不能修改props值的问题
visibleSync: false, popup: {
showDrawer: false, type: Boolean,
timer: null, default: true
style1: {} },
}; // 显示显示弹窗的圆角,单位rpx
}, borderRadius: {
computed: { type: [Number, String],
// 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom) default: 0
style() { },
let style = {}; zIndex: {
let translate = '100%'; type: [Number, String],
// 判断是否是否百分比或者auto值,是的话,直接使用该值,否则默认为rpx单位的数值 default: ''
let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px'; }
// 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏 },
if (this.mode == 'left' || this.mode == 'top') translate = length == 'auto' ? '-100%' : '-' + length; data() {
if (this.mode == 'left' || this.mode == 'right') { return {
style = { visibleSync: false,
width: length, showDrawer: false,
height: '100%', timer: null,
transform: `translate3D(${translate},0px,0px)` style1: {}
}; };
} else if (this.mode == 'top' || this.mode == 'bottom') { },
style = { computed: {
width: '100%', // 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
height: length, style() {
transform: `translate3D(0px,${translate},0px)` let style = {};
}; let translate = '100%';
} // 判断是否是否百分比或者auto值,是的话,直接使用该值,否则默认为rpx单位的数值
style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.popup; let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
// 如果用户设置了borderRadius值,添加弹窗的圆角 // 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
if (this.borderRadius) { if (this.mode == 'left' || this.mode == 'top') translate = length == 'auto' ? '-100%' : '-' + length;
switch (this.mode) { if (this.mode == 'left' || this.mode == 'right') {
case 'left': style = {
style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`; width: length,
break; height: '100%',
case 'top': transform: `translate3D(${translate},0px,0px)`
style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`; };
break; } else if (this.mode == 'top' || this.mode == 'bottom') {
case 'right': style = {
style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`; width: '100%',
break; height: length,
case 'bottom': transform: `translate3D(0px,${translate},0px)`
style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`; };
break; }
default: ; style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.popup;
} // 如果用户设置了borderRadius值,添加弹窗的圆角
// 不加可能圆角无效 if (this.borderRadius) {
style.overflow = 'hidden'; switch (this.mode) {
} case 'left':
return style; style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
}, break;
// 中部弹窗的特有样式 case 'top':
centerStyle() { style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
let style = {}; break;
let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px'; case 'right':
style.width = length; style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.popup; break;
if(this.borderRadius) { case 'bottom':
style.borderRadius = `${this.borderRadius}rpx`; style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
// 不加可能圆角无效 break;
style.overflow = 'hidden'; default:
} ;
return style; }
} // 不加可能圆角无效
}, style.overflow = 'hidden';
watch: { }
value(val) { return style;
if (val) { },
this.open(); // 中部弹窗的特有样式
} else { centerStyle() {
this.close(); let style = {};
} let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
} style.width = length;
}, style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.popup;
created() { if (this.borderRadius) {
// 先让弹窗组件渲染,再改变遮罩和抽屉元素的样式,让其动画其起作用(必须要有延时,才会有效果) style.borderRadius = `${this.borderRadius}rpx`;
this.visibleSync = this.value; // 不加可能圆角无效
setTimeout(() => { style.overflow = 'hidden';
this.showDrawer = this.value; }
}, 30); return style;
}, }
methods: { },
// 遮罩被点击 watch: {
maskClick() { value(val) {
this.close(); if (val) {
}, this.open();
close() { } else {
this.change('showDrawer', 'visibleSync', false); this.close();
}, }
// 中部弹出时,需要.u-drawer-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗 }
// 让其只在mode=center时起作用 },
modeCenterClose(mode) { created() {
if (mode != 'center' || !this.maskCloseAble) return; // 先让弹窗组件渲染,再改变遮罩和抽屉元素的样式,让其动画其起作用(必须要有延时,才会有效果)
this.close(); this.visibleSync = this.value;
}, setTimeout(() => {
open() { this.showDrawer = this.value;
this.change('visibleSync', 'showDrawer', true); }, 30);
}, },
// 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件 methods: {
// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用 // 遮罩被点击
change(param1, param2, status) { maskClick() {
// 如果this.popup为false,以为着为picker,actionsheet等组件调用了popup组件 this.close();
if (this.popup == true) this.$emit('input', status); },
this[param1] = status; close() {
if (this.timer) { this.change('showDrawer', 'visibleSync', false);
clearTimeout(this.timer); },
} // 中部弹出时,需要.u-drawer-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗
this.timer = setTimeout( // 让其只在mode=center时起作用
() => { modeCenterClose(mode) {
this[param2] = status; if (mode != 'center' || !this.maskCloseAble) return;
this.$emit(status ? 'open' : 'close'); this.close();
}, },
status ? 30 : 300 open() {
); this.change('visibleSync', 'showDrawer', true);
} },
} // 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
}; // 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
</script> change(param1, param2, status) {
// 如果this.popup为false,以为着为picker,actionsheet等组件调用了popup组件
<style scoped lang="scss"> if (this.popup == true) this.$emit('input', status);
.u-drawer { this[param1] = status;
/* #ifndef APP-NVUE */ if (this.timer) {
display: block; clearTimeout(this.timer);
/* #endif */ }
position: fixed; this.timer = setTimeout(
top: 0; () => {
left: 0; this[param2] = status;
right: 0; this.$emit(status ? 'open' : 'close');
bottom: 0; },
overflow: hidden; status ? 30 : 300
z-index: 999; );
} }
}
.u-drawer-content { };
/* #ifndef APP-NVUE */ </script>
display: block;
/* #endif */ <style scoped lang="scss">
position: absolute; .u-drawer {
z-index: 1003; /* #ifndef APP-NVUE */
transition: all 0.25s linear; display: block;
} /* #endif */
position: fixed;
.u-drawer-left { top: 0;
top: 0; left: 0;
bottom: 0; right: 0;
left: 0; bottom: 0;
background-color: #ffffff; overflow: hidden;
} z-index: 999;
}
.u-drawer-right {
right: 0; .u-drawer-content {
top: 0; /* #ifndef APP-NVUE */
bottom: 0; display: block;
background-color: #ffffff; /* #endif */
} position: absolute;
z-index: 1003;
.u-drawer-top { transition: all 0.25s linear;
top: 0; }
left: 0;
right: 0; .u-drawer-left {
background-color: #ffffff; top: 0;
} bottom: 0;
left: 0;
.u-drawer-bottom { background-color: #ffffff;
bottom: 0; }
left: 0;
right: 0; .u-drawer-right {
background-color: #ffffff; right: 0;
} top: 0;
bottom: 0;
.u-drawer-center { background-color: #ffffff;
/* #ifndef APP-NVUE */ }
display: flex;
flex-direction: column; .u-drawer-top {
/* #endif */ top: 0;
bottom: 0; left: 0;
left: 0; right: 0;
right: 0; background-color: #ffffff;
top: 0; }
justify-content: center;
align-items: center; .u-drawer-bottom {
opacity: 0; bottom: 0;
z-index: 99999; left: 0;
} right: 0;
background-color: #ffffff;
.u-mode-center-box { }
min-width: 100rpx;
min-height: 100rpx; .u-drawer-center {
/* #ifndef APP-NVUE */ /* #ifndef APP-NVUE */
display: block; display: flex;
/* #endif */ flex-direction: column;
position: relative; /* #endif */
background-color: #ffffff; bottom: 0;
} left: 0;
right: 0;
.u-drawer-content-visible.u-drawer-center { top: 0;
transform: scale(1); justify-content: center;
opacity: 1; align-items: center;
} opacity: 0;
z-index: 99999;
.u-animation-zoom { }
transform: scale(1.15);
} .u-mode-center-box {
min-width: 100rpx;
.u-drawer-content-visible { min-height: 100rpx;
transform: translate3D(0px, 0px, 0px) !important; /* #ifndef APP-NVUE */
} display: block;
/* #endif */
.u-drawer-mask { position: relative;
/* #ifndef APP-NVUE */ background-color: #ffffff;
display: block; }
/* #endif */
opacity: 0; .u-drawer-content-visible.u-drawer-center {
position: absolute; transform: scale(1);
top: 0; opacity: 1;
left: 0; }
bottom: 0;
right: 0; .u-animation-zoom {
background-color: rgba(0, 0, 0, 0.4); transform: scale(1.15);
transition: opacity 0.25s; }
}
.u-drawer-content-visible {
.u-drawer-mask-visible { transform: translate3D(0px, 0px, 0px) !important;
/* #ifndef APP-NVUE */ }
display: block;
/* #endif */ .u-drawer-mask {
opacity: 1; /* #ifndef APP-NVUE */
} display: block;
/* #endif */
opacity: 0;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color: rgba(0, 0, 0, 0.4);
transition: opacity 0.25s;
}
.u-drawer-mask-visible {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
opacity: 1;
}
</style> </style>
<template> <template>
<view class="u-radio-group"> <view class="u-radio-group">
<slot></slot> <slot></slot>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 是否禁用所有单选框 * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配<u-radio>使用
disabled: { * @tutorial https://www.uviewui.com/components/radio.html
type: Boolean, * @property {Boolean} disabled 是否禁用所有radio(默认false)
default: false * @property {String} active-color 选中时的颜色,应用到所有子Radio组件(默认#2979ff)
}, * @event {Function} change 任一个radio状态发生变化时触发
// 匹配某一个radio组件,如果某个radio的name值等于此值,那么这个radio就被会选中 * @example <u-radio-group v-model="value"></u-radio-group>
value: { */
type: [String, Number], export default {
default: '' name: "u-radio-group",
}, props: {
// 选中状态下的颜色 // 是否禁用所有单选框
activeColor: { disabled: {
type: String, type: Boolean,
default: '#2979ff' default: false
}, },
// 组件的整体大小 // 匹配某一个radio组件,如果某个radio的name值等于此值,那么这个radio就被会选中
size: { value: {
type: [String, Number], type: [String, Number],
default: 40 default: ''
} },
}, // 选中状态下的颜色
provide() { activeColor: {
return { type: String,
radioGroup: this default: '#2979ff'
} },
}, // 组件的整体大小
methods: { size: {
// 该方法有子组件radio调用,当一个radio被选中的时候,给父组件设置value值(props传递的value) type: [String, Number],
setValue(val) { default: 40
// 通过emit事件,设置父组件通过v-model双向绑定的值 }
this.$emit('input', val); },
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 provide() {
this.$nextTick(function(){ return {
this.$emit('change', this.value); radioGroup: this
}); }
} },
} methods: {
} // 该方法有子组件radio调用,当一个radio被选中的时候,给父组件设置value值(props传递的value)
</script> setValue(val) {
// 通过emit事件,设置父组件通过v-model双向绑定的值
<style lang="scss" scoped> this.$emit('input', val);
.u-radio-group { // 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
display: inline-flex; this.$nextTick(function() {
} this.$emit('change', this.value);
</style> });
\ No newline at end of file }
}
}
</script>
<style lang="scss" scoped>
.u-radio-group {
display: inline-flex;
}
</style>
<template> <template>
<view class="u-radio"> <view class="u-radio">
<view class="u-radio__icon-wrap" @tap="toggle"> <view class="u-radio__icon-wrap" @tap="toggle">
<u-icon :class="iconClass" name="checkbox-mark" :size="iconSize" :color="iconColor" class="u-radio__icon" :style="[iconStyle]" /> <u-icon :class="iconClass" name="checkbox-mark" :size="iconSize" :color="iconColor" class="u-radio__icon" :style="[iconStyle]" />
</view> </view>
<view class="u-label-class u-radio__label" @tap="onClickLabel"><slot /></view> <view class="u-label-class u-radio__label" @tap="onClickLabel">
</view> <slot />
</template> </view>
</view>
<script> </template>
export default {
props: { <script>
// radio的名称 /**
name: { * alertTips 提示
type: [String, Number], * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配<u-radio-group>使用
default: '' * @tutorial https://www.uviewui.com/components/radio.html
}, * @property {String Number} icon-size 图标大小,单位rpx(默认24)
// 形状,square为方形,circle为原型 * @property {String Number} size 组件整体的大小,单位rpx(默认40)
shape: { * @property {String Number} name radio组件的标示符
type: String, * @property {String} shape 形状,见上方说明(默认circle)
default: 'square' * @property {Boolean} disabled 是否禁用(默认false)
}, * @property {Boolean} label-disabled 点击文本是否可以操作radio(默认true)
// 是否禁用 * @property {String} active-color 选中时的颜色,如设置radioGroup的active-color将失效
disabled: { * @event {Function} change 某个radio状态发生变化时触发(选中状态)
type: Boolean, * @example <u-radio :label-disabled="false">门掩黄昏,无计留春住</u-radio>
default: false */
}, export default {
// 是否禁止点击提示语选中复选框 name: "u-radio",
labelDisabled: { props: {
type: Boolean, // radio的名称
default: false name: {
}, type: [String, Number],
// 选中状态下的颜色,如设置此值,将会覆盖radioGroup的activeColor值 default: ''
activeColor: { },
type: String, // 形状,square为方形,circle为原型
default: '' shape: {
}, type: String,
// 图标的大小,单位rpx default: 'square'
iconSize: { },
type: [String, Number], // 是否禁用
default: 24 disabled: {
} type: Boolean,
}, default: false
inject: ['radioGroup'], },
data() { // 是否禁止点击提示语选中复选框
return { labelDisabled: {
parentDisabled: false type: Boolean,
}; default: false
}, },
created() { // 选中状态下的颜色,如设置此值,将会覆盖radioGroup的activeColor值
this.parentDisabled = this.radioGroup.disabled; activeColor: {
}, type: String,
computed: { default: ''
// 设置radio的状态,要求radio的name等于radioGroup的value时才为选中状态 },
iconStyle() { // 图标的大小,单位rpx
let style = {}; iconSize: {
if (this.radioActiveColor && this.name == this.radioGroup.value && !this.disabled && !this.parentDisabled) { type: [String, Number],
style.borderColor = this.radioActiveColor; default: 24
style.backgroundColor = this.radioActiveColor; }
} },
style.width = this.radioGroup.size + 'rpx'; inject: ['radioGroup'],
style.height = this.radioGroup.size + 'rpx'; data() {
return style; return {
}, parentDisabled: false
iconColor() { };
return this.name == this.radioGroup.value ? '#ffffff' : 'transparent'; },
}, created() {
iconClass() { this.parentDisabled = this.radioGroup.disabled;
let classs = []; },
classs.push('u-radio__icon--' + this.shape); computed: {
if(this.name == this.radioGroup.value) classs.push('u-radio__icon--checked'); // 设置radio的状态,要求radio的name等于radioGroup的value时才为选中状态
if(this.disabled || this.parentDisabled) classs.push('u-radio__icon--disabled'); iconStyle() {
if(this.name == this.radioGroup.value && (this.disabled || this.parentDisabled)) classs.push('u-radio__icon--disabled--checked'); let style = {};
return classs; if (this.radioActiveColor && this.name == this.radioGroup.value && !this.disabled && !this.parentDisabled) {
}, style.borderColor = this.radioActiveColor;
// 激活的颜色,可能受radioGroup和本组件的activeColor影响 style.backgroundColor = this.radioActiveColor;
// 本组件的activeColor值优先 }
radioActiveColor() { style.width = this.radioGroup.size + 'rpx';
return this.activeColor ? this.activeColor : this.radioGroup.activeColor; style.height = this.radioGroup.size + 'rpx';
} return style;
}, },
methods: { iconColor() {
onClickLabel() { return this.name == this.radioGroup.value ? '#ffffff' : 'transparent';
if (!this.disabled && !this.labelDisabled && !this.parentDisabled) { },
this.radioGroup.setValue(this.name); iconClass() {
this.emitEvent(); let classs = [];
} classs.push('u-radio__icon--' + this.shape);
}, if (this.name == this.radioGroup.value) classs.push('u-radio__icon--checked');
toggle() { if (this.disabled || this.parentDisabled) classs.push('u-radio__icon--disabled');
if (!this.disabled && !this.parentDisabled) { if (this.name == this.radioGroup.value && (this.disabled || this.parentDisabled)) classs.push(
this.radioGroup.setValue(this.name); 'u-radio__icon--disabled--checked');
this.emitEvent(); return classs;
} },
}, // 激活的颜色,可能受radioGroup和本组件的activeColor影响
emitEvent() { // 本组件的activeColor值优先
this.$emit('change', this.name) radioActiveColor() {
}, return this.activeColor ? this.activeColor : this.radioGroup.activeColor;
} }
}; },
</script> methods: {
onClickLabel() {
<style lang="scss" scoped> if (!this.disabled && !this.labelDisabled && !this.parentDisabled) {
.u-radio { this.radioGroup.setValue(this.name);
display: -webkit-flex; this.emitEvent();
display: flex; }
-webkit-align-items: center; },
align-items: center; toggle() {
overflow: hidden; if (!this.disabled && !this.parentDisabled) {
-webkit-user-select: none; this.radioGroup.setValue(this.name);
user-select: none; this.emitEvent();
} }
},
.u-radio__icon-wrap, emitEvent() {
.u-radio__label { this.$emit('change', this.name)
color: $u-content-color; },
} }
};
.u-radio__icon-wrap { </script>
-webkit-flex: none;
flex: none; <style lang="scss" scoped>
} .u-radio {
display: -webkit-flex;
.u-radio__icon { display: flex;
display: -webkit-flex; -webkit-align-items: center;
display: flex; align-items: center;
-webkit-align-items: center; overflow: hidden;
align-items: center; -webkit-user-select: none;
-webkit-justify-content: center; user-select: none;
justify-content: center; }
box-sizing: border-box;
width: 42rpx; .u-radio__icon-wrap,
height: 42rpx; .u-radio__label {
color: transparent; color: $u-content-color;
text-align: center; }
transition-property: color, border-color, background-color;
font-size: 20px; .u-radio__icon-wrap {
border: 1px solid #c8c9cc; -webkit-flex: none;
transition-duration: 0.2s; flex: none;
} }
.u-radio__icon--circle { .u-radio__icon {
border-radius: 100%; display: -webkit-flex;
} display: flex;
-webkit-align-items: center;
.u-radio__icon--square { align-items: center;
border-radius: 3px; -webkit-justify-content: center;
} justify-content: center;
box-sizing: border-box;
.u-radio__icon--checked { width: 42rpx;
color: #fff; height: 42rpx;
background-color: #2979ff; color: transparent;
border-color: #2979ff; text-align: center;
} transition-property: color, border-color, background-color;
font-size: 20px;
.u-radio__icon--disabled { border: 1px solid #c8c9cc;
background-color: #ebedf0; transition-duration: 0.2s;
border-color: #c8c9cc; }
}
.u-radio__icon--circle {
.u-radio__icon--disabled--checked { border-radius: 100%;
color: #c8c9cc!important; }
}
.u-radio__icon--square {
.u-radio__label { border-radius: 3px;
word-wrap: break-word; }
margin-left: 10rpx;
margin-right: 18rpx; .u-radio__icon--checked {
color: $u-content-color; color: #fff;
font-size: 30rpx; background-color: #2979ff;
} border-color: #2979ff;
}
.u-radio__label--disabled {
color: #c8c9cc; .u-radio__icon--disabled {
} background-color: #ebedf0;
border-color: #c8c9cc;
.u-radio__label:empty { }
margin: 0;
} .u-radio__icon--disabled--checked {
color: #c8c9cc !important;
}
.u-radio__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 18rpx;
color: $u-content-color;
font-size: 30rpx;
}
.u-radio__label--disabled {
color: #c8c9cc;
}
.u-radio__label:empty {
margin: 0;
}
</style> </style>
<template> <template>
<view class="u-rate" :id="elId" @touchmove.stop.prevent="touchMove"> <view class="u-rate" :id="elId" @touchmove.stop.prevent="touchMove">
<view class="u-star-wrap" v-for="(item, index) in count" :key="index" :class="[elClass]"> <view class="u-star-wrap" v-for="(item, index) in count" :key="index" :class="[elClass]">
<u-icon :name="activeIndex > index ? activeIcon : inactiveIcon" @click="click(index + 1, $event)" :style="{ <u-icon :name="activeIndex > index ? activeIcon : inactiveIcon" @click="click(index + 1, $event)" :style="{
color: activeIndex > index ? activeColor : inactiveColor, color: activeIndex > index ? activeColor : inactiveColor,
fontSize: size + 'rpx', fontSize: size + 'rpx',
padding: `0 ${gutter / 2 + 'rpx'}` padding: `0 ${gutter / 2 + 'rpx'}`
}"></u-icon> }"></u-icon>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 要显示的星星数量 * @description 该组件一般用于满意度调查,星型评分的场景
count: { * @tutorial https://www.uviewui.com/components/rate.html
type: [Number, String], * @property {String Number} count 最多可选的星星数量(默认5)
default: 5 * @property {String Number} current 默认选中的星星数量(默认0)
}, * @property {Boolean} disabled 是否禁止用户操作(默认false)
// 当前需要默认选中的星星(选中的个数) * @property {String Number} size 星星的大小,单位rpx(默认32)
current: { * @property {String} inactive-color 未选中星星的颜色(默认#b2b2b2)
type: [Number, String], * @property {String} active-color 选中的星星颜色(默认#FA3534)
default: 0 * @property {String} gutter 星星之间的距离(默认#323233)
}, * @property {String Number} min-count 最少选中星星的个数(默认0)
// 是否不可选中 * @property {Boolean} allow-half 是否允许半星选择(默认false)
disabled: { * @event {Function} change 选中的星星发生变化时触发
type: Boolean, * @example <u-rate :count="count" :current="2"></u-rate>
default: false */
}, export default {
// 星星的大小,单位rpx name: "u-rate",
size: { props: {
type: [Number, String], // 要显示的星星数量
default: 32 count: {
}, type: [Number, String],
// 未选中时的颜色 default: 5
inactiveColor: { },
type: String, // 当前需要默认选中的星星(选中的个数)
default: "#b2b2b2" current: {
}, type: [Number, String],
// 选中的颜色 default: 0
activeColor: { },
type: String, // 是否不可选中
default: "#FA3534" disabled: {
}, type: Boolean,
// 星星之间的间距,单位rpx default: false
gutter: { },
type: [Number, String], // 星星的大小,单位rpx
default: 10 size: {
}, type: [Number, String],
// 最少能选择的星星个数 default: 32
minCount: { },
type: [Number, String], // 未选中时的颜色
default: 0 inactiveColor: {
}, type: String,
// 是否允许半星(功能尚未实现) default: "#b2b2b2"
allowHalf: { },
type: Boolean, // 选中的颜色
default: false activeColor: {
}, type: String,
// 选中时的图标(星星) default: "#FA3534"
activeIcon: { },
type: String, // 星星之间的间距,单位rpx
default: 'star-fill' gutter: {
}, type: [Number, String],
// 未选中时的图标(星星) default: 10
inactiveIcon: { },
type: String, // 最少能选择的星星个数
default: 'star' minCount: {
}, type: [Number, String],
}, default: 0
data() { },
return { // 是否允许半星(功能尚未实现)
// 生成一个唯一id,否则一个页面多个评分组件,会造成冲突 allowHalf: {
elId: this.$u.guid(), type: Boolean,
elClass: this.$u.guid(), default: false
starBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离 },
activeIndex: this.current, // 当前激活的星星的index // 选中时的图标(星星)
starWidth: 0, // 每个星星的宽度 activeIcon: {
starWidthArr: [], //每个星星最右边到组件盒子最左边的距离 type: String,
} default: 'star-fill'
}, },
watch: { // 未选中时的图标(星星)
current(val) { inactiveIcon: {
this.activeIndex = val; type: String,
} default: 'star'
}, },
methods: { },
// 获取评分组件盒子的布局信息 data() {
getElRectById() { return {
let query = uni.createSelectorQuery().in(this) // 生成一个唯一id,否则一个页面多个评分组件,会造成冲突
query.select("#" + this.elId).boundingClientRect((res) => { elId: this.$u.guid(),
// 如果获取不到,延时到本轮代码的末期再尝试 elClass: this.$u.guid(),
if (!res.left) { starBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
setTimeout(this.getElRectByClass); activeIndex: this.current, // 当前激活的星星的index
return; starWidth: 0, // 每个星星的宽度
} starWidthArr: [], //每个星星最右边到组件盒子最左边的距离
this.starBoxLeft = res.left; }
}).exec() },
}, watch: {
// 获取单个星星的尺寸 current(val) {
getElRectByClass() { this.activeIndex = val;
let query = uni.createSelectorQuery().in(this) }
query.select("." + this.elClass).boundingClientRect((res) => { },
if (!res.width) { methods: {
setTimeout(this.getElRectByClass()); // 获取评分组件盒子的布局信息
return; getElRectById() {
} let query = uni.createSelectorQuery().in(this)
this.starWidth = res.width; query.select("#" + this.elId).boundingClientRect((res) => {
// 把每个星星右边到组件盒子左边的距离放入数组中 // 如果获取不到,延时到本轮代码的末期再尝试
for(let i = 0; i < this.count; i ++) { if (!res.left) {
this.starWidthArr[i] = (i + 1) * this.starWidth; setTimeout(this.getElRectByClass);
} return;
}).exec() }
}, this.starBoxLeft = res.left;
// 手指滑动 }).exec()
touchMove(e) { },
if (this.disabled) { // 获取单个星星的尺寸
return; getElRectByClass() {
} let query = uni.createSelectorQuery().in(this)
if (!e.changedTouches[0]) { query.select("." + this.elClass).boundingClientRect((res) => {
return; if (!res.width) {
} setTimeout(this.getElRectByClass());
const movePageX = e.changedTouches[0].pageX; return;
// 滑动点相对于评分盒子左边的距离 }
const distance = movePageX - this.starBoxLeft; this.starWidth = res.width;
// 把每个星星右边到组件盒子左边的距离放入数组中
// 如果滑动到了评分盒子的左边界,就设置为0星 for (let i = 0; i < this.count; i++) {
if (distance <= 0) { this.starWidthArr[i] = (i + 1) * this.starWidth;
this.activeIndex = 0; }
} }).exec()
// 滑动的距离,相当于多少颗星星 },
let index = Math.ceil(distance / this.starWidth); // 手指滑动
this.activeIndex = index > this.count ? this.count : index; touchMove(e) {
// 对最少颗星星的限制 if (this.disabled) {
if(this.activeIndex < this.minCount) this.activeIndex = this.minCount; return;
this.$emit('change', this.activeIndex) }
}, if (!e.changedTouches[0]) {
// 通过点击,直接选中 return;
click(index, e) { }
if (this.disabled) { const movePageX = e.changedTouches[0].pageX;
return; // 滑动点相对于评分盒子左边的距离
} const distance = movePageX - this.starBoxLeft;
// 半星选择,尚未实现
if(this.allowHalf) { // 如果滑动到了评分盒子的左边界,就设置为0星
if (distance <= 0) {
} this.activeIndex = 0;
// 对第一个星星特殊处理,只有一个的时候,点击可以取消,否则无法作0星评价 }
if (index == 1) { // 滑动的距离,相当于多少颗星星
if (this.activeIndex == 1) this.activeIndex = 0; let index = Math.ceil(distance / this.starWidth);
else this.activeIndex = 1; this.activeIndex = index > this.count ? this.count : index;
} else { // 对最少颗星星的限制
this.activeIndex = index; if (this.activeIndex < this.minCount) this.activeIndex = this.minCount;
} this.$emit('change', this.activeIndex)
// 对最少颗星星的限制 },
if(this.activeIndex < this.minCount) this.activeIndex = this.minCount; // 通过点击,直接选中
this.$emit('change', this.activeIndex) click(index, e) {
}, if (this.disabled) {
}, return;
mounted() { }
this.getElRectById(); // 半星选择,尚未实现
this.getElRectByClass(); if (this.allowHalf) {
},
} }
</script> // 对第一个星星特殊处理,只有一个的时候,点击可以取消,否则无法作0星评价
if (index == 1) {
<style scoped lang="scss"> if (this.activeIndex == 1) this.activeIndex = 0;
.u-rate { else this.activeIndex = 1;
display: -webkit-inline-flex; } else {
display: inline-flex; this.activeIndex = index;
align-items: center; }
margin: 0; // 对最少颗星星的限制
padding: 0; if (this.activeIndex < this.minCount) this.activeIndex = this.minCount;
} this.$emit('change', this.activeIndex)
},
.u-icon { },
box-sizing: border-box; mounted() {
} this.getElRectById();
this.getElRectByClass();
},
}
</script>
<style scoped lang="scss">
.u-rate {
display: -webkit-inline-flex;
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
}
.u-icon {
box-sizing: border-box;
}
</style> </style>
<template> <template>
<view class=""> <view class="">
<view class="u-content" :style="{ height: isLongContent && !showMore ? showHeight + 'px' : 'auto' }"> <view class="u-content" :style="{ height: isLongContent && !showMore ? showHeight + 'px' : 'auto' }">
<slot></slot> <slot></slot>
</view> </view>
<view @tap="toggleReadMore" v-if="isLongContent" class="u-showmore-wrap" :class="{ 'u-show-more': showMore }"> <view @tap="toggleReadMore" v-if="isLongContent" class="u-showmore-wrap" :class="{ 'u-show-more': showMore }">
<text class="u-readmore-btn" :style="{ <text class="u-readmore-btn" :style="{
fontSize: fontSize + 'rpx', fontSize: fontSize + 'rpx',
color: color color: color
}"> }">
{{ showMore ? openText : closeText }} {{ showMore ? openText : closeText }}
</text> </text>
<u-icon :color="color" :size="fontSize" class="u-icon" :name="showMore ? 'arrow-up' : 'arrow-down'"></u-icon> <u-icon :color="color" :size="fontSize" class="u-icon" :name="showMore ? 'arrow-up' : 'arrow-down'"></u-icon>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { /**
props: { * alertTips 提示
// 默认的显示占位高度,单位为px * @description 该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。
showHeight: { * @tutorial https://www.uviewui.com/components/readMore.html
type: [Number, String], * @property {String Number} show-height 内容超出此高度才会显示展开全文按钮,单位rpx(默认400)
default: 400 * @property {Boolean} toggle 展开后是否显示收起按钮(默认false)
}, * @property {String} close-text 关闭时的提示文字(默认“展开阅读全文”)
// 展开后是否显示"收起"按钮 * @property {String Number} font-size 提示文字的大小,单位rpx(默认28)
toggle: { * @property {String} open-text 展开时的提示文字(默认“收起”)
type: Boolean, * @property {String} color 提示文字的颜色(默认#2979ff)
default: false * @example <u-read-more><rich-text :nodes="content"></rich-text></u-read-more>
}, */
// 关闭时的提示文字 export default {
closeText: { name: "u-read-more",
type: String, props: {
default: '展开阅读全文' // 默认的显示占位高度,单位为px
}, showHeight: {
// 展开时的提示文字 type: [Number, String],
openText: { default: 400
type: String, },
default: '收起' // 展开后是否显示"收起"按钮
}, toggle: {
// 提示的文字颜色 type: Boolean,
color: { default: false
type: String, },
default: '#2979ff' // 关闭时的提示文字
}, closeText: {
// 提示文字的大小 type: String,
fontSize: { default: '展开阅读全文'
type: [String, Number], },
default: 28 // 展开时的提示文字
} openText: {
}, type: String,
watch: { default: '收起'
paramsChange(val) { },
this.init(); // 提示的文字颜色
} color: {
}, type: String,
computed: { default: '#2979ff'
paramsChange() { },
return `${this.toggle}-${this.showHeight}`; // 提示文字的大小
} fontSize: {
}, type: [String, Number],
data() { default: 28
return { }
isLongContent: false, // 是否需要隐藏一部分内容 },
showMore: false, // 当前隐藏与显示的状态,true-显示,false-收起 watch: {
}; paramsChange(val) {
}, this.init();
mounted() { }
this.init(); },
}, computed: {
methods: { paramsChange() {
init() { return `${this.toggle}-${this.showHeight}`;
const query = uni.createSelectorQuery(this).in(this); }
query },
.select('.u-content') data() {
.boundingClientRect(res => { return {
if (res) { isLongContent: false, // 是否需要隐藏一部分内容
// 判断高度,如果真实内容高度大于占位高度,则显示收起与展开的控制按钮 showMore: false, // 当前隐藏与显示的状态,true-显示,false-收起
if (res.height > this.showHeight) { };
this.isLongContent = true; },
this.showMore = false; mounted() {
} this.init();
} },
}) methods: {
.exec(); init() {
}, const query = uni.createSelectorQuery(this).in(this);
// 展开或者收起 query
toggleReadMore() { .select('.u-content')
this.showMore = !this.showMore; .boundingClientRect(res => {
// 如果toggle为false,隐藏"收起"部分的内容 if (res) {
if(this.toggle == false) this.isLongContent = false; // 判断高度,如果真实内容高度大于占位高度,则显示收起与展开的控制按钮
} if (res.height > this.showHeight) {
} this.isLongContent = true;
}; this.showMore = false;
</script> }
}
<style lang="scss" scoped> })
.u-content { .exec();
font-size: 30rpx; },
color: $u-content-color; // 展开或者收起
line-height: 1.8; toggleReadMore() {
text-align: left; this.showMore = !this.showMore;
text-indent: 2em; // 如果toggle为false,隐藏"收起"部分的内容
overflow: hidden; if (this.toggle == false) this.isLongContent = false;
} }
}
.u-showmore-wrap { };
position: relative; </script>
width: 100%;
background-image: linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%); <style lang="scss" scoped>
padding-top: 300rpx; .u-content {
margin-top: -300rpx; font-size: 30rpx;
padding-bottom: 26rpx; color: $u-content-color;
display: flex; line-height: 1.8;
align-items: center; text-align: left;
justify-content: center; text-indent: 2em;
} overflow: hidden;
}
.u-show-more {
padding-top: 0; .u-showmore-wrap {
background: none; position: relative;
margin-top: 20rpx; width: 100%;
} background-image: linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%);
padding-top: 300rpx;
.u-readmore-btn { margin-top: -300rpx;
display: flex; padding-bottom: 26rpx;
align-items: center; display: flex;
justify-content: center; align-items: center;
line-height: 1; justify-content: center;
} }
.u-icon { .u-show-more {
margin-left: 14rpx; padding-top: 0;
} background: none;
margin-top: 20rpx;
}
.u-readmore-btn {
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}
.u-icon {
margin-left: 14rpx;
}
</style> </style>
<template> <template>
<view v-if="loading" :style="{ <view v-if="loading" :style="{
width: windowWinth + 'px', width: windowWinth + 'px',
height: windowHeight + 'px', height: windowHeight + 'px',
backgroundColor: bgColor, backgroundColor: bgColor,
position: 'absolute', position: 'absolute',
left: left + 'px', left: left + 'px',
top: top + 'px', top: top + 'px',
zIndex: 9998, zIndex: 9998,
overflow: 'hidden' overflow: 'hidden'
}" @touchmove.stop.prevent> }"
<view v-for="(item, index) in RectNodes" :key="$u.guid()" :class="[animation ? 'skeleton-fade' : '']" :style="{ @touchmove.stop.prevent>
width: item.width + 'px', <view v-for="(item, index) in RectNodes" :key="$u.guid()" :class="[animation ? 'skeleton-fade' : '']" :style="{
height: item.height + 'px', width: item.width + 'px',
backgroundColor: elColor, height: item.height + 'px',
position: 'absolute', backgroundColor: elColor,
left: (item.left - left) + 'px', position: 'absolute',
top: (item.top - top) + 'px' left: (item.left - left) + 'px',
}"></view> top: (item.top - top) + 'px'
<view v-for="(item, index) in circleNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{ }"></view>
width: item.width + 'px', <view v-for="(item, index) in circleNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{
height: item.height + 'px', width: item.width + 'px',
backgroundColor: elColor, height: item.height + 'px',
borderRadius: item.width/2 + 'px', backgroundColor: elColor,
position: 'absolute', borderRadius: item.width/2 + 'px',
left: (item.left - left) + 'px', position: 'absolute',
top: (item.top - top) + 'px' left: (item.left - left) + 'px',
}"></view> top: (item.top - top) + 'px'
<view v-for="(item, index) in filletNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{ }"></view>
width: item.width + 'px', <view v-for="(item, index) in filletNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{
height: item.height + 'px', width: item.width + 'px',
backgroundColor: elColor, height: item.height + 'px',
borderRadius: borderRadius + 'rpx', backgroundColor: elColor,
position: 'absolute', borderRadius: borderRadius + 'rpx',
left: (item.left - left) + 'px', position: 'absolute',
top: (item.top - top) + 'px' left: (item.left - left) + 'px',
}"></view> top: (item.top - top) + 'px'
</view> }"></view>
</template> </view>
</template>
<script>
export default { <script>
props: { /**
// 需要渲染的元素背景颜色,十六进制或者rgb等都可以 * alertTips 提示
elColor: { * @description 骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。
type: String, * @tutorial https://www.uviewui.com/components/skeleton.html
default: '#e5e5e5' * @property {String} el-color 骨架块状元素的背景颜色(默认#e5e5e5)
}, * @property {String} bg-color 骨架组件背景颜色(默认#ffffff)
// 整个骨架屏页面的背景颜色 * @property {Boolean} animation 骨架块是否显示动画效果(默认false)
bgColor: { * @property {String Number} border-radius u-skeleton-fillet类名元素,对应的骨架块的圆角大小,单位rpx(默认10)
type: String, * @property {Boolean} loading 是否显示骨架组件,请求完成后,将此值设置为false(默认true)
default: '#ffffff' * @example <u-skeleton :loading="true" :animation="true"></u-skeleton>
}, */
// 是否显示加载动画 export default {
animation: { name: "u-skeleton",
type: Boolean, props: {
default: false // 需要渲染的元素背景颜色,十六进制或者rgb等都可以
}, elColor: {
// 圆角值,只对类名为u-skeleton-fillet的元素生效,为数值,不带单位 type: String,
borderRadius: { default: '#e5e5e5'
type: [String, Number], },
default: "10" // 整个骨架屏页面的背景颜色
}, bgColor: {
// 是否显示骨架,true-显示,false-隐藏 type: String,
loading: { default: '#ffffff'
type: Boolean, },
default: true // 是否显示加载动画
} animation: {
}, type: Boolean,
data() { default: false
return { },
windowWinth: 750, // 骨架屏宽度 // 圆角值,只对类名为u-skeleton-fillet的元素生效,为数值,不带单位
windowHeight: 1500, // 骨架屏高度 borderRadius: {
filletNodes: [], // 圆角元素 type: [String, Number],
circleNodes: [], // 圆形元素 default: "10"
RectNodes: [], // 矩形元素 },
top: 0, // 是否显示骨架,true-显示,false-隐藏
left: 0, loading: {
} type: Boolean,
}, default: true
methods: { }
// 查询各节点的信息 },
selecterQueryInfo() { data() {
// 获取整个父组件容器的高度,当做骨架屏的高度 return {
uni.createSelectorQuery().selectAll('.u-skeleton').boundingClientRect().exec((res) => { windowWinth: 750, // 骨架屏宽度
this.windowHeight = res[0][0].height; windowHeight: 1500, // 骨架屏高度
this.windowWinth = res[0][0].width; filletNodes: [], // 圆角元素
this.top = res[0][0].bottom - res[0][0].height; circleNodes: [], // 圆形元素
this.left = res[0][0].left; RectNodes: [], // 矩形元素
}); top: 0,
// 矩形骨架元素 left: 0,
this.getRectEls(); }
// 圆形骨架元素 },
this.getCircleEls(); methods: {
// 圆角骨架元素 // 查询各节点的信息
this.getFilletEls(); selecterQueryInfo() {
}, // 获取整个父组件容器的高度,当做骨架屏的高度
// 矩形元素列表 uni.createSelectorQuery().selectAll('.u-skeleton').boundingClientRect().exec((res) => {
getRectEls() { this.windowHeight = res[0][0].height;
uni.createSelectorQuery().selectAll('.u-skeleton-rect').boundingClientRect().exec((res) => { this.windowWinth = res[0][0].width;
this.RectNodes = res[0]; this.top = res[0][0].bottom - res[0][0].height;
}); this.left = res[0][0].left;
}, });
// 圆角元素列表 // 矩形骨架元素
getFilletEls() { this.getRectEls();
uni.createSelectorQuery().selectAll('.u-skeleton-fillet').boundingClientRect().exec((res) => { // 圆形骨架元素
this.filletNodes = res[0]; this.getCircleEls();
}); // 圆角骨架元素
}, this.getFilletEls();
// 圆形元素列表 },
getCircleEls() { // 矩形元素列表
uni.createSelectorQuery().selectAll('.u-skeleton-circle').boundingClientRect().exec((res) => { getRectEls() {
this.circleNodes = res[0]; uni.createSelectorQuery().selectAll('.u-skeleton-rect').boundingClientRect().exec((res) => {
}); this.RectNodes = res[0];
} });
}, },
// 组件被挂载 // 圆角元素列表
mounted() { getFilletEls() {
// 获取系统信息 uni.createSelectorQuery().selectAll('.u-skeleton-fillet').boundingClientRect().exec((res) => {
let systemInfo = uni.getSystemInfoSync(); this.filletNodes = res[0];
this.windowHeight = systemInfo.windowHeight; });
this.windowWinth = systemInfo.windowWidth; },
this.selecterQueryInfo(); // 圆形元素列表
} getCircleEls() {
} uni.createSelectorQuery().selectAll('.u-skeleton-circle').boundingClientRect().exec((res) => {
</script> this.circleNodes = res[0];
});
<style lang="scss" scoped> }
.skeleton-fade { },
width: 100%; // 组件被挂载
height: 100%; mounted() {
background: rgb(194, 207, 214); // 获取系统信息
animation-duration: 1.5s; let systemInfo = uni.getSystemInfoSync();
animation-name: blink; this.windowHeight = systemInfo.windowHeight;
animation-timing-function: ease-in-out; this.windowWinth = systemInfo.windowWidth;
animation-iteration-count: infinite; this.selecterQueryInfo();
} }
}
@keyframes blink { </script>
0% {
opacity: 1; <style lang="scss" scoped>
} .skeleton-fade {
width: 100%;
50% { height: 100%;
opacity: 0.4; background: rgb(194, 207, 214);
} animation-duration: 1.5s;
animation-name: blink;
100% { animation-timing-function: ease-in-out;
opacity: 1; animation-iteration-count: infinite;
} }
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment