first commit
This commit is contained in:
46
uni_modules/uview-ui/components/u-button/nvue.scss
Normal file
46
uni_modules/uview-ui/components/u-button/nvue.scss
Normal file
@@ -0,0 +1,46 @@
|
||||
$u-button-active-opacity:0.75 !default;
|
||||
$u-button-loading-text-margin-left:4px !default;
|
||||
$u-button-text-color: #FFFFFF !default;
|
||||
$u-button-text-plain-error-color:$u-error !default;
|
||||
$u-button-text-plain-warning-color:$u-warning !default;
|
||||
$u-button-text-plain-success-color:$u-success !default;
|
||||
$u-button-text-plain-info-color:$u-info !default;
|
||||
$u-button-text-plain-primary-color:$u-primary !default;
|
||||
.u-button {
|
||||
&--active {
|
||||
opacity: $u-button-active-opacity;
|
||||
}
|
||||
|
||||
&--active--plain {
|
||||
background-color: rgb(217, 217, 217);
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
margin-left:$u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&__text,
|
||||
&__loading-text {
|
||||
color:$u-button-text-color;
|
||||
}
|
||||
|
||||
&__text--plain--error {
|
||||
color:$u-button-text-plain-error-color;
|
||||
}
|
||||
|
||||
&__text--plain--warning {
|
||||
color:$u-button-text-plain-warning-color;
|
||||
}
|
||||
|
||||
&__text--plain--success{
|
||||
color:$u-button-text-plain-success-color;
|
||||
}
|
||||
|
||||
&__text--plain--info {
|
||||
color:$u-button-text-plain-info-color;
|
||||
}
|
||||
|
||||
&__text--plain--primary {
|
||||
color:$u-button-text-plain-primary-color;
|
||||
}
|
||||
}
|
||||
161
uni_modules/uview-ui/components/u-button/props.js
Normal file
161
uni_modules/uview-ui/components/u-button/props.js
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* @Author : LQ
|
||||
* @Description :
|
||||
* @version : 1.0
|
||||
* @Date : 2021-08-16 10:04:04
|
||||
* @LastAuthor : LQ
|
||||
* @lastTime : 2021-08-16 10:04:24
|
||||
* @FilePath : /u-view2.0/uview-ui/components/u-button/props.js
|
||||
*/
|
||||
export default {
|
||||
props: {
|
||||
// 是否细边框
|
||||
hairline: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.hairline
|
||||
},
|
||||
// 按钮的预置样式,info,primary,error,warning,success
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.type
|
||||
},
|
||||
// 按钮尺寸,large,normal,small,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.size
|
||||
},
|
||||
// 按钮形状,circle(两边为半圆),square(带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.shape
|
||||
},
|
||||
// 按钮是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.plain
|
||||
},
|
||||
// 是否禁止状态
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.disabled
|
||||
},
|
||||
// 是否加载中
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.loading
|
||||
},
|
||||
// 加载中提示文字
|
||||
loadingText: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.loadingText
|
||||
},
|
||||
// 加载状态图标类型
|
||||
loadingMode: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.loadingMode
|
||||
},
|
||||
// 加载图标大小
|
||||
loadingSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.loadingSize
|
||||
},
|
||||
// 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
// https://uniapp.dcloud.io/component/button
|
||||
openType: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.openType
|
||||
},
|
||||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
// 取值为submit(提交表单),reset(重置表单)
|
||||
formType: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.formType
|
||||
},
|
||||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
|
||||
// 只微信小程序、QQ小程序有效
|
||||
appParameter: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.appParameter
|
||||
},
|
||||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
|
||||
hoverStopPropagation: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.hoverStopPropagation
|
||||
},
|
||||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
|
||||
lang: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.lang
|
||||
},
|
||||
// 会话来源,open-type="contact"时有效。只微信小程序有效
|
||||
sessionFrom: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sessionFrom
|
||||
},
|
||||
// 会话内消息卡片标题,open-type="contact"时有效
|
||||
// 默认当前标题,只微信小程序有效
|
||||
sendMessageTitle: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessageTitle
|
||||
},
|
||||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
|
||||
// 默认当前分享路径,只微信小程序有效
|
||||
sendMessagePath: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessagePath
|
||||
},
|
||||
// 会话内消息卡片图片,open-type="contact"时有效
|
||||
// 默认当前页面截图,只微信小程序有效
|
||||
sendMessageImg: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.sendMessageImg
|
||||
},
|
||||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
|
||||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
|
||||
showMessageCard: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.button.showMessageCard
|
||||
},
|
||||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
dataName: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.dataName
|
||||
},
|
||||
// 节流,一定时间内只能触发一次
|
||||
throttleTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.throttleTime
|
||||
},
|
||||
// 按住后多久出现点击态,单位毫秒
|
||||
hoverStartTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.hoverStartTime
|
||||
},
|
||||
// 手指松开后点击态保留时间,单位毫秒
|
||||
hoverStayTime: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.hoverStayTime
|
||||
},
|
||||
// 按钮文字,之所以通过props传入,是因为slot传入的话
|
||||
// nvue中无法控制文字的样式
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.button.text
|
||||
},
|
||||
// 按钮图标
|
||||
icon: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.icon
|
||||
},
|
||||
// 按钮图标
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.icon
|
||||
},
|
||||
// 按钮颜色,支持传入linear-gradient渐变色
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.button.color
|
||||
}
|
||||
}
|
||||
}
|
||||
495
uni_modules/uview-ui/components/u-button/u-button.vue
Normal file
495
uni_modules/uview-ui/components/u-button/u-button.vue
Normal file
@@ -0,0 +1,495 @@
|
||||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<button
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
:form-type="formType"
|
||||
:open-type="openType"
|
||||
:app-parameter="appParameter"
|
||||
:hover-stop-propagation="hoverStopPropagation"
|
||||
:send-message-title="sendMessageTitle"
|
||||
:send-message-path="sendMessagePath"
|
||||
:lang="lang"
|
||||
:data-name="dataName"
|
||||
:session-from="sessionFrom"
|
||||
:send-message-img="sendMessageImg"
|
||||
:show-message-card="showMessageCard"
|
||||
@getphonenumber="getphonenumber"
|
||||
@getuserinfo="getuserinfo"
|
||||
@error="error"
|
||||
@opensetting="opensetting"
|
||||
@launchapp="launchapp"
|
||||
@agreeprivacyauthorization="agreeprivacyauthorization"
|
||||
:hover-class="!disabled && !loading ? 'u-button--active' : ''"
|
||||
class="u-button u-reset-button"
|
||||
:style="[baseColor, $u.addStyle(customStyle)]"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="loadingSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
:customStyle="{ marginRight: '2px' }"
|
||||
></u-icon>
|
||||
<slot>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[{ fontSize: textSize + 'px' }]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</slot>
|
||||
</template>
|
||||
</button>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view
|
||||
:hover-start-time="Number(hoverStartTime)"
|
||||
:hover-stay-time="Number(hoverStayTime)"
|
||||
class="u-button"
|
||||
:hover-class="
|
||||
!disabled && !loading && !color && (plain || type === 'info')
|
||||
? 'u-button--active--plain'
|
||||
: !disabled && !loading && !plain
|
||||
? 'u-button--active'
|
||||
: ''
|
||||
"
|
||||
@tap="clickHandler"
|
||||
:class="bemClass"
|
||||
:style="[baseColor, $u.addStyle(customStyle)]"
|
||||
>
|
||||
<template v-if="loading">
|
||||
<u-loading-icon
|
||||
:mode="loadingMode"
|
||||
:size="loadingSize * 1.15"
|
||||
:color="loadingColor"
|
||||
></u-loading-icon>
|
||||
<text
|
||||
class="u-button__loading-text"
|
||||
:style="[nvueTextStyle]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ loadingText || text }}</text
|
||||
>
|
||||
</template>
|
||||
<template v-else>
|
||||
<u-icon
|
||||
v-if="icon"
|
||||
:name="icon"
|
||||
:color="iconColorCom"
|
||||
:size="textSize * 1.35"
|
||||
></u-icon>
|
||||
<text
|
||||
class="u-button__text"
|
||||
:style="[
|
||||
{
|
||||
marginLeft: icon ? '2px' : 0,
|
||||
},
|
||||
nvueTextStyle,
|
||||
]"
|
||||
:class="[plain && `u-button__text--plain--${type}`]"
|
||||
>{{ text }}</text
|
||||
>
|
||||
</template>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import button from "../../libs/mixin/button.js";
|
||||
import openType from "../../libs/mixin/openType.js";
|
||||
import props from "./props.js";
|
||||
/**
|
||||
* button 按钮
|
||||
* @description Button 按钮
|
||||
* @tutorial https://www.uviewui.com/components/button.html
|
||||
*
|
||||
* @property {Boolean} hairline 是否显示按钮的细边框 (默认 true )
|
||||
* @property {String} type 按钮的预置样式,info,primary,error,warning,success (默认 'info' )
|
||||
* @property {String} size 按钮尺寸,large,normal,mini (默认 normal)
|
||||
* @property {String} shape 按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
|
||||
* @property {Boolean} plain 按钮是否镂空,背景色透明 (默认 false)
|
||||
* @property {Boolean} disabled 是否禁用 (默认 false)
|
||||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
|
||||
* @property {String | Number} loadingText 加载中提示文字
|
||||
* @property {String} loadingMode 加载状态图标类型 (默认 'spinner' )
|
||||
* @property {String | Number} loadingSize 加载图标大小 (默认 15 )
|
||||
* @property {String} openType 开放能力,具体请看uniapp稳定关于button组件部分说明
|
||||
* @property {String} formType 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
|
||||
* @property {String} appParameter 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
|
||||
* @property {Boolean} hoverStopPropagation 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
|
||||
* @property {String} lang 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
|
||||
* @property {String} sessionFrom 会话来源,openType="contact"时有效
|
||||
* @property {String} sendMessageTitle 会话内消息卡片标题,openType="contact"时有效
|
||||
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径,openType="contact"时有效
|
||||
* @property {String} sendMessageImg 会话内消息卡片图片,openType="contact"时有效
|
||||
* @property {Boolean} showMessageCard 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
|
||||
* @property {String} dataName 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
|
||||
* @property {String | Number} throttleTime 节流,一定时间内只能触发一次 (默认 0 )
|
||||
* @property {String | Number} hoverStartTime 按住后多久出现点击态,单位毫秒 (默认 0 )
|
||||
* @property {String | Number} hoverStayTime 手指松开后点击态保留时间,单位毫秒 (默认 200 )
|
||||
* @property {String | Number} text 按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
|
||||
* @property {String} icon 按钮图标
|
||||
* @property {String} iconColor 按钮图标颜色
|
||||
* @property {String} color 按钮颜色,支持传入linear-gradient渐变色
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @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 成功的回调
|
||||
* @event {Function} agreeprivacyauthorization 用户同意隐私协议事件回调
|
||||
* @example <u-button>月落</u-button>
|
||||
*/
|
||||
export default {
|
||||
name: "u-button",
|
||||
// #ifdef MP
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
// #endif
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
// 生成bem风格的类名
|
||||
bemClass() {
|
||||
// this.bem为一个computed变量,在mixin中
|
||||
if (!this.color) {
|
||||
return this.bem(
|
||||
"button",
|
||||
["type", "shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
} else {
|
||||
// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
|
||||
return this.bem(
|
||||
"button",
|
||||
["shape", "size"],
|
||||
["disabled", "plain", "hairline"]
|
||||
);
|
||||
}
|
||||
},
|
||||
loadingColor() {
|
||||
if (this.plain) {
|
||||
// 如果有设置color值,则用color值,否则使用type主题颜色
|
||||
return this.color
|
||||
? this.color
|
||||
: uni.$u.config.color[`u-${this.type}`];
|
||||
}
|
||||
if (this.type === "info") {
|
||||
return "#c9c9c9";
|
||||
}
|
||||
return "rgb(200, 200, 200)";
|
||||
},
|
||||
iconColorCom() {
|
||||
// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
|
||||
// u-icon的color能接受一个主题颜色的值
|
||||
if (this.iconColor) return this.iconColor;
|
||||
if (this.plain) {
|
||||
return this.color ? this.color : this.type;
|
||||
} else {
|
||||
return this.type === "info" ? "#000000" : "#ffffff";
|
||||
}
|
||||
},
|
||||
baseColor() {
|
||||
let style = {};
|
||||
if (this.color) {
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
style.color = this.plain ? this.color : "white";
|
||||
if (!this.plain) {
|
||||
// 非镂空,背景色使用自定义的颜色
|
||||
style["background-color"] = this.color;
|
||||
}
|
||||
if (this.color.indexOf("gradient") !== -1) {
|
||||
// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
|
||||
// weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
|
||||
// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
|
||||
style.borderTopWidth = 0;
|
||||
style.borderRightWidth = 0;
|
||||
style.borderBottomWidth = 0;
|
||||
style.borderLeftWidth = 0;
|
||||
if (!this.plain) {
|
||||
style.backgroundImage = this.color;
|
||||
}
|
||||
} else {
|
||||
// 非渐变色,则设置边框相关的属性
|
||||
style.borderColor = this.color;
|
||||
style.borderWidth = "1px";
|
||||
style.borderStyle = "solid";
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
|
||||
nvueTextStyle() {
|
||||
let style = {};
|
||||
// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
|
||||
if (this.type === "info") {
|
||||
style.color = "#323233";
|
||||
}
|
||||
if (this.color) {
|
||||
style.color = this.plain ? this.color : "white";
|
||||
}
|
||||
style.fontSize = this.textSize + "px";
|
||||
return style;
|
||||
},
|
||||
// 字体大小
|
||||
textSize() {
|
||||
let fontSize = 14,
|
||||
{ size } = this;
|
||||
if (size === "large") fontSize = 16;
|
||||
if (size === "normal") fontSize = 14;
|
||||
if (size === "small") fontSize = 12;
|
||||
if (size === "mini") fontSize = 10;
|
||||
return fontSize;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clickHandler() {
|
||||
// 非禁止并且非加载中,才能点击
|
||||
if (!this.disabled && !this.loading) {
|
||||
// 进行节流控制,每this.throttle毫秒内,只在开始处执行
|
||||
uni.$u.throttle(() => {
|
||||
this.$emit("click");
|
||||
}, this.throttleTime);
|
||||
}
|
||||
},
|
||||
// 下面为对接uniapp官方按钮开放能力事件回调的对接
|
||||
getphonenumber(res) {
|
||||
this.$emit("getphonenumber", res);
|
||||
},
|
||||
getuserinfo(res) {
|
||||
this.$emit("getuserinfo", res);
|
||||
},
|
||||
error(res) {
|
||||
this.$emit("error", res);
|
||||
},
|
||||
opensetting(res) {
|
||||
this.$emit("opensetting", res);
|
||||
},
|
||||
launchapp(res) {
|
||||
this.$emit("launchapp", res);
|
||||
},
|
||||
agreeprivacyauthorization(res) {
|
||||
this.$emit("agreeprivacyauthorization", res);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "./vue.scss";
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
@import "./nvue.scss";
|
||||
/* #endif */
|
||||
|
||||
$u-button-u-button-height: 40px !default;
|
||||
$u-button-text-font-size: 15px !default;
|
||||
$u-button-loading-text-font-size: 15px !default;
|
||||
$u-button-loading-text-margin-left: 4px !default;
|
||||
$u-button-large-width: 100% !default;
|
||||
$u-button-large-height: 50px !default;
|
||||
$u-button-normal-padding: 0 12px !default;
|
||||
$u-button-large-padding: 0 15px !default;
|
||||
$u-button-normal-font-size: 14px !default;
|
||||
$u-button-small-min-width: 60px !default;
|
||||
$u-button-small-height: 30px !default;
|
||||
$u-button-small-padding: 0px 8px !default;
|
||||
$u-button-mini-padding: 0px 8px !default;
|
||||
$u-button-small-font-size: 12px !default;
|
||||
$u-button-mini-height: 22px !default;
|
||||
$u-button-mini-font-size: 10px !default;
|
||||
$u-button-mini-min-width: 50px !default;
|
||||
$u-button-disabled-opacity: 0.5 !default;
|
||||
$u-button-info-color: #323233 !default;
|
||||
$u-button-info-background-color: #fff !default;
|
||||
$u-button-info-border-color: #ebedf0 !default;
|
||||
$u-button-info-border-width: 1px !default;
|
||||
$u-button-info-border-style: solid !default;
|
||||
$u-button-success-color: #fff !default;
|
||||
$u-button-success-background-color: $u-success !default;
|
||||
$u-button-success-border-color: $u-button-success-background-color !default;
|
||||
$u-button-success-border-width: 1px !default;
|
||||
$u-button-success-border-style: solid !default;
|
||||
$u-button-primary-color: #fff !default;
|
||||
$u-button-primary-background-color: $u-primary !default;
|
||||
$u-button-primary-border-color: $u-button-primary-background-color !default;
|
||||
$u-button-primary-border-width: 1px !default;
|
||||
$u-button-primary-border-style: solid !default;
|
||||
$u-button-error-color: #fff !default;
|
||||
$u-button-error-background-color: $u-error !default;
|
||||
$u-button-error-border-color: $u-button-error-background-color !default;
|
||||
$u-button-error-border-width: 1px !default;
|
||||
$u-button-error-border-style: solid !default;
|
||||
$u-button-warning-color: #fff !default;
|
||||
$u-button-warning-background-color: $u-warning !default;
|
||||
$u-button-warning-border-color: $u-button-warning-background-color !default;
|
||||
$u-button-warning-border-width: 1px !default;
|
||||
$u-button-warning-border-style: solid !default;
|
||||
$u-button-block-width: 100% !default;
|
||||
$u-button-circle-border-top-right-radius: 100px !default;
|
||||
$u-button-circle-border-top-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-left-radius: 100px !default;
|
||||
$u-button-circle-border-bottom-right-radius: 100px !default;
|
||||
$u-button-square-border-top-right-radius: 3px !default;
|
||||
$u-button-square-border-top-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-left-radius: 3px !default;
|
||||
$u-button-square-border-bottom-right-radius: 3px !default;
|
||||
$u-button-icon-min-width: 1em !default;
|
||||
$u-button-plain-background-color: #fff !default;
|
||||
$u-button-hairline-border-width: 0.5px !default;
|
||||
|
||||
.u-button {
|
||||
height: $u-button-u-button-height;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@include flex;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
|
||||
&__text {
|
||||
font-size: $u-button-text-font-size;
|
||||
}
|
||||
|
||||
&__loading-text {
|
||||
font-size: $u-button-loading-text-font-size;
|
||||
margin-left: $u-button-loading-text-margin-left;
|
||||
}
|
||||
|
||||
&--large {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: $u-button-large-width;
|
||||
/* #endif */
|
||||
height: $u-button-large-height;
|
||||
padding: $u-button-large-padding;
|
||||
}
|
||||
|
||||
&--normal {
|
||||
padding: $u-button-normal-padding;
|
||||
font-size: $u-button-normal-font-size;
|
||||
}
|
||||
|
||||
&--small {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-small-min-width;
|
||||
/* #endif */
|
||||
height: $u-button-small-height;
|
||||
padding: $u-button-small-padding;
|
||||
font-size: $u-button-small-font-size;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: $u-button-mini-height;
|
||||
font-size: $u-button-mini-font-size;
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-mini-min-width;
|
||||
/* #endif */
|
||||
padding: $u-button-mini-padding;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: $u-button-disabled-opacity;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-button-info-color;
|
||||
background-color: $u-button-info-background-color;
|
||||
border-color: $u-button-info-border-color;
|
||||
border-width: $u-button-info-border-width;
|
||||
border-style: $u-button-info-border-style;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-button-success-color;
|
||||
background-color: $u-button-success-background-color;
|
||||
border-color: $u-button-success-border-color;
|
||||
border-width: $u-button-success-border-width;
|
||||
border-style: $u-button-success-border-style;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: $u-button-primary-color;
|
||||
background-color: $u-button-primary-background-color;
|
||||
border-color: $u-button-primary-border-color;
|
||||
border-width: $u-button-primary-border-width;
|
||||
border-style: $u-button-primary-border-style;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-button-error-color;
|
||||
background-color: $u-button-error-background-color;
|
||||
border-color: $u-button-error-border-color;
|
||||
border-width: $u-button-error-border-width;
|
||||
border-style: $u-button-error-border-style;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-button-warning-color;
|
||||
background-color: $u-button-warning-background-color;
|
||||
border-color: $u-button-warning-border-color;
|
||||
border-width: $u-button-warning-border-width;
|
||||
border-style: $u-button-warning-border-style;
|
||||
}
|
||||
|
||||
&--block {
|
||||
@include flex;
|
||||
width: $u-button-block-width;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-top-right-radius: $u-button-circle-border-top-right-radius;
|
||||
border-top-left-radius: $u-button-circle-border-top-left-radius;
|
||||
border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
|
||||
border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-bottom-left-radius: $u-button-square-border-top-right-radius;
|
||||
border-bottom-right-radius: $u-button-square-border-top-left-radius;
|
||||
border-top-left-radius: $u-button-square-border-bottom-left-radius;
|
||||
border-top-right-radius: $u-button-square-border-bottom-right-radius;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
min-width: $u-button-icon-min-width;
|
||||
line-height: inherit !important;
|
||||
vertical-align: top;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: $u-button-plain-background-color;
|
||||
}
|
||||
|
||||
&--hairline {
|
||||
border-width: $u-button-hairline-border-width !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
80
uni_modules/uview-ui/components/u-button/vue.scss
Normal file
80
uni_modules/uview-ui/components/u-button/vue.scss
Normal file
@@ -0,0 +1,80 @@
|
||||
// nvue下hover-class无效
|
||||
$u-button-before-top:50% !default;
|
||||
$u-button-before-left:50% !default;
|
||||
$u-button-before-width:100% !default;
|
||||
$u-button-before-height:100% !default;
|
||||
$u-button-before-transform:translate(-50%, -50%) !default;
|
||||
$u-button-before-opacity:0 !default;
|
||||
$u-button-before-background-color:#000 !default;
|
||||
$u-button-before-border-color:#000 !default;
|
||||
$u-button-active-before-opacity:.15 !default;
|
||||
$u-button-icon-margin-left:4px !default;
|
||||
$u-button-plain-u-button-info-color:$u-info;
|
||||
$u-button-plain-u-button-success-color:$u-success;
|
||||
$u-button-plain-u-button-error-color:$u-error;
|
||||
$u-button-plain-u-button-warning-color:$u-error;
|
||||
|
||||
.u-button {
|
||||
width: 100%;
|
||||
|
||||
&__text {
|
||||
white-space: nowrap;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
top:$u-button-before-top;
|
||||
left:$u-button-before-left;
|
||||
width:$u-button-before-width;
|
||||
height:$u-button-before-height;
|
||||
border: inherit;
|
||||
border-radius: inherit;
|
||||
transform:$u-button-before-transform;
|
||||
opacity:$u-button-before-opacity;
|
||||
content: " ";
|
||||
background-color:$u-button-before-background-color;
|
||||
border-color:$u-button-before-border-color;
|
||||
}
|
||||
|
||||
&--active {
|
||||
&:before {
|
||||
opacity: .15
|
||||
}
|
||||
}
|
||||
|
||||
&__icon+&__text:not(:empty),
|
||||
&__loading-text {
|
||||
margin-left:$u-button-icon-margin-left;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--primary {
|
||||
color: $u-primary;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--info {
|
||||
color:$u-button-plain-u-button-info-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--success {
|
||||
color:$u-button-plain-u-button-success-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--error {
|
||||
color:$u-button-plain-u-button-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--plain {
|
||||
&.u-button--warning {
|
||||
color:$u-button-plain-u-button-warning-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
uni_modules/uview-ui/components/u-code/props.js
Normal file
34
uni_modules/uview-ui/components/u-code/props.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export default {
|
||||
props: {
|
||||
// 倒计时总秒数
|
||||
seconds: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.code.seconds
|
||||
},
|
||||
// 尚未开始时提示
|
||||
startText: {
|
||||
type: String,
|
||||
default: uni.$u.props.code.startText
|
||||
},
|
||||
// 正在倒计时中的提示
|
||||
changeText: {
|
||||
type: String,
|
||||
default: uni.$u.props.code.changeText
|
||||
},
|
||||
// 倒计时结束时的提示
|
||||
endText: {
|
||||
type: String,
|
||||
default: uni.$u.props.code.endText
|
||||
},
|
||||
// 是否在H5刷新或各端返回再进入时继续倒计时
|
||||
keepRunning: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.code.keepRunning
|
||||
},
|
||||
// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
uniqueKey: {
|
||||
type: String,
|
||||
default: uni.$u.props.code.uniqueKey
|
||||
}
|
||||
}
|
||||
}
|
||||
129
uni_modules/uview-ui/components/u-code/u-code.vue
Normal file
129
uni_modules/uview-ui/components/u-code/u-code.vue
Normal file
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<view class="u-code">
|
||||
<!-- 此组件功能由js完成,无需写html逻辑 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Code 验证码输入框
|
||||
* @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
|
||||
* @tutorial https://www.uviewui.com/components/code.html
|
||||
* @property {String | Number} seconds 倒计时所需的秒数(默认 60 )
|
||||
* @property {String} startText 开始前的提示语,见官网说明(默认 '获取验证码' )
|
||||
* @property {String} changeText 倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
|
||||
* @property {String} endText 倒计结束的提示语,见官网说明(默认 '重新获取' )
|
||||
* @property {Boolean} keepRunning 是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
|
||||
* @property {String} uniqueKey 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
|
||||
*
|
||||
* @event {Function} change 倒计时期间,每秒触发一次
|
||||
* @event {Function} start 开始倒计时触发
|
||||
* @event {Function} end 结束倒计时触发
|
||||
* @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
|
||||
*/
|
||||
export default {
|
||||
name: "u-code",
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
data() {
|
||||
return {
|
||||
secNum: this.seconds,
|
||||
timer: null,
|
||||
canGetCode: true, // 是否可以执行验证码操作
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.checkKeepRunning()
|
||||
},
|
||||
watch: {
|
||||
seconds: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.secNum = n
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
checkKeepRunning() {
|
||||
// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
|
||||
let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
|
||||
if(!lastTimestamp) return this.changeEvent(this.startText)
|
||||
// 当前秒的时间戳
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
|
||||
if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
|
||||
// 剩余尚未执行完的倒计秒数
|
||||
this.secNum = lastTimestamp - nowTimestamp
|
||||
// 清除本地保存的变量
|
||||
uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
|
||||
// 开始倒计时
|
||||
this.start()
|
||||
} else {
|
||||
// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
|
||||
this.changeEvent(this.startText)
|
||||
}
|
||||
},
|
||||
// 开始倒计时
|
||||
start() {
|
||||
// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
|
||||
if(this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
this.$emit('start')
|
||||
this.canGetCode = false
|
||||
// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
|
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
|
||||
this.timer = setInterval(() => {
|
||||
if (--this.secNum) {
|
||||
// 用当前倒计时的秒数替换提示字符串中的"x"字母
|
||||
this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
|
||||
} else {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
this.changeEvent(this.endText)
|
||||
this.secNum = this.seconds
|
||||
this.$emit('end')
|
||||
this.canGetCode = true
|
||||
}
|
||||
}, 1000)
|
||||
this.setTimeToStorage()
|
||||
},
|
||||
// 重置,可以让用户再次获取验证码
|
||||
reset() {
|
||||
this.canGetCode = true
|
||||
clearInterval(this.timer)
|
||||
this.secNum = this.seconds
|
||||
this.changeEvent(this.endText)
|
||||
},
|
||||
changeEvent(text) {
|
||||
this.$emit('change', text)
|
||||
},
|
||||
// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
|
||||
setTimeToStorage() {
|
||||
if(!this.keepRunning || !this.timer) return
|
||||
// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
|
||||
// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
|
||||
if(this.secNum > 0 && this.secNum <= this.seconds) {
|
||||
// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
|
||||
let nowTimestamp = Math.floor((+ new Date()) / 1000)
|
||||
// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
|
||||
uni.setStorage({
|
||||
key: this.uniqueKey + '_$uCountDownTimestamp',
|
||||
data: nowTimestamp + Number(this.secNum)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
|
||||
beforeDestroy() {
|
||||
this.setTimeToStorage()
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
</style>
|
||||
116
uni_modules/uview-ui/components/u-datetime-picker/props.js
Normal file
116
uni_modules/uview-ui/components/u-datetime-picker/props.js
Normal file
@@ -0,0 +1,116 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否打开组件
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.datetimePicker.show
|
||||
},
|
||||
// 是否展示顶部的操作栏
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.datetimePicker.showToolbar
|
||||
},
|
||||
// 绑定值
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.datetimePicker.value
|
||||
},
|
||||
// 顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.title
|
||||
},
|
||||
// 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.mode
|
||||
},
|
||||
// 可选的最大时间
|
||||
maxDate: {
|
||||
type: Number,
|
||||
// 最大默认值为后10年
|
||||
default: uni.$u.props.datetimePicker.maxDate
|
||||
},
|
||||
// 可选的最小时间
|
||||
minDate: {
|
||||
type: Number,
|
||||
// 最小默认值为前10年
|
||||
default: uni.$u.props.datetimePicker.minDate
|
||||
},
|
||||
// 可选的最小小时,仅mode=time有效
|
||||
minHour: {
|
||||
type: Number,
|
||||
default: uni.$u.props.datetimePicker.minHour
|
||||
},
|
||||
// 可选的最大小时,仅mode=time有效
|
||||
maxHour: {
|
||||
type: Number,
|
||||
default: uni.$u.props.datetimePicker.maxHour
|
||||
},
|
||||
// 可选的最小分钟,仅mode=time有效
|
||||
minMinute: {
|
||||
type: Number,
|
||||
default: uni.$u.props.datetimePicker.minMinute
|
||||
},
|
||||
// 可选的最大分钟,仅mode=time有效
|
||||
maxMinute: {
|
||||
type: Number,
|
||||
default: uni.$u.props.datetimePicker.maxMinute
|
||||
},
|
||||
// 选项过滤函数
|
||||
filter: {
|
||||
type: [Function, null],
|
||||
default: uni.$u.props.datetimePicker.filter
|
||||
},
|
||||
// 选项格式化函数
|
||||
formatter: {
|
||||
type: [Function, null],
|
||||
default: uni.$u.props.datetimePicker.formatter
|
||||
},
|
||||
// 是否显示加载中状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.datetimePicker.loading
|
||||
},
|
||||
// 各列中,单个选项的高度
|
||||
itemHeight: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.datetimePicker.itemHeight
|
||||
},
|
||||
// 取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.cancelText
|
||||
},
|
||||
// 确认按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.confirmText
|
||||
},
|
||||
// 取消按钮的颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.cancelColor
|
||||
},
|
||||
// 确认按钮的颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.datetimePicker.confirmColor
|
||||
},
|
||||
// 每列中可见选项的数量
|
||||
visibleItemCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.datetimePicker.visibleItemCount
|
||||
},
|
||||
// 是否允许点击遮罩关闭选择器
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.datetimePicker.closeOnClickOverlay
|
||||
},
|
||||
// 各列的默认索引
|
||||
defaultIndex: {
|
||||
type: Array,
|
||||
default: uni.$u.props.datetimePicker.defaultIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
<template>
|
||||
<u-picker
|
||||
ref="picker"
|
||||
:show="show"
|
||||
:closeOnClickOverlay="closeOnClickOverlay"
|
||||
:columns="columns"
|
||||
:title="title"
|
||||
:itemHeight="itemHeight"
|
||||
:showToolbar="showToolbar"
|
||||
:visibleItemCount="visibleItemCount"
|
||||
:defaultIndex="innerDefaultIndex"
|
||||
:cancelText="cancelText"
|
||||
:confirmText="confirmText"
|
||||
:cancelColor="cancelColor"
|
||||
:confirmColor="confirmColor"
|
||||
@close="close"
|
||||
@cancel="cancel"
|
||||
@confirm="confirm"
|
||||
@change="change"
|
||||
>
|
||||
</u-picker>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function times(n, iteratee) {
|
||||
let index = -1
|
||||
const result = Array(n < 0 ? 0 : n)
|
||||
while (++index < n) {
|
||||
result[index] = iteratee(index)
|
||||
}
|
||||
return result
|
||||
}
|
||||
import props from './props.js';
|
||||
import dayjs from '../../libs/util/dayjs.js';
|
||||
/**
|
||||
* DatetimePicker 时间日期选择器
|
||||
* @description 此选择器用于时间日期
|
||||
* @tutorial https://www.uviewui.com/components/datetimePicker.html
|
||||
* @property {Boolean} show 用于控制选择器的弹出与收起 ( 默认 false )
|
||||
* @property {Boolean} showToolbar 是否显示顶部的操作栏 ( 默认 true )
|
||||
* @property {String | Number} value 绑定值
|
||||
* @property {String} title 顶部标题
|
||||
* @property {String} mode 展示格式 mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择 ( 默认 ‘datetime )
|
||||
* @property {Number} maxDate 可选的最大时间 默认值为后10年
|
||||
* @property {Number} minDate 可选的最小时间 默认值为前10年
|
||||
* @property {Number} minHour 可选的最小小时,仅mode=time有效 ( 默认 0 )
|
||||
* @property {Number} maxHour 可选的最大小时,仅mode=time有效 ( 默认 23 )
|
||||
* @property {Number} minMinute 可选的最小分钟,仅mode=time有效 ( 默认 0 )
|
||||
* @property {Number} maxMinute 可选的最大分钟,仅mode=time有效 ( 默认 59 )
|
||||
* @property {Function} filter 选项过滤函数
|
||||
* @property {Function} formatter 选项格式化函数
|
||||
* @property {Boolean} loading 是否显示加载中状态 ( 默认 false )
|
||||
* @property {String | Number} itemHeight 各列中,单个选项的高度 ( 默认 44 )
|
||||
* @property {String} cancelText 取消按钮的文字 ( 默认 '取消' )
|
||||
* @property {String} confirmText 确认按钮的文字 ( 默认 '确认' )
|
||||
* @property {String} cancelColor 取消按钮的颜色 ( 默认 '#909193' )
|
||||
* @property {String} confirmColor 确认按钮的颜色 ( 默认 '#3c9cff' )
|
||||
* @property {String | Number} visibleItemCount 每列中可见选项的数量 ( 默认 5 )
|
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器 ( 默认 false )
|
||||
* @property {Array} defaultIndex 各列的默认索引
|
||||
* @event {Function} close 关闭选择器时触发
|
||||
* @event {Function} confirm 点击确定按钮,返回当前选择的值
|
||||
* @event {Function} change 当选择值变化时触发
|
||||
* @event {Function} cancel 点击取消按钮
|
||||
* @example <u-datetime-picker :show="show" :value="value1" mode="datetime" ></u-datetime-picker>
|
||||
*/
|
||||
export default {
|
||||
name: 'datetime-picker',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
columns: [],
|
||||
innerDefaultIndex: [],
|
||||
innerFormatter: (type, value) => value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.updateColumnValue(this.innerValue)
|
||||
}
|
||||
},
|
||||
propsChange() {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
|
||||
propsChange() {
|
||||
return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.innerValue = this.correctValue(this.value)
|
||||
this.updateColumnValue(this.innerValue)
|
||||
},
|
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||||
setFormatter(e) {
|
||||
this.innerFormatter = e
|
||||
},
|
||||
// 关闭选择器
|
||||
close() {
|
||||
if (this.closeOnClickOverlay) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
// 点击工具栏的取消按钮
|
||||
cancel() {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击工具栏的确定按钮
|
||||
confirm() {
|
||||
this.$emit('confirm', {
|
||||
value: this.innerValue,
|
||||
mode: this.mode
|
||||
})
|
||||
this.$emit('input', this.innerValue)
|
||||
},
|
||||
//用正则截取输出值,当出现多组数字时,抛出错误
|
||||
intercept(e,type){
|
||||
let judge = e.match(/\d+/g)
|
||||
//判断是否掺杂数字
|
||||
if(judge.length>1){
|
||||
uni.$u.error("请勿在过滤或格式化函数时添加数字")
|
||||
return 0
|
||||
}else if(type&&judge[0].length==4){//判断是否是年份
|
||||
return judge[0]
|
||||
}else if(judge[0].length>2){
|
||||
uni.$u.error("请勿在过滤或格式化函数时添加数字")
|
||||
return 0
|
||||
}else{
|
||||
return judge[0]
|
||||
}
|
||||
},
|
||||
// 列发生变化时触发
|
||||
change(e) {
|
||||
const { indexs, values } = e
|
||||
let selectValue = ''
|
||||
if(this.mode === 'time') {
|
||||
// 根据value各列索引,从各列数组中,取出当前时间的选中值
|
||||
selectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`
|
||||
} else {
|
||||
// 将选择的值转为数值,比如'03'转为数值的3,'2019'转为数值的2019
|
||||
const year = parseInt(this.intercept(values[0][indexs[0]],'year'))
|
||||
const month = parseInt(this.intercept(values[1][indexs[1]]))
|
||||
let date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)
|
||||
let hour = 0, minute = 0
|
||||
// 此月份的最大天数
|
||||
const maxDate = dayjs(`${year}-${month}`).daysInMonth()
|
||||
// year-month模式下,date不会出现在列中,设置为1,为了符合后边需要减1的需求
|
||||
if (this.mode === 'year-month') {
|
||||
date = 1
|
||||
}
|
||||
// 不允许超过maxDate值
|
||||
date = Math.min(maxDate, date)
|
||||
if (this.mode === 'datetime') {
|
||||
hour = parseInt(this.intercept(values[3][indexs[3]]))
|
||||
minute = parseInt(this.intercept(values[4][indexs[4]]))
|
||||
}
|
||||
// 转为时间模式
|
||||
selectValue = Number(new Date(year, month - 1, date, hour, minute))
|
||||
}
|
||||
// 取出准确的合法值,防止超越边界的情况
|
||||
selectValue = this.correctValue(selectValue)
|
||||
this.innerValue = selectValue
|
||||
this.updateColumnValue(selectValue)
|
||||
// 发出change时间,value为当前选中的时间戳
|
||||
this.$emit('change', {
|
||||
value: selectValue,
|
||||
// #ifndef MP-WEIXIN || MP-TOUTIAO
|
||||
// 微信小程序不能传递this实例,会因为循环引用而报错
|
||||
picker: this.$refs.picker,
|
||||
// #endif
|
||||
mode: this.mode
|
||||
})
|
||||
},
|
||||
// 更新各列的值,进行补0、格式化等操作
|
||||
updateColumnValue(value) {
|
||||
this.innerValue = value
|
||||
this.updateColumns()
|
||||
this.updateIndexs(value)
|
||||
},
|
||||
// 更新索引
|
||||
updateIndexs(value) {
|
||||
let values = []
|
||||
const formatter = this.formatter || this.innerFormatter
|
||||
const padZero = uni.$u.padZero
|
||||
if (this.mode === 'time') {
|
||||
// 将time模式的时间用:分隔成数组
|
||||
const timeArr = value.split(':')
|
||||
// 使用formatter格式化方法进行管道处理
|
||||
values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]
|
||||
} else {
|
||||
const date = new Date(value)
|
||||
values = [
|
||||
formatter('year', `${dayjs(value).year()}`),
|
||||
// 月份补0
|
||||
formatter('month', padZero(dayjs(value).month() + 1))
|
||||
]
|
||||
if (this.mode === 'date') {
|
||||
// date模式,需要添加天列
|
||||
values.push(formatter('day', padZero(dayjs(value).date())))
|
||||
}
|
||||
if (this.mode === 'datetime') {
|
||||
// 数组的push方法,可以写入多个参数
|
||||
values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute())))
|
||||
}
|
||||
}
|
||||
|
||||
// 根据当前各列的所有值,从各列默认值中找到默认值在各列中的索引
|
||||
const indexs = this.columns.map((column, index) => {
|
||||
// 通过取大值,可以保证不会出现找不到索引的-1情况
|
||||
return Math.max(0, column.findIndex(item => item === values[index]))
|
||||
})
|
||||
this.innerDefaultIndex = indexs
|
||||
},
|
||||
// 更新各列的值
|
||||
updateColumns() {
|
||||
const formatter = this.formatter || this.innerFormatter
|
||||
// 获取各列的值,并且map后,对各列的具体值进行补0操作
|
||||
const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))
|
||||
this.columns = results
|
||||
},
|
||||
getOriginColumns() {
|
||||
// 生成各列的值
|
||||
const results = this.getRanges().map(({ type, range }) => {
|
||||
let values = times(range[1] - range[0] + 1, (index) => {
|
||||
let value = range[0] + index
|
||||
value = type === 'year' ? `${value}` : uni.$u.padZero(value)
|
||||
return value
|
||||
})
|
||||
// 进行过滤
|
||||
if (this.filter) {
|
||||
values = this.filter(type, values)
|
||||
}
|
||||
return { type, values }
|
||||
})
|
||||
return results
|
||||
},
|
||||
// 通过最大值和最小值生成数组
|
||||
generateArray(start, end) {
|
||||
return Array.from(new Array(end + 1).keys()).slice(start)
|
||||
},
|
||||
// 得出合法的时间
|
||||
correctValue(value) {
|
||||
const isDateMode = this.mode !== 'time'
|
||||
if (isDateMode && !uni.$u.test.date(value)) {
|
||||
// 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
|
||||
value = this.minDate
|
||||
} else if (!isDateMode && !value) {
|
||||
// 如果是时间类型,而又没有默认值的话,就用最小时间
|
||||
value = `${uni.$u.padZero(this.minHour)}:${uni.$u.padZero(this.minMinute)}`
|
||||
}
|
||||
// 时间类型
|
||||
if (!isDateMode) {
|
||||
if (String(value).indexOf(':') === -1) return uni.$u.error('时间错误,请传递如12:24的格式')
|
||||
let [hour, minute] = value.split(':')
|
||||
// 对时间补零,同时控制在最小值和最大值之间
|
||||
hour = uni.$u.padZero(uni.$u.range(this.minHour, this.maxHour, Number(hour)))
|
||||
minute = uni.$u.padZero(uni.$u.range(this.minMinute, this.maxMinute, Number(minute)))
|
||||
return `${ hour }:${ minute }`
|
||||
} else {
|
||||
// 如果是日期格式,控制在最小日期和最大日期之间
|
||||
value = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value
|
||||
value = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value
|
||||
return value
|
||||
}
|
||||
},
|
||||
// 获取每列的最大和最小值
|
||||
getRanges() {
|
||||
if (this.mode === 'time') {
|
||||
return [
|
||||
{
|
||||
type: 'hour',
|
||||
range: [this.minHour, this.maxHour],
|
||||
},
|
||||
{
|
||||
type: 'minute',
|
||||
range: [this.minMinute, this.maxMinute],
|
||||
},
|
||||
];
|
||||
}
|
||||
const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);
|
||||
const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);
|
||||
const result = [
|
||||
{
|
||||
type: 'year',
|
||||
range: [minYear, maxYear],
|
||||
},
|
||||
{
|
||||
type: 'month',
|
||||
range: [minMonth, maxMonth],
|
||||
},
|
||||
{
|
||||
type: 'day',
|
||||
range: [minDate, maxDate],
|
||||
},
|
||||
{
|
||||
type: 'hour',
|
||||
range: [minHour, maxHour],
|
||||
},
|
||||
{
|
||||
type: 'minute',
|
||||
range: [minMinute, maxMinute],
|
||||
},
|
||||
];
|
||||
if (this.mode === 'date')
|
||||
result.splice(3, 2);
|
||||
if (this.mode === 'year-month')
|
||||
result.splice(2, 3);
|
||||
return result;
|
||||
},
|
||||
// 根据minDate、maxDate、minHour、maxHour等边界值,判断各列的开始和结束边界值
|
||||
getBoundary(type, innerValue) {
|
||||
const value = new Date(innerValue)
|
||||
const boundary = new Date(this[`${type}Date`])
|
||||
const year = dayjs(boundary).year()
|
||||
let month = 1
|
||||
let date = 1
|
||||
let hour = 0
|
||||
let minute = 0
|
||||
if (type === 'max') {
|
||||
month = 12
|
||||
// 月份的天数
|
||||
date = dayjs(value).daysInMonth()
|
||||
hour = 23
|
||||
minute = 59
|
||||
}
|
||||
// 获取边界值,逻辑是:当年达到了边界值(最大或最小年),就检查月允许的最大和最小值,以此类推
|
||||
if (dayjs(value).year() === year) {
|
||||
month = dayjs(boundary).month() + 1
|
||||
if (dayjs(value).month() + 1 === month) {
|
||||
date = dayjs(boundary).date()
|
||||
if (dayjs(value).date() === date) {
|
||||
hour = dayjs(boundary).hour()
|
||||
if (dayjs(value).hour() === hour) {
|
||||
minute = dayjs(boundary).minute()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
[`${type}Year`]: year,
|
||||
[`${type}Month`]: month,
|
||||
[`${type}Date`]: date,
|
||||
[`${type}Hour`]: hour,
|
||||
[`${type}Minute`]: minute
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
</style>
|
||||
214
uni_modules/uview-ui/components/u-icon/icons.js
Normal file
214
uni_modules/uview-ui/components/u-icon/icons.js
Normal file
@@ -0,0 +1,214 @@
|
||||
export default {
|
||||
'uicon-level': '\ue693',
|
||||
'uicon-column-line': '\ue68e',
|
||||
'uicon-checkbox-mark': '\ue807',
|
||||
'uicon-folder': '\ue7f5',
|
||||
'uicon-movie': '\ue7f6',
|
||||
'uicon-star-fill': '\ue669',
|
||||
'uicon-star': '\ue65f',
|
||||
'uicon-phone-fill': '\ue64f',
|
||||
'uicon-phone': '\ue622',
|
||||
'uicon-apple-fill': '\ue881',
|
||||
'uicon-chrome-circle-fill': '\ue885',
|
||||
'uicon-backspace': '\ue67b',
|
||||
'uicon-attach': '\ue632',
|
||||
'uicon-cut': '\ue948',
|
||||
'uicon-empty-car': '\ue602',
|
||||
'uicon-empty-coupon': '\ue682',
|
||||
'uicon-empty-address': '\ue646',
|
||||
'uicon-empty-favor': '\ue67c',
|
||||
'uicon-empty-permission': '\ue686',
|
||||
'uicon-empty-news': '\ue687',
|
||||
'uicon-empty-search': '\ue664',
|
||||
'uicon-github-circle-fill': '\ue887',
|
||||
'uicon-rmb': '\ue608',
|
||||
'uicon-person-delete-fill': '\ue66a',
|
||||
'uicon-reload': '\ue788',
|
||||
'uicon-order': '\ue68f',
|
||||
'uicon-server-man': '\ue6bc',
|
||||
'uicon-search': '\ue62a',
|
||||
'uicon-fingerprint': '\ue955',
|
||||
'uicon-more-dot-fill': '\ue630',
|
||||
'uicon-scan': '\ue662',
|
||||
'uicon-share-square': '\ue60b',
|
||||
'uicon-map': '\ue61d',
|
||||
'uicon-map-fill': '\ue64e',
|
||||
'uicon-tags': '\ue629',
|
||||
'uicon-tags-fill': '\ue651',
|
||||
'uicon-bookmark-fill': '\ue63b',
|
||||
'uicon-bookmark': '\ue60a',
|
||||
'uicon-eye': '\ue613',
|
||||
'uicon-eye-fill': '\ue641',
|
||||
'uicon-mic': '\ue64a',
|
||||
'uicon-mic-off': '\ue649',
|
||||
'uicon-calendar': '\ue66e',
|
||||
'uicon-calendar-fill': '\ue634',
|
||||
'uicon-trash': '\ue623',
|
||||
'uicon-trash-fill': '\ue658',
|
||||
'uicon-play-left': '\ue66d',
|
||||
'uicon-play-right': '\ue610',
|
||||
'uicon-minus': '\ue618',
|
||||
'uicon-plus': '\ue62d',
|
||||
'uicon-info': '\ue653',
|
||||
'uicon-info-circle': '\ue7d2',
|
||||
'uicon-info-circle-fill': '\ue64b',
|
||||
'uicon-question': '\ue715',
|
||||
'uicon-error': '\ue6d3',
|
||||
'uicon-close': '\ue685',
|
||||
'uicon-checkmark': '\ue6a8',
|
||||
'uicon-android-circle-fill': '\ue67e',
|
||||
'uicon-android-fill': '\ue67d',
|
||||
'uicon-ie': '\ue87b',
|
||||
'uicon-IE-circle-fill': '\ue889',
|
||||
'uicon-google': '\ue87a',
|
||||
'uicon-google-circle-fill': '\ue88a',
|
||||
'uicon-setting-fill': '\ue872',
|
||||
'uicon-setting': '\ue61f',
|
||||
'uicon-minus-square-fill': '\ue855',
|
||||
'uicon-plus-square-fill': '\ue856',
|
||||
'uicon-heart': '\ue7df',
|
||||
'uicon-heart-fill': '\ue851',
|
||||
'uicon-camera': '\ue7d7',
|
||||
'uicon-camera-fill': '\ue870',
|
||||
'uicon-more-circle': '\ue63e',
|
||||
'uicon-more-circle-fill': '\ue645',
|
||||
'uicon-chat': '\ue620',
|
||||
'uicon-chat-fill': '\ue61e',
|
||||
'uicon-bag-fill': '\ue617',
|
||||
'uicon-bag': '\ue619',
|
||||
'uicon-error-circle-fill': '\ue62c',
|
||||
'uicon-error-circle': '\ue624',
|
||||
'uicon-close-circle': '\ue63f',
|
||||
'uicon-close-circle-fill': '\ue637',
|
||||
'uicon-checkmark-circle': '\ue63d',
|
||||
'uicon-checkmark-circle-fill': '\ue635',
|
||||
'uicon-question-circle-fill': '\ue666',
|
||||
'uicon-question-circle': '\ue625',
|
||||
'uicon-share': '\ue631',
|
||||
'uicon-share-fill': '\ue65e',
|
||||
'uicon-shopping-cart': '\ue621',
|
||||
'uicon-shopping-cart-fill': '\ue65d',
|
||||
'uicon-bell': '\ue609',
|
||||
'uicon-bell-fill': '\ue640',
|
||||
'uicon-list': '\ue650',
|
||||
'uicon-list-dot': '\ue616',
|
||||
'uicon-zhihu': '\ue6ba',
|
||||
'uicon-zhihu-circle-fill': '\ue709',
|
||||
'uicon-zhifubao': '\ue6b9',
|
||||
'uicon-zhifubao-circle-fill': '\ue6b8',
|
||||
'uicon-weixin-circle-fill': '\ue6b1',
|
||||
'uicon-weixin-fill': '\ue6b2',
|
||||
'uicon-twitter-circle-fill': '\ue6ab',
|
||||
'uicon-twitter': '\ue6aa',
|
||||
'uicon-taobao-circle-fill': '\ue6a7',
|
||||
'uicon-taobao': '\ue6a6',
|
||||
'uicon-weibo-circle-fill': '\ue6a5',
|
||||
'uicon-weibo': '\ue6a4',
|
||||
'uicon-qq-fill': '\ue6a1',
|
||||
'uicon-qq-circle-fill': '\ue6a0',
|
||||
'uicon-moments-circel-fill': '\ue69a',
|
||||
'uicon-moments': '\ue69b',
|
||||
'uicon-qzone': '\ue695',
|
||||
'uicon-qzone-circle-fill': '\ue696',
|
||||
'uicon-baidu-circle-fill': '\ue680',
|
||||
'uicon-baidu': '\ue681',
|
||||
'uicon-facebook-circle-fill': '\ue68a',
|
||||
'uicon-facebook': '\ue689',
|
||||
'uicon-car': '\ue60c',
|
||||
'uicon-car-fill': '\ue636',
|
||||
'uicon-warning-fill': '\ue64d',
|
||||
'uicon-warning': '\ue694',
|
||||
'uicon-clock-fill': '\ue638',
|
||||
'uicon-clock': '\ue60f',
|
||||
'uicon-edit-pen': '\ue612',
|
||||
'uicon-edit-pen-fill': '\ue66b',
|
||||
'uicon-email': '\ue611',
|
||||
'uicon-email-fill': '\ue642',
|
||||
'uicon-minus-circle': '\ue61b',
|
||||
'uicon-minus-circle-fill': '\ue652',
|
||||
'uicon-plus-circle': '\ue62e',
|
||||
'uicon-plus-circle-fill': '\ue661',
|
||||
'uicon-file-text': '\ue663',
|
||||
'uicon-file-text-fill': '\ue665',
|
||||
'uicon-pushpin': '\ue7e3',
|
||||
'uicon-pushpin-fill': '\ue86e',
|
||||
'uicon-grid': '\ue673',
|
||||
'uicon-grid-fill': '\ue678',
|
||||
'uicon-play-circle': '\ue647',
|
||||
'uicon-play-circle-fill': '\ue655',
|
||||
'uicon-pause-circle-fill': '\ue654',
|
||||
'uicon-pause': '\ue8fa',
|
||||
'uicon-pause-circle': '\ue643',
|
||||
'uicon-eye-off': '\ue648',
|
||||
'uicon-eye-off-outline': '\ue62b',
|
||||
'uicon-gift-fill': '\ue65c',
|
||||
'uicon-gift': '\ue65b',
|
||||
'uicon-rmb-circle-fill': '\ue657',
|
||||
'uicon-rmb-circle': '\ue677',
|
||||
'uicon-kefu-ermai': '\ue656',
|
||||
'uicon-server-fill': '\ue751',
|
||||
'uicon-coupon-fill': '\ue8c4',
|
||||
'uicon-coupon': '\ue8ae',
|
||||
'uicon-integral': '\ue704',
|
||||
'uicon-integral-fill': '\ue703',
|
||||
'uicon-home-fill': '\ue964',
|
||||
'uicon-home': '\ue965',
|
||||
'uicon-hourglass-half-fill': '\ue966',
|
||||
'uicon-hourglass': '\ue967',
|
||||
'uicon-account': '\ue628',
|
||||
'uicon-plus-people-fill': '\ue626',
|
||||
'uicon-minus-people-fill': '\ue615',
|
||||
'uicon-account-fill': '\ue614',
|
||||
'uicon-thumb-down-fill': '\ue726',
|
||||
'uicon-thumb-down': '\ue727',
|
||||
'uicon-thumb-up': '\ue733',
|
||||
'uicon-thumb-up-fill': '\ue72f',
|
||||
'uicon-lock-fill': '\ue979',
|
||||
'uicon-lock-open': '\ue973',
|
||||
'uicon-lock-opened-fill': '\ue974',
|
||||
'uicon-lock': '\ue97a',
|
||||
'uicon-red-packet-fill': '\ue690',
|
||||
'uicon-photo-fill': '\ue98b',
|
||||
'uicon-photo': '\ue98d',
|
||||
'uicon-volume-off-fill': '\ue659',
|
||||
'uicon-volume-off': '\ue644',
|
||||
'uicon-volume-fill': '\ue670',
|
||||
'uicon-volume': '\ue633',
|
||||
'uicon-red-packet': '\ue691',
|
||||
'uicon-download': '\ue63c',
|
||||
'uicon-arrow-up-fill': '\ue6b0',
|
||||
'uicon-arrow-down-fill': '\ue600',
|
||||
'uicon-play-left-fill': '\ue675',
|
||||
'uicon-play-right-fill': '\ue676',
|
||||
'uicon-rewind-left-fill': '\ue679',
|
||||
'uicon-rewind-right-fill': '\ue67a',
|
||||
'uicon-arrow-downward': '\ue604',
|
||||
'uicon-arrow-leftward': '\ue601',
|
||||
'uicon-arrow-rightward': '\ue603',
|
||||
'uicon-arrow-upward': '\ue607',
|
||||
'uicon-arrow-down': '\ue60d',
|
||||
'uicon-arrow-right': '\ue605',
|
||||
'uicon-arrow-left': '\ue60e',
|
||||
'uicon-arrow-up': '\ue606',
|
||||
'uicon-skip-back-left': '\ue674',
|
||||
'uicon-skip-forward-right': '\ue672',
|
||||
'uicon-rewind-right': '\ue66f',
|
||||
'uicon-rewind-left': '\ue671',
|
||||
'uicon-arrow-right-double': '\ue68d',
|
||||
'uicon-arrow-left-double': '\ue68c',
|
||||
'uicon-wifi-off': '\ue668',
|
||||
'uicon-wifi': '\ue667',
|
||||
'uicon-empty-data': '\ue62f',
|
||||
'uicon-empty-history': '\ue684',
|
||||
'uicon-empty-list': '\ue68b',
|
||||
'uicon-empty-page': '\ue627',
|
||||
'uicon-empty-order': '\ue639',
|
||||
'uicon-man': '\ue697',
|
||||
'uicon-woman': '\ue69c',
|
||||
'uicon-man-add': '\ue61c',
|
||||
'uicon-man-add-fill': '\ue64c',
|
||||
'uicon-man-delete': '\ue61a',
|
||||
'uicon-man-delete-fill': '\ue66a',
|
||||
'uicon-zh': '\ue70a',
|
||||
'uicon-en': '\ue692'
|
||||
}
|
||||
89
uni_modules/uview-ui/components/u-icon/props.js
Normal file
89
uni_modules/uview-ui/components/u-icon/props.js
Normal file
@@ -0,0 +1,89 @@
|
||||
export default {
|
||||
props: {
|
||||
// 图标类名
|
||||
name: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.name
|
||||
},
|
||||
// 图标颜色,可接受主题色
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.color
|
||||
},
|
||||
// 字体大小,单位px
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.size
|
||||
},
|
||||
// 是否显示粗体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.icon.bold
|
||||
},
|
||||
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.index
|
||||
},
|
||||
// 触摸图标时的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.hoverClass
|
||||
},
|
||||
// 自定义扩展前缀,方便用户扩展自己的图标库
|
||||
customPrefix: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.customPrefix
|
||||
},
|
||||
// 图标右边或者下面的文字
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.label
|
||||
},
|
||||
// label的位置,只能右边或者下边
|
||||
labelPos: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.labelPos
|
||||
},
|
||||
// label的大小
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.labelSize
|
||||
},
|
||||
// label的颜色
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.labelColor
|
||||
},
|
||||
// label与图标的距离
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.space
|
||||
},
|
||||
// 图片的mode
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: uni.$u.props.icon.imgMode
|
||||
},
|
||||
// 用于显示图片小图标时,图片的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.width
|
||||
},
|
||||
// 用于显示图片小图标时,图片的高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.height
|
||||
},
|
||||
// 用于解决某些情况下,让图标垂直居中的用途
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.icon.top
|
||||
},
|
||||
// 是否阻止事件传播
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.icon.stop
|
||||
}
|
||||
}
|
||||
}
|
||||
234
uni_modules/uview-ui/components/u-icon/u-icon.vue
Normal file
234
uni_modules/uview-ui/components/u-icon/u-icon.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-icon"
|
||||
@tap="clickHandler"
|
||||
:class="['u-icon--' + labelPos]"
|
||||
>
|
||||
<image
|
||||
class="u-icon__img"
|
||||
v-if="isImg"
|
||||
:src="name"
|
||||
:mode="imgMode"
|
||||
:style="[imgStyle, $u.addStyle(customStyle)]"
|
||||
></image>
|
||||
<text
|
||||
v-else
|
||||
class="u-icon__icon"
|
||||
:class="uClasses"
|
||||
:style="[iconStyle, $u.addStyle(customStyle)]"
|
||||
:hover-class="hoverClass"
|
||||
>{{icon}}</text>
|
||||
<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
|
||||
<text
|
||||
v-if="label !== ''"
|
||||
class="u-icon__label"
|
||||
:style="{
|
||||
color: labelColor,
|
||||
fontSize: $u.addUnit(labelSize),
|
||||
marginLeft: labelPos == 'right' ? $u.addUnit(space) : 0,
|
||||
marginTop: labelPos == 'bottom' ? $u.addUnit(space) : 0,
|
||||
marginRight: labelPos == 'left' ? $u.addUnit(space) : 0,
|
||||
marginBottom: labelPos == 'top' ? $u.addUnit(space) : 0,
|
||||
}"
|
||||
>{{ label }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
// nvue通过weex的dom模块引入字体,相关文档地址如下:
|
||||
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
|
||||
const fontUrl = 'https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf'
|
||||
const domModule = weex.requireModule('dom')
|
||||
domModule.addRule('fontFace', {
|
||||
'fontFamily': "uicon-iconfont",
|
||||
'src': `url('${fontUrl}')`
|
||||
})
|
||||
// #endif
|
||||
|
||||
// 引入图标名称,已经对应的unicode
|
||||
import icons from './icons'
|
||||
|
||||
import props from './props.js';;
|
||||
|
||||
/**
|
||||
* icon 图标
|
||||
* @description 基于字体的图标集,包含了大多数常见场景的图标。
|
||||
* @tutorial https://www.uviewui.com/components/icon.html
|
||||
* @property {String} name 图标名称,见示例图标集
|
||||
* @property {String} color 图标颜色,可接受主题色 (默认 color['u-content-color'] )
|
||||
* @property {String | Number} size 图标字体大小,单位px (默认 '16px' )
|
||||
* @property {Boolean} bold 是否显示粗体 (默认 false )
|
||||
* @property {String | Number} index 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
* @property {String} hoverClass 图标按下去的样式类,用法同uni的view组件的hoverClass参数,详情见官网
|
||||
* @property {String} customPrefix 自定义扩展前缀,方便用户扩展自己的图标库 (默认 'uicon' )
|
||||
* @property {String | Number} label 图标右侧的label文字
|
||||
* @property {String} labelPos label相对于图标的位置,只能right或bottom (默认 'right' )
|
||||
* @property {String | Number} labelSize label字体大小,单位px (默认 '15px' )
|
||||
* @property {String} labelColor 图标右侧的label文字颜色 ( 默认 color['u-content-color'] )
|
||||
* @property {String | Number} space label与图标的距离,单位px (默认 '3px' )
|
||||
* @property {String} imgMode 图片的mode
|
||||
* @property {String | Number} width 显示图片小图标时的宽度
|
||||
* @property {String | Number} height 显示图片小图标时的高度
|
||||
* @property {String | Number} top 图标在垂直方向上的定位 用于解决某些情况下,让图标垂直居中的用途 (默认 0 )
|
||||
* @property {Boolean} stop 是否阻止事件传播 (默认 false )
|
||||
* @property {Object} customStyle icon的样式,对象形式
|
||||
* @event {Function} click 点击图标时触发
|
||||
* @event {Function} touchstart 事件触摸时触发
|
||||
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-icon',
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
computed: {
|
||||
uClasses() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// // uView的自定义图标类名为u-iconfont
|
||||
// if (this.customPrefix == 'uicon') {
|
||||
// classes.push('u-iconfont')
|
||||
// } else {
|
||||
// classes.push(this.customPrefix)
|
||||
// }
|
||||
// 主题色,通过类配置
|
||||
if (this.color && uni.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: uni.$u.addUnit(this.size),
|
||||
lineHeight: uni.$u.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: uni.$u.addUnit(this.top)
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.color && !uni.$u.config.type.includes(this.color)) style.color = this.color
|
||||
|
||||
return style
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
return this.name.indexOf('/') !== -1
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {}
|
||||
// 如果设置width和height属性,则优先使用,否则使用size属性
|
||||
style.width = this.width ? uni.$u.addUnit(this.width) : uni.$u.addUnit(this.size)
|
||||
style.height = this.height ? uni.$u.addUnit(this.height) : uni.$u.addUnit(this.size)
|
||||
return style
|
||||
},
|
||||
// 通过图标名,查找对应的图标
|
||||
icon() {
|
||||
// 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码
|
||||
return icons['uicon-' + this.name] || this.name
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickHandler(e) {
|
||||
this.$emit('click', this.index)
|
||||
// 是否阻止事件冒泡
|
||||
this.stop && this.preventEvent(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
// 变量定义
|
||||
$u-icon-primary: $u-primary !default;
|
||||
$u-icon-success: $u-success !default;
|
||||
$u-icon-info: $u-info !default;
|
||||
$u-icon-warning: $u-warning !default;
|
||||
$u-icon-error: $u-error !default;
|
||||
$u-icon-label-line-height:1 !default;
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
// 非nvue下加载字体
|
||||
@font-face {
|
||||
font-family: 'uicon-iconfont';
|
||||
src: url('https://at.alicdn.com/t/font_2225171_8kdcwk4po24.ttf') format('truetype');
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
.u-icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
|
||||
&--left {
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--right {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--top {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-family: uicon-iconfont;
|
||||
position: relative;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
|
||||
&--primary {
|
||||
color: $u-icon-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-icon-success;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-icon-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-icon-warning;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: $u-icon-info;
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
/* #ifndef APP-NVUE */
|
||||
height: auto;
|
||||
will-change: transform;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
&__label {
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: $u-icon-label-line-height;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
84
uni_modules/uview-ui/components/u-image/props.js
Normal file
84
uni_modules/uview-ui/components/u-image/props.js
Normal file
@@ -0,0 +1,84 @@
|
||||
export default {
|
||||
props: {
|
||||
// 图片地址
|
||||
src: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.src
|
||||
},
|
||||
// 裁剪模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.mode
|
||||
},
|
||||
// 宽度,单位任意
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.image.width
|
||||
},
|
||||
// 高度,单位任意
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.image.height
|
||||
},
|
||||
// 图片形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.shape
|
||||
},
|
||||
// 圆角,单位任意
|
||||
radius: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.image.radius
|
||||
},
|
||||
// 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.lazyLoad
|
||||
},
|
||||
// 开启长按图片显示识别微信小程序码菜单
|
||||
showMenuByLongpress: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.showMenuByLongpress
|
||||
},
|
||||
// 加载中的图标,或者小图片
|
||||
loadingIcon: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.loadingIcon
|
||||
},
|
||||
// 加载失败的图标,或者小图片
|
||||
errorIcon: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.errorIcon
|
||||
},
|
||||
// 是否显示加载中的图标或者自定义的slot
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.showLoading
|
||||
},
|
||||
// 是否显示加载错误的图标或者自定义的slot
|
||||
showError: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.showError
|
||||
},
|
||||
// 是否需要淡入效果
|
||||
fade: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.fade
|
||||
},
|
||||
// 只支持网络资源,只对微信小程序有效
|
||||
webp: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.image.webp
|
||||
},
|
||||
// 过渡时间,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.image.duration
|
||||
},
|
||||
// 背景颜色,用于深色页面加载图片时,为了和背景色融合
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.image.bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
232
uni_modules/uview-ui/components/u-image/u-image.vue
Normal file
232
uni_modules/uview-ui/components/u-image/u-image.vue
Normal file
@@ -0,0 +1,232 @@
|
||||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:show="show"
|
||||
:duration="fade ? 1000 : 0"
|
||||
>
|
||||
<view
|
||||
class="u-image"
|
||||
@tap="onClick"
|
||||
:style="[wrapStyle, backgroundStyle]"
|
||||
>
|
||||
<image
|
||||
v-if="!isError"
|
||||
:src="src"
|
||||
:mode="mode"
|
||||
@error="onErrorHandler"
|
||||
@load="onLoadHandler"
|
||||
:show-menu-by-longpress="showMenuByLongpress"
|
||||
:lazy-load="lazyLoad"
|
||||
class="u-image__image"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '10000px' : $u.addUnit(radius),
|
||||
width: $u.addUnit(width),
|
||||
height: $u.addUnit(height)
|
||||
}"
|
||||
></image>
|
||||
<view
|
||||
v-if="showLoading && loading"
|
||||
class="u-image__loading"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius),
|
||||
backgroundColor: this.bgColor,
|
||||
width: $u.addUnit(width),
|
||||
height: $u.addUnit(height)
|
||||
}"
|
||||
>
|
||||
<slot name="loading">
|
||||
<u-icon
|
||||
:name="loadingIcon"
|
||||
:width="width"
|
||||
:height="height"
|
||||
></u-icon>
|
||||
</slot>
|
||||
</view>
|
||||
<view
|
||||
v-if="showError && isError && !loading"
|
||||
class="u-image__error"
|
||||
:style="{
|
||||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(radius),
|
||||
width: $u.addUnit(width),
|
||||
height: $u.addUnit(height)
|
||||
}"
|
||||
>
|
||||
<slot name="error">
|
||||
<u-icon
|
||||
:name="errorIcon"
|
||||
:width="width"
|
||||
:height="height"
|
||||
></u-icon>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Image 图片
|
||||
* @description 此组件为uni-app的image组件的加强版,在继承了原有功能外,还支持淡入动画、加载中、加载失败提示、圆角值和形状等。
|
||||
* @tutorial https://uviewui.com/components/image.html
|
||||
* @property {String} src 图片地址
|
||||
* @property {String} mode 裁剪模式,见官网说明 (默认 'aspectFill' )
|
||||
* @property {String | Number} width 宽度,单位任意,如果为数值,则为px单位 (默认 '300' )
|
||||
* @property {String | Number} height 高度,单位任意,如果为数值,则为px单位 (默认 '225' )
|
||||
* @property {String} shape 图片形状,circle-圆形,square-方形 (默认 'square' )
|
||||
* @property {String | Number} radius 圆角值,单位任意,如果为数值,则为px单位 (默认 0 )
|
||||
* @property {Boolean} lazyLoad 是否懒加载,仅微信小程序、App、百度小程序、字节跳动小程序有效 (默认 true )
|
||||
* @property {Boolean} showMenuByLongpress 是否开启长按图片显示识别小程序码菜单,仅微信小程序有效 (默认 true )
|
||||
* @property {String} loadingIcon 加载中的图标,或者小图片 (默认 'photo' )
|
||||
* @property {String} errorIcon 加载失败的图标,或者小图片 (默认 'error-circle' )
|
||||
* @property {Boolean} showLoading 是否显示加载中的图标或者自定义的slot (默认 true )
|
||||
* @property {Boolean} showError 是否显示加载错误的图标或者自定义的slot (默认 true )
|
||||
* @property {Boolean} fade 是否需要淡入效果 (默认 true )
|
||||
* @property {Boolean} webp 只支持网络资源,只对微信小程序有效 (默认 false )
|
||||
* @property {String | Number} duration 搭配fade参数的过渡时间,单位ms (默认 500 )
|
||||
* @property {String} bgColor 背景颜色,用于深色页面加载图片时,为了和背景色融合 (默认 '#f3f4f6' )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @event {Function} click 点击图片时触发
|
||||
* @event {Function} error 图片加载失败时触发
|
||||
* @event {Function} load 图片加载成功时触发
|
||||
* @example <u-image width="100%" height="300px" :src="src"></u-image>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-image',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 图片是否加载错误,如果是,则显示错误占位图
|
||||
isError: false,
|
||||
// 初始化组件时,默认为加载中状态
|
||||
loading: true,
|
||||
// 不透明度,为了实现淡入淡出的效果
|
||||
opacity: 1,
|
||||
// 过渡时间,因为props的值无法修改,故需要一个中间值
|
||||
durationTime: this.duration,
|
||||
// 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景
|
||||
backgroundStyle: {},
|
||||
// 用于fade模式的控制组件显示与否
|
||||
show: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
src: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
if (!n) {
|
||||
// 如果传入null或者'',或者false,或者undefined,标记为错误状态
|
||||
this.isError = true
|
||||
|
||||
} else {
|
||||
this.isError = false;
|
||||
this.loading = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wrapStyle() {
|
||||
let style = {};
|
||||
// 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位
|
||||
style.width = this.$u.addUnit(this.width);
|
||||
style.height = this.$u.addUnit(this.height);
|
||||
// 如果是显示圆形,设置一个很多的半径值即可
|
||||
style.borderRadius = this.shape == 'circle' ? '10000px' : uni.$u.addUnit(this.radius)
|
||||
// 如果设置圆角,必须要有hidden,否则可能圆角无效
|
||||
style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible'
|
||||
// if (this.fade) {
|
||||
// style.opacity = this.opacity
|
||||
// // nvue下,这几个属性必须要分开写
|
||||
// style.transitionDuration = `${this.durationTime}ms`
|
||||
// style.transitionTimingFunction = 'ease-in-out'
|
||||
// style.transitionProperty = 'opacity'
|
||||
// }
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
|
||||
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.show = true
|
||||
},
|
||||
methods: {
|
||||
// 点击图片
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// 图片加载失败
|
||||
onErrorHandler(err) {
|
||||
this.loading = false
|
||||
this.isError = true
|
||||
this.$emit('error', err)
|
||||
},
|
||||
// 图片加载完成,标记loading结束
|
||||
onLoadHandler(event) {
|
||||
this.loading = false
|
||||
this.isError = false
|
||||
this.$emit('load', event)
|
||||
this.removeBgColor()
|
||||
// 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色
|
||||
// 否则无需fade效果时,png图片依然能看到下方的背景色
|
||||
// if (!this.fade) return this.removeBgColor();
|
||||
// // 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的灰色),再改成1,是为了获得过渡效果
|
||||
// this.opacity = 0;
|
||||
// // 这里设置为0,是为了图片展示到背景全透明这个过程时间为0,延时之后延时之后重新设置为duration,是为了获得背景透明(灰色)
|
||||
// // 到图片展示的过程中的淡入效果
|
||||
// this.durationTime = 0;
|
||||
// // 延时50ms,否则在浏览器H5,过渡效果无效
|
||||
// setTimeout(() => {
|
||||
// this.durationTime = this.duration;
|
||||
// this.opacity = 1;
|
||||
// setTimeout(() => {
|
||||
// this.removeBgColor();
|
||||
// }, this.durationTime);
|
||||
// }, 50);
|
||||
},
|
||||
// 移除图片的背景色
|
||||
removeBgColor() {
|
||||
// 淡入动画过渡完成后,将背景设置为透明色,否则png图片会看到灰色的背景
|
||||
this.backgroundStyle = {
|
||||
backgroundColor: 'transparent'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
$u-image-error-top:0px !default;
|
||||
$u-image-error-left:0px !default;
|
||||
$u-image-error-width:100% !default;
|
||||
$u-image-error-hight:100% !default;
|
||||
$u-image-error-background-color:$u-bg-color !default;
|
||||
$u-image-error-color:$u-tips-color !default;
|
||||
$u-image-error-font-size: 46rpx !default;
|
||||
|
||||
.u-image {
|
||||
position: relative;
|
||||
transition: opacity 0.5s ease-in-out;
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__loading,
|
||||
&__error {
|
||||
position: absolute;
|
||||
top: $u-image-error-top;
|
||||
left: $u-image-error-left;
|
||||
width: $u-image-error-width;
|
||||
height: $u-image-error-hight;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: $u-image-error-background-color;
|
||||
color: $u-image-error-color;
|
||||
font-size: $u-image-error-font-size;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
187
uni_modules/uview-ui/components/u-input/props.js
Normal file
187
uni_modules/uview-ui/components/u-input/props.js
Normal file
@@ -0,0 +1,187 @@
|
||||
export default {
|
||||
props: {
|
||||
// 输入的值
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.value
|
||||
},
|
||||
// 输入框类型
|
||||
// number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数
|
||||
// idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序
|
||||
// digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序
|
||||
// text-文本输入键盘
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.type
|
||||
},
|
||||
// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,
|
||||
// 兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.fixed
|
||||
},
|
||||
// 是否禁用输入框
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.disabled
|
||||
},
|
||||
// 禁用状态时的背景色
|
||||
disabledColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.disabledColor
|
||||
},
|
||||
// 是否显示清除控件
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.clearable
|
||||
},
|
||||
// 是否密码类型
|
||||
password: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.password
|
||||
},
|
||||
// 最大输入长度,设置为 -1 的时候不限制最大长度
|
||||
maxlength: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.maxlength
|
||||
},
|
||||
// 输入框为空时的占位符
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.placeholder
|
||||
},
|
||||
// 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
|
||||
placeholderClass: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.placeholderClass
|
||||
},
|
||||
// 指定placeholder的样式
|
||||
placeholderStyle: {
|
||||
type: [String, Object],
|
||||
default: uni.$u.props.input.placeholderStyle
|
||||
},
|
||||
// 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效
|
||||
showWordLimit: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.showWordLimit
|
||||
},
|
||||
// 设置右下角按钮的文字,有效值:send|search|next|go|done,兼容性详见uni-app文档
|
||||
// https://uniapp.dcloud.io/component/input
|
||||
// https://uniapp.dcloud.io/component/textarea
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.confirmType
|
||||
},
|
||||
// 点击键盘右下角按钮时是否保持键盘不收起,H5无效
|
||||
confirmHold: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.confirmHold
|
||||
},
|
||||
// focus时,点击页面的时候不收起键盘,微信小程序有效
|
||||
holdKeyboard: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.holdKeyboard
|
||||
},
|
||||
// 自动获取焦点
|
||||
// 在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.focus
|
||||
},
|
||||
// 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效
|
||||
autoBlur: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.autoBlur
|
||||
},
|
||||
// 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效
|
||||
disableDefaultPadding: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.disableDefaultPadding
|
||||
},
|
||||
// 指定focus时光标的位置
|
||||
cursor: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.cursor
|
||||
},
|
||||
// 输入框聚焦时底部与键盘的距离
|
||||
cursorSpacing: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.cursorSpacing
|
||||
},
|
||||
// 光标起始位置,自动聚集时有效,需与selection-end搭配使用
|
||||
selectionStart: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.selectionStart
|
||||
},
|
||||
// 光标结束位置,自动聚集时有效,需与selection-start搭配使用
|
||||
selectionEnd: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.selectionEnd
|
||||
},
|
||||
// 键盘弹起时,是否自动上推页面
|
||||
adjustPosition: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.adjustPosition
|
||||
},
|
||||
// 输入框内容对齐方式,可选值为:left|center|right
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.inputAlign
|
||||
},
|
||||
// 输入框字体的大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.input.fontSize
|
||||
},
|
||||
// 输入框字体颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.color
|
||||
},
|
||||
// 输入框前置图标
|
||||
prefixIcon: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.prefixIcon
|
||||
},
|
||||
// 前置图标样式,对象或字符串
|
||||
prefixIconStyle: {
|
||||
type: [String, Object],
|
||||
default: uni.$u.props.input.prefixIconStyle
|
||||
},
|
||||
// 输入框后置图标
|
||||
suffixIcon: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.suffixIcon
|
||||
},
|
||||
// 后置图标样式,对象或字符串
|
||||
suffixIconStyle: {
|
||||
type: [String, Object],
|
||||
default: uni.$u.props.input.suffixIconStyle
|
||||
},
|
||||
// 边框类型,surround-四周边框,bottom-底部边框,none-无边框
|
||||
border: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.border
|
||||
},
|
||||
// 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.input.readonly
|
||||
},
|
||||
// 输入框形状,circle-圆形,square-方形
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.input.shape
|
||||
},
|
||||
// 用于处理或者过滤输入框内容的方法
|
||||
formatter: {
|
||||
type: [Function, null],
|
||||
default: uni.$u.props.input.formatter
|
||||
},
|
||||
// 是否忽略组件内对文本合成系统事件的处理
|
||||
ignoreCompositionEvent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
354
uni_modules/uview-ui/components/u-input/u-input.vue
Normal file
354
uni_modules/uview-ui/components/u-input/u-input.vue
Normal file
@@ -0,0 +1,354 @@
|
||||
<template>
|
||||
<view class="u-input" :class="inputClass" :style="[wrapperStyle]">
|
||||
<view class="u-input__content">
|
||||
<view
|
||||
class="u-input__content__prefix-icon"
|
||||
v-if="prefixIcon || $slots.prefix"
|
||||
>
|
||||
<slot name="prefix">
|
||||
<u-icon
|
||||
:name="prefixIcon"
|
||||
size="18"
|
||||
:customStyle="prefixIconStyle"
|
||||
></u-icon>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-input__content__field-wrapper" @tap="clickHandler">
|
||||
<!-- 根据uni-app的input组件文档,H5和APP中只要声明了password参数(无论true还是false),type均失效,此时
|
||||
为了防止type=number时,又存在password属性,type无效,此时需要设置password为undefined
|
||||
-->
|
||||
<input
|
||||
class="u-input__content__field-wrapper__field"
|
||||
:style="[inputStyle]"
|
||||
:type="type"
|
||||
:focus="focus"
|
||||
:cursor="cursor"
|
||||
:value="innerValue"
|
||||
:auto-blur="autoBlur"
|
||||
:disabled="disabled || readonly"
|
||||
:maxlength="maxlength"
|
||||
:placeholder="placeholder"
|
||||
:placeholder-style="placeholderStyle"
|
||||
:placeholder-class="placeholderClass"
|
||||
:confirm-type="confirmType"
|
||||
:confirm-hold="confirmHold"
|
||||
:hold-keyboard="holdKeyboard"
|
||||
:cursor-spacing="cursorSpacing"
|
||||
:adjust-position="adjustPosition"
|
||||
:selection-end="selectionEnd"
|
||||
:selection-start="selectionStart"
|
||||
:password="password || type === 'password' || false"
|
||||
:ignoreCompositionEvent="ignoreCompositionEvent"
|
||||
@input="onInput"
|
||||
@blur="onBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
@keyboardheightchange="onkeyboardheightchange"
|
||||
/>
|
||||
</view>
|
||||
<view
|
||||
class="u-input__content__clear"
|
||||
v-if="isShowClear"
|
||||
@tap="onClear"
|
||||
>
|
||||
<u-icon
|
||||
name="close"
|
||||
size="11"
|
||||
color="#ffffff"
|
||||
customStyle="line-height: 12px"
|
||||
></u-icon>
|
||||
</view>
|
||||
<view
|
||||
class="u-input__content__subfix-icon"
|
||||
v-if="suffixIcon || $slots.suffix"
|
||||
>
|
||||
<slot name="suffix">
|
||||
<u-icon
|
||||
:name="suffixIcon"
|
||||
size="18"
|
||||
:customStyle="suffixIconStyle"
|
||||
></u-icon>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from "./props.js";
|
||||
/**
|
||||
* Input 输入框
|
||||
* @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
|
||||
* @tutorial https://uviewui.com/components/input.html
|
||||
* @property {String | Number} value 输入的值
|
||||
* @property {String} type 输入框类型,见上方说明 ( 默认 'text' )
|
||||
* @property {Boolean} fixed 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true,兼容性:微信小程序、百度小程序、字节跳动小程序、QQ小程序 ( 默认 false )
|
||||
* @property {Boolean} disabled 是否禁用输入框 ( 默认 false )
|
||||
* @property {String} disabledColor 禁用状态时的背景色( 默认 '#f5f7fa' )
|
||||
* @property {Boolean} clearable 是否显示清除控件 ( 默认 false )
|
||||
* @property {Boolean} password 是否密码类型 ( 默认 false )
|
||||
* @property {String | Number} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度 ( 默认 -1 )
|
||||
* @property {String} placeholder 输入框为空时的占位符
|
||||
* @property {String} placeholderClass 指定placeholder的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/ ( 默认 'input-placeholder' )
|
||||
* @property {String | Object} placeholderStyle 指定placeholder的样式,字符串/对象形式,如"color: red;"
|
||||
* @property {Boolean} showWordLimit 是否显示输入字数统计,只在 type ="text"或type ="textarea"时有效 ( 默认 false )
|
||||
* @property {String} confirmType 设置右下角按钮的文字,兼容性详见uni-app文档 ( 默认 'done' )
|
||||
* @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起,H5无效 ( 默认 false )
|
||||
* @property {Boolean} holdKeyboard focus时,点击页面的时候不收起键盘,微信小程序有效 ( 默认 false )
|
||||
* @property {Boolean} focus 自动获取焦点,在 H5 平台能否聚焦以及软键盘是否跟随弹出,取决于当前浏览器本身的实现。nvue 页面不支持,需使用组件的 focus()、blur() 方法控制焦点 ( 默认 false )
|
||||
* @property {Boolean} autoBlur 键盘收起时,是否自动失去焦点,目前仅App3.0.0+有效 ( 默认 false )
|
||||
* @property {Boolean} disableDefaultPadding 是否去掉 iOS 下的默认内边距,仅微信小程序,且type=textarea时有效 ( 默认 false )
|
||||
* @property {String | Number} cursor 指定focus时光标的位置( 默认 -1 )
|
||||
* @property {String | Number} cursorSpacing 输入框聚焦时底部与键盘的距离 ( 默认 30 )
|
||||
* @property {String | Number} selectionStart 光标起始位置,自动聚集时有效,需与selection-end搭配使用 ( 默认 -1 )
|
||||
* @property {String | Number} selectionEnd 光标结束位置,自动聚集时有效,需与selection-start搭配使用 ( 默认 -1 )
|
||||
* @property {Boolean} adjustPosition 键盘弹起时,是否自动上推页面 ( 默认 true )
|
||||
* @property {String} inputAlign 输入框内容对齐方式( 默认 'left' )
|
||||
* @property {String | Number} fontSize 输入框字体的大小 ( 默认 '15px' )
|
||||
* @property {String} color 输入框字体颜色 ( 默认 '#303133' )
|
||||
* @property {Function} formatter 内容式化函数
|
||||
* @property {String} prefixIcon 输入框前置图标
|
||||
* @property {String | Object} prefixIconStyle 前置图标样式,对象或字符串
|
||||
* @property {String} suffixIcon 输入框后置图标
|
||||
* @property {String | Object} suffixIconStyle 后置图标样式,对象或字符串
|
||||
* @property {String} border 边框类型,surround-四周边框,bottom-底部边框,none-无边框 ( 默认 'surround' )
|
||||
* @property {Boolean} readonly 是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
|
||||
* @property {String} shape 输入框形状,circle-圆形,square-方形 ( 默认 'square' )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @property {Boolean} ignoreCompositionEvent 是否忽略组件内对文本合成系统事件的处理。
|
||||
* @example <u-input v-model="value" :password="true" suffix-icon="lock-fill" />
|
||||
*/
|
||||
export default {
|
||||
name: "u-input",
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 输入框的值
|
||||
innerValue: "",
|
||||
// 是否处于获得焦点状态
|
||||
focused: false,
|
||||
// value是否第一次变化,在watch中,由于加入immediate属性,会在第一次触发,此时不应该认为value发生了变化
|
||||
firstChange: true,
|
||||
// value绑定值的变化是由内部还是外部引起的
|
||||
changeFromInner: false,
|
||||
// 过滤处理方法
|
||||
innerFormatter: value => value
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
this.innerValue = newVal;
|
||||
/* #ifdef H5 */
|
||||
// 在H5中,外部value变化后,修改input中的值,不会触发@input事件,此时手动调用值变化方法
|
||||
if (
|
||||
this.firstChange === false &&
|
||||
this.changeFromInner === false
|
||||
) {
|
||||
this.valueChange();
|
||||
}
|
||||
/* #endif */
|
||||
this.firstChange = false;
|
||||
// 重置changeFromInner的值为false,标识下一次引起默认为外部引起的
|
||||
this.changeFromInner = false;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 是否显示清除控件
|
||||
isShowClear() {
|
||||
const { clearable, readonly, focused, innerValue } = this;
|
||||
return !!clearable && !readonly && !!focused && innerValue !== "";
|
||||
},
|
||||
// 组件的类名
|
||||
inputClass() {
|
||||
let classes = [],
|
||||
{ border, disabled, shape } = this;
|
||||
border === "surround" &&
|
||||
(classes = classes.concat(["u-border", "u-input--radius"]));
|
||||
classes.push(`u-input--${shape}`);
|
||||
border === "bottom" &&
|
||||
(classes = classes.concat([
|
||||
"u-border-bottom",
|
||||
"u-input--no-radius",
|
||||
]));
|
||||
return classes.join(" ");
|
||||
},
|
||||
// 组件的样式
|
||||
wrapperStyle() {
|
||||
const style = {};
|
||||
// 禁用状态下,被背景色加上对应的样式
|
||||
if (this.disabled) {
|
||||
style.backgroundColor = this.disabledColor;
|
||||
}
|
||||
// 无边框时,去除内边距
|
||||
if (this.border === "none") {
|
||||
style.padding = "0";
|
||||
} else {
|
||||
// 由于uni-app的iOS开发者能力有限,导致需要分开写才有效
|
||||
style.paddingTop = "6px";
|
||||
style.paddingBottom = "6px";
|
||||
style.paddingLeft = "9px";
|
||||
style.paddingRight = "9px";
|
||||
}
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
|
||||
},
|
||||
// 输入框的样式
|
||||
inputStyle() {
|
||||
const style = {
|
||||
color: this.color,
|
||||
fontSize: uni.$u.addUnit(this.fontSize),
|
||||
textAlign: this.inputAlign
|
||||
};
|
||||
return style;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
|
||||
setFormatter(e) {
|
||||
this.innerFormatter = e
|
||||
},
|
||||
// 当键盘输入时,触发input事件
|
||||
onInput(e) {
|
||||
let { value = "" } = e.detail || {};
|
||||
// 格式化过滤方法
|
||||
const formatter = this.formatter || this.innerFormatter
|
||||
const formatValue = formatter(value)
|
||||
// 为了避免props的单向数据流特性,需要先将innerValue值设置为当前值,再在$nextTick中重新赋予设置后的值才有效
|
||||
this.innerValue = value
|
||||
this.$nextTick(() => {
|
||||
this.innerValue = formatValue;
|
||||
this.valueChange();
|
||||
})
|
||||
},
|
||||
// 输入框失去焦点时触发
|
||||
onBlur(event) {
|
||||
this.$emit("blur", event.detail.value);
|
||||
// H5端的blur会先于点击清除控件的点击click事件触发,导致focused
|
||||
// 瞬间为false,从而隐藏了清除控件而无法被点击到
|
||||
uni.$u.sleep(50).then(() => {
|
||||
this.focused = false;
|
||||
});
|
||||
// 尝试调用u-form的验证方法
|
||||
uni.$u.formValidate(this, "blur");
|
||||
},
|
||||
// 输入框聚焦时触发
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit("focus");
|
||||
},
|
||||
// 点击完成按钮时触发
|
||||
onConfirm(event) {
|
||||
this.$emit("confirm", this.innerValue);
|
||||
},
|
||||
// 键盘高度发生变化的时候触发此事件
|
||||
// 兼容性:微信小程序2.7.0+、App 3.1.0+
|
||||
onkeyboardheightchange() {
|
||||
this.$emit("keyboardheightchange");
|
||||
},
|
||||
// 内容发生变化,进行处理
|
||||
valueChange() {
|
||||
const value = this.innerValue;
|
||||
this.$nextTick(() => {
|
||||
this.$emit("input", value);
|
||||
// 标识value值的变化是由内部引起的
|
||||
this.changeFromInner = true;
|
||||
this.$emit("change", value);
|
||||
// 尝试调用u-form的验证方法
|
||||
uni.$u.formValidate(this, "change");
|
||||
});
|
||||
},
|
||||
// 点击清除控件
|
||||
onClear() {
|
||||
this.innerValue = "";
|
||||
this.$nextTick(() => {
|
||||
this.valueChange();
|
||||
this.$emit("clear");
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 在安卓nvue上,事件无法冒泡
|
||||
* 在某些时间,我们希望监听u-from-item的点击事件,此时会导致点击u-form-item内的u-input后
|
||||
* 无法触发u-form-item的点击事件,这里通过手动调用u-form-item的方法进行触发
|
||||
*/
|
||||
clickHandler() {
|
||||
// #ifdef APP-NVUE
|
||||
if (uni.$u.os() === "android") {
|
||||
const formItem = uni.$u.$parent.call(this, "u-form-item");
|
||||
if (formItem) {
|
||||
formItem.clickHandler();
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-input {
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
|
||||
&--radius,
|
||||
&--square {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&--no-radius {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&__field-wrapper {
|
||||
position: relative;
|
||||
@include flex(row);
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
|
||||
&__field {
|
||||
line-height: 26px;
|
||||
text-align: left;
|
||||
color: $u-main-color;
|
||||
height: 24px;
|
||||
font-size: 15px;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__clear {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 100px;
|
||||
background-color: #c6c7cb;
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: scale(0.82);
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&__subfix-icon {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&__prefix-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
24
uni_modules/uview-ui/components/u-overlay/props.js
Normal file
24
uni_modules/uview-ui/components/u-overlay/props.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否显示遮罩
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.overlay.show
|
||||
},
|
||||
// 层级z-index
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.overlay.zIndex
|
||||
},
|
||||
// 遮罩的过渡时间,单位为ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.overlay.duration
|
||||
},
|
||||
// 不透明度值,当做rgba的第四个参数
|
||||
opacity: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.overlay.opacity
|
||||
}
|
||||
}
|
||||
}
|
||||
68
uni_modules/uview-ui/components/u-overlay/u-overlay.vue
Normal file
68
uni_modules/uview-ui/components/u-overlay/u-overlay.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<u-transition
|
||||
:show="show"
|
||||
custom-class="u-overlay"
|
||||
:duration="duration"
|
||||
:custom-style="overlayStyle"
|
||||
@click="clickHandler"
|
||||
>
|
||||
<slot />
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
|
||||
/**
|
||||
* overlay 遮罩
|
||||
* @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
|
||||
* @tutorial https://www.uviewui.com/components/overlay.html
|
||||
* @property {Boolean} show 是否显示遮罩(默认 false )
|
||||
* @property {String | Number} zIndex zIndex 层级(默认 10070 )
|
||||
* @property {String | Number} duration 动画时长,单位毫秒(默认 300 )
|
||||
* @property {String | Number} opacity 不透明度值,当做rgba的第四个参数 (默认 0.5 )
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
* @event {Function} click 点击遮罩发送事件
|
||||
* @example <u-overlay :show="show" @click="show = false"></u-overlay>
|
||||
*/
|
||||
export default {
|
||||
name: "u-overlay",
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
computed: {
|
||||
overlayStyle() {
|
||||
const style = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: this.zIndex,
|
||||
bottom: 0,
|
||||
'background-color': `rgba(0, 0, 0, ${this.opacity})`
|
||||
}
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickHandler() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-overlay-top:0 !default;
|
||||
$u-overlay-left:0 !default;
|
||||
$u-overlay-width:100% !default;
|
||||
$u-overlay-height:100% !default;
|
||||
$u-overlay-background-color:rgba(0, 0, 0, .7) !default;
|
||||
.u-overlay {
|
||||
position: fixed;
|
||||
top:$u-overlay-top;
|
||||
left:$u-overlay-left;
|
||||
width: $u-overlay-width;
|
||||
height:$u-overlay-height;
|
||||
background-color:$u-overlay-background-color;
|
||||
}
|
||||
</style>
|
||||
499
uni_modules/uview-ui/components/u-parse/node/node.vue
Normal file
499
uni_modules/uview-ui/components/u-parse/node/node.vue
Normal file
@@ -0,0 +1,499 @@
|
||||
<template>
|
||||
<view :id="attrs.id" :class="'_'+name+' '+attrs.class" :style="attrs.style">
|
||||
<block v-for="(n, i) in childs" v-bind:key="i">
|
||||
<!-- 图片 -->
|
||||
<!-- 占位图 -->
|
||||
<image v-if="n.name=='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
|
||||
<!-- 显示图片 -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<img v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]==-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap"/>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || APP-PLUS -->
|
||||
<image v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]==-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="n.h?'':'widthFix'" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
|
||||
<!-- #endif -->
|
||||
<!-- 文本 -->
|
||||
<!-- #ifndef MP-BAIDU -->
|
||||
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
|
||||
<!-- #endif -->
|
||||
<text v-else-if="n.name=='br'">\n</text>
|
||||
<!-- 链接 -->
|
||||
<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
|
||||
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
|
||||
</view>
|
||||
<!-- 视频 -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-PLUS -->
|
||||
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 || APP-PLUS -->
|
||||
<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
|
||||
<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :src="n.attrs.src" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-TOUTIAO -->
|
||||
<!-- 音频 -->
|
||||
<audio v-else-if="n.name=='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
|
||||
<!-- #endif -->
|
||||
<view v-else-if="(n.name=='table'&&n.c)||n.name=='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
|
||||
<node v-if="n.name=='li'" :childs="n.children" :opts="opts" />
|
||||
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
|
||||
<node v-if="tbody.name=='td'||tbody.name=='th'" :childs="tbody.children" :opts="opts" />
|
||||
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
|
||||
<view v-if="tr.name=='td'||tr.name=='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
|
||||
<node :childs="tr.children" :opts="opts" />
|
||||
</view>
|
||||
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
|
||||
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
|
||||
<node :childs="td.children" :opts="opts" />
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 富文本 -->
|
||||
<!-- #ifdef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
|
||||
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :style="n.f" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
|
||||
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" />
|
||||
<!-- #endif -->
|
||||
<!-- 继续递归 -->
|
||||
<view v-else-if="n.c==2" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
|
||||
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
|
||||
</view>
|
||||
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
<script module="handler" lang="wxs">
|
||||
// 行内标签列表
|
||||
var inlineTags = {
|
||||
abbr: true,
|
||||
b: true,
|
||||
big: true,
|
||||
code: true,
|
||||
del: true,
|
||||
em: true,
|
||||
i: true,
|
||||
ins: true,
|
||||
label: true,
|
||||
q: true,
|
||||
small: true,
|
||||
span: true,
|
||||
strong: true,
|
||||
sub: true,
|
||||
sup: true
|
||||
}
|
||||
/**
|
||||
* @description 是否使用 rich-text 显示剩余内容
|
||||
*/
|
||||
module.exports = {
|
||||
use: function (item) {
|
||||
// 微信和 QQ 的 rich-text inline 布局无效
|
||||
if (inlineTags[item.name] || (item.attrs.style || '').indexOf('display:inline') != -1)
|
||||
return false
|
||||
return !item.c
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
import node from './node'
|
||||
export default {
|
||||
name: 'node',
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
ctrl: {}
|
||||
}
|
||||
},
|
||||
props: {
|
||||
name: String,
|
||||
attrs: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
childs: Array,
|
||||
opts: Array
|
||||
},
|
||||
components: {
|
||||
|
||||
node
|
||||
},
|
||||
mounted() {
|
||||
for (this.root = this.$parent; this.root.$options.name != 'mp-html'; this.root = this.root.$parent);
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.opts[0]) {
|
||||
for (var i = this.childs.length; i--;)
|
||||
if (this.childs[i].name == 'img')
|
||||
break
|
||||
if (i != -1) {
|
||||
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
|
||||
top: 500,
|
||||
bottom: 500
|
||||
})
|
||||
this.observer.observe('._img', res => {
|
||||
if (res.intersectionRatio) {
|
||||
this.$set(this.ctrl, 'load', 1)
|
||||
this.observer.disconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
beforeDestroy() {
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (this.observer)
|
||||
this.observer.disconnect()
|
||||
// #endif
|
||||
},
|
||||
methods:{
|
||||
// #ifdef MP-WEIXIN
|
||||
toJSON() { },
|
||||
// #endif
|
||||
/**
|
||||
* @description 播放视频事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
play(e) {
|
||||
// #ifndef APP-PLUS
|
||||
if (this.root.pauseVideo) {
|
||||
var flag = false, id = e.target.id
|
||||
for (var i = this.root._videos.length; i--;) {
|
||||
if (this.root._videos[i].id == id)
|
||||
flag = true
|
||||
else
|
||||
this.root._videos[i].pause() // 自动暂停其他视频
|
||||
}
|
||||
// 将自己加入列表
|
||||
if (!flag) {
|
||||
var ctx = uni.createVideoContext(id
|
||||
// #ifndef MP-BAIDU
|
||||
, this
|
||||
// #endif
|
||||
)
|
||||
ctx.id = id
|
||||
this.root._videos.push(ctx)
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgTap(e) {
|
||||
var node = this.childs[e.currentTarget.dataset.i]
|
||||
if (node.a)
|
||||
return this.linkTap(node.a)
|
||||
if (node.attrs.ignore)
|
||||
return
|
||||
// #ifdef H5 || APP-PLUS
|
||||
node.attrs.src = node.attrs.src || node.attrs['data-src']
|
||||
// #endif
|
||||
this.root.$emit('imgTap', node.attrs)
|
||||
// 自动预览图片
|
||||
if (this.root.previewImg)
|
||||
uni.previewImage({
|
||||
current: parseInt(node.attrs.i),
|
||||
urls: this.root.imgList
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片长按
|
||||
*/
|
||||
imgLongTap(e) {
|
||||
// #ifdef APP-PLUS
|
||||
var attrs = this.childs[e.currentTarget.dataset.i].attrs
|
||||
if (!attrs.ignore)
|
||||
uni.showActionSheet({
|
||||
itemList: ['保存图片'],
|
||||
success: () => {
|
||||
uni.downloadFile({
|
||||
url: this.root.imgList[attrs.i],
|
||||
success: res => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success() {
|
||||
uni.showToast({
|
||||
title: '保存成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 图片加载完成事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
imgLoad(e) {
|
||||
var i = e.currentTarget.dataset.i
|
||||
// #ifndef H5 || APP-PLUS
|
||||
// 设置原宽度
|
||||
if (!this.childs[i].w)
|
||||
this.$set(this.ctrl, i, e.detail.width)
|
||||
else
|
||||
// #endif
|
||||
// 加载完毕,取消加载中占位图
|
||||
if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] == -1)
|
||||
this.$set(this.ctrl, i, 1)
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 链接点击事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
linkTap(e) {
|
||||
var attrs = e.currentTarget ? this.childs[e.currentTarget.dataset.i].attrs : e,
|
||||
href = attrs.href
|
||||
this.root.$emit('linkTap', attrs)
|
||||
if (href) {
|
||||
// 跳转锚点
|
||||
if (href[0] == '#')
|
||||
this.root.navigateTo(href.substring(1)).catch(() => { })
|
||||
// 复制外部链接
|
||||
else if (href.includes('://')) {
|
||||
if (this.root.copyLink) {
|
||||
// #ifdef H5
|
||||
window.open(href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: href,
|
||||
success: () =>
|
||||
uni.showToast({
|
||||
title: '链接已复制'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openWeb(href)
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
// 跳转页面
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail() {
|
||||
uni.switchTab({
|
||||
url: href,
|
||||
fail() { }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 错误事件
|
||||
* @param {Event} e
|
||||
*/
|
||||
mediaError(e) {
|
||||
var i = e.currentTarget.dataset.i,
|
||||
node = this.childs[i]
|
||||
// 加载其他源
|
||||
if (node.name == 'video' || node.name == 'audio') {
|
||||
var index = (this.ctrl[i] || 0) + 1
|
||||
if (index > node.src.length)
|
||||
index = 0
|
||||
if (index < node.src.length)
|
||||
return this.$set(this.ctrl, i, index)
|
||||
}
|
||||
// 显示错误占位图
|
||||
else if (node.name == 'img' && this.opts[2])
|
||||
this.$set(this.ctrl, i, -1)
|
||||
if (this.root)
|
||||
this.root.$emit('error', {
|
||||
source: node.name,
|
||||
attrs: node.attrs,
|
||||
errMsg: e.detail.errMsg
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* a 标签默认效果 */
|
||||
._a {
|
||||
padding: 1.5px 0 1.5px 0;
|
||||
color: #366092;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* a 标签点击态效果 */
|
||||
._hover {
|
||||
text-decoration: underline;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 图片默认效果 */
|
||||
._img {
|
||||
max-width: 100%;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
/* 内部样式 */
|
||||
|
||||
._b,
|
||||
._strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._code {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
._del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
._em,
|
||||
._i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
._h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
._h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
._h3 {
|
||||
font-size: 1.17em;
|
||||
}
|
||||
|
||||
._h5 {
|
||||
font-size: 0.83em;
|
||||
}
|
||||
|
||||
._h6 {
|
||||
font-size: 0.67em;
|
||||
}
|
||||
|
||||
._h1,
|
||||
._h2,
|
||||
._h3,
|
||||
._h4,
|
||||
._h5,
|
||||
._h6 {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
._image {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
._ins {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
._li {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
._ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
._ol,
|
||||
._ul {
|
||||
display: block;
|
||||
padding-left: 40px;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
._q::before {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._q::after {
|
||||
content: '"';
|
||||
}
|
||||
|
||||
._sub {
|
||||
font-size: smaller;
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
._sup {
|
||||
font-size: smaller;
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
._thead,
|
||||
._tbody,
|
||||
._tfoot {
|
||||
display: table-row-group;
|
||||
}
|
||||
|
||||
._tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
._td,
|
||||
._th {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
._th {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
._ul {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
._ul ._ul {
|
||||
margin: 0;
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
._ul ._ul ._ul {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
._abbr,
|
||||
._b,
|
||||
._code,
|
||||
._del,
|
||||
._em,
|
||||
._i,
|
||||
._ins,
|
||||
._label,
|
||||
._q,
|
||||
._span,
|
||||
._strong,
|
||||
._sub,
|
||||
._sup {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* #ifdef APP-PLUS */
|
||||
._video {
|
||||
width: 300px;
|
||||
height: 225px;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
1075
uni_modules/uview-ui/components/u-parse/parser.js
Normal file
1075
uni_modules/uview-ui/components/u-parse/parser.js
Normal file
File diff suppressed because it is too large
Load Diff
45
uni_modules/uview-ui/components/u-parse/props.js
Normal file
45
uni_modules/uview-ui/components/u-parse/props.js
Normal file
@@ -0,0 +1,45 @@
|
||||
export default {
|
||||
props: {
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
bgColor: String,
|
||||
// #endif
|
||||
content: String,
|
||||
copyLink: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.copyLink
|
||||
},
|
||||
domain: String,
|
||||
errorImg: {
|
||||
type: String,
|
||||
default: uni.$u.props.parse.errorImg
|
||||
},
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.lazyLoad
|
||||
},
|
||||
loadingImg: {
|
||||
type: String,
|
||||
default: uni.$u.props.parse.loadingImg
|
||||
},
|
||||
pauseVideo: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.pauseVideo
|
||||
},
|
||||
previewImg: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.previewImg
|
||||
},
|
||||
scrollTable: Boolean,
|
||||
selectable: Boolean,
|
||||
setTitle: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.setTitle
|
||||
},
|
||||
showImgMenu: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.parse.showImgMenu
|
||||
},
|
||||
tagStyle: Object,
|
||||
useAnchor: null
|
||||
}
|
||||
}
|
||||
366
uni_modules/uview-ui/components/u-parse/u-parse.vue
Normal file
366
uni_modules/uview-ui/components/u-parse/u-parse.vue
Normal file
@@ -0,0 +1,366 @@
|
||||
<template>
|
||||
<view id="_root" :class="(selectable?'_select ':'')+'_root'">
|
||||
<slot v-if="!nodes[0]" />
|
||||
<!-- #ifndef APP-PLUS-NVUE -->
|
||||
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS-NVUE -->
|
||||
<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* mp-html v2.0.4
|
||||
* @description 富文本组件
|
||||
* @tutorial https://github.com/jin-yufeng/mp-html
|
||||
* @property {String} bgColor 背景颜色,只适用与APP-PLUS-NVUE
|
||||
* @property {String} content 用于渲染的富文本字符串(默认 true )
|
||||
* @property {Boolean} copyLink 是否允许外部链接被点击时自动复制
|
||||
* @property {String} domain 主域名,用于拼接链接
|
||||
* @property {String} errorImg 图片出错时的占位图链接
|
||||
* @property {Boolean} lazyLoad 是否开启图片懒加载(默认 true )
|
||||
* @property {string} loadingImg 图片加载过程中的占位图链接
|
||||
* @property {Boolean} pauseVideo 是否在播放一个视频时自动暂停其它视频(默认 true )
|
||||
* @property {Boolean} previewImg 是否允许图片被点击时自动预览(默认 true )
|
||||
* @property {Boolean} scrollTable 是否给每个表格添加一个滚动层使其能单独横向滚动
|
||||
* @property {Boolean} selectable 是否开启长按复制
|
||||
* @property {Boolean} setTitle 是否将 title 标签的内容设置到页面标题(默认 true )
|
||||
* @property {Boolean} showImgMenu 是否允许图片被长按时显示菜单(默认 true )
|
||||
* @property {Object} tagStyle 标签的默认样式
|
||||
* @property {Boolean | Number} useAnchor 是否使用锚点链接
|
||||
*
|
||||
* @event {Function} load dom 结构加载完毕时触发
|
||||
* @event {Function} ready 所有图片加载完毕时触发
|
||||
* @event {Function} imgTap 图片被点击时触发
|
||||
* @event {Function} linkTap 链接被点击时触发
|
||||
* @event {Function} error 媒体加载出错时触发
|
||||
*/
|
||||
const plugins=[]
|
||||
const parser = require('./parser')
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
import node from './node/node'
|
||||
// #endif
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
// #endif
|
||||
export default {
|
||||
name: 'mp-html',
|
||||
data() {
|
||||
return {
|
||||
nodes: [],
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
height: 0
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
mixins:[props],
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
components: {
|
||||
node
|
||||
},
|
||||
// #endif
|
||||
watch: {
|
||||
content(content) {
|
||||
this.setContent(content)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.plugins = []
|
||||
for (let i = plugins.length; i--;)
|
||||
this.plugins.push(new plugins[i](this))
|
||||
},
|
||||
mounted() {
|
||||
if (this.content && !this.nodes.length)
|
||||
this.setContent(this.content)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this._hook('onDetached')
|
||||
clearInterval(this._timer)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 将锚点跳转的范围限定在一个 scroll-view 内
|
||||
* @param {Object} page scroll-view 所在页面的示例
|
||||
* @param {String} selector scroll-view 的选择器
|
||||
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
|
||||
*/
|
||||
in(page, selector, scrollTop) {
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
if (page && selector && scrollTop)
|
||||
this._in = {
|
||||
page,
|
||||
selector,
|
||||
scrollTop
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 锚点跳转
|
||||
* @param {String} id 要跳转的锚点 id
|
||||
* @param {Number} offset 跳转位置的偏移量
|
||||
* @returns {Promise}
|
||||
*/
|
||||
navigateTo(id, offset) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.useAnchor)
|
||||
return reject('Anchor is disabled')
|
||||
offset = offset || parseInt(this.useAnchor) || 0
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (!id) {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset
|
||||
})
|
||||
resolve()
|
||||
} else {
|
||||
this._navigateTo = {
|
||||
resolve,
|
||||
reject,
|
||||
offset
|
||||
}
|
||||
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
let deep = ' '
|
||||
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
|
||||
deep = '>>>'
|
||||
// #endif
|
||||
const selector = uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this._in ? this._in.page : this)
|
||||
// #endif
|
||||
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
|
||||
if (this._in)
|
||||
selector.select(this._in.selector).scrollOffset()
|
||||
.select(this._in.selector).boundingClientRect() // 获取 scroll-view 的位置和滚动距离
|
||||
else
|
||||
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
|
||||
selector.exec(res => {
|
||||
if (!res[0])
|
||||
return reject('Label not found')
|
||||
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
|
||||
if (this._in)
|
||||
// scroll-view 跳转
|
||||
this._in.page[this._in.scrollTop] = scrollTop
|
||||
else
|
||||
// 页面跳转
|
||||
uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: 300
|
||||
})
|
||||
resolve()
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取文本内容
|
||||
* @return {String}
|
||||
*/
|
||||
getText() {
|
||||
let text = '';
|
||||
(function traversal(nodes) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
const node = nodes[i]
|
||||
if (node.type == 'text')
|
||||
text += node.text.replace(/&/g, '&')
|
||||
else if (node.name == 'br')
|
||||
text += '\n'
|
||||
else {
|
||||
// 块级标签前后加换行
|
||||
const isBlock = node.name == 'p' || node.name == 'div' || node.name == 'tr' || node.name == 'li' || (node.name[0] == 'h' && node.name[1] > '0' && node.name[1] < '7')
|
||||
if (isBlock && text && text[text.length - 1] != '\n')
|
||||
text += '\n'
|
||||
// 递归获取子节点的文本
|
||||
if (node.children)
|
||||
traversal(node.children)
|
||||
if (isBlock && text[text.length - 1] != '\n')
|
||||
text += '\n'
|
||||
else if (node.name == 'td' || node.name == 'th')
|
||||
text += '\t'
|
||||
}
|
||||
}
|
||||
})(this.nodes)
|
||||
return text
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 获取内容大小和位置
|
||||
* @return {Promise}
|
||||
*/
|
||||
getRect() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject('Root label not found'))
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 设置内容
|
||||
* @param {String} content html 内容
|
||||
* @param {Boolean} append 是否在尾部追加
|
||||
*/
|
||||
setContent(content, append) {
|
||||
if (!append || !this.imgList)
|
||||
this.imgList = []
|
||||
const nodes = new parser(this).parse(content)
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
if (this._ready)
|
||||
this._set(nodes, append)
|
||||
// #endif
|
||||
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
|
||||
|
||||
// #ifndef APP-PLUS-NVUE
|
||||
this._videos = []
|
||||
this.$nextTick(() => {
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
})
|
||||
|
||||
// 等待图片加载完毕
|
||||
let height
|
||||
clearInterval(this._timer)
|
||||
this._timer = setInterval(() => {
|
||||
this.getRect().then(rect => {
|
||||
// 350ms 总高度无变化就触发 ready 事件
|
||||
if (rect.height == height) {
|
||||
this.$emit('ready', rect)
|
||||
clearInterval(this._timer)
|
||||
}
|
||||
height = rect.height
|
||||
}).catch(() => { })
|
||||
}, 350)
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 调用插件钩子函数
|
||||
*/
|
||||
_hook(name) {
|
||||
for (let i = plugins.length; i--;)
|
||||
if (this.plugins[i][name])
|
||||
this.plugins[i][name]()
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS-NVUE
|
||||
/**
|
||||
* @description 设置内容
|
||||
*/
|
||||
_set(nodes, append) {
|
||||
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.bgColor, this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
|
||||
},
|
||||
|
||||
/**
|
||||
* @description 接收到 web-view 消息
|
||||
*/
|
||||
_onMessage(e) {
|
||||
const message = e.detail.data[0]
|
||||
switch (message.action) {
|
||||
// web-view 初始化完毕
|
||||
case 'onJSBridgeReady':
|
||||
this._ready = true
|
||||
if (this.nodes)
|
||||
this._set(this.nodes)
|
||||
break
|
||||
// 内容 dom 加载完毕
|
||||
case 'onLoad':
|
||||
this.height = message.height
|
||||
this._hook('onLoad')
|
||||
this.$emit('load')
|
||||
break
|
||||
// 所有图片加载完毕
|
||||
case 'onReady':
|
||||
this.getRect().then(res => {
|
||||
this.$emit('ready', res)
|
||||
}).catch(() => { })
|
||||
break
|
||||
// 总高度发生变化
|
||||
case 'onHeightChange':
|
||||
this.height = message.height
|
||||
break
|
||||
// 图片点击
|
||||
case 'onImgTap':
|
||||
this.$emit('imgTap', message.attrs)
|
||||
if (this.previewImg)
|
||||
uni.previewImage({
|
||||
current: parseInt(message.attrs.i),
|
||||
urls: this.imgList
|
||||
})
|
||||
break
|
||||
// 链接点击
|
||||
case 'onLinkTap':
|
||||
const href = message.attrs.href
|
||||
this.$emit('linkTap', message.attrs)
|
||||
if (href) {
|
||||
// 锚点跳转
|
||||
if (href[0] == '#') {
|
||||
if (this.useAnchor)
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset
|
||||
})
|
||||
}
|
||||
// 打开外链
|
||||
else if (href.includes('://')) {
|
||||
if (this.copyLink)
|
||||
plus.runtime.openWeb(href)
|
||||
}
|
||||
else
|
||||
uni.navigateTo({
|
||||
url: href,
|
||||
fail() {
|
||||
wx.switchTab({
|
||||
url: href
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
break
|
||||
// 获取到锚点的偏移量
|
||||
case 'getOffset':
|
||||
if (typeof message.offset == 'number') {
|
||||
dom.scrollToElement(this.$refs.web, {
|
||||
offset: message.offset + this._navigateTo.offset
|
||||
})
|
||||
this._navigateTo.resolve()
|
||||
} else
|
||||
this._navigateTo.reject('Label not found')
|
||||
break
|
||||
// 点击
|
||||
case 'onClick':
|
||||
this.$emit('tap')
|
||||
break
|
||||
// 出错
|
||||
case 'onError':
|
||||
this.$emit('error', {
|
||||
source: message.source,
|
||||
attrs: message.attrs
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
/* 根节点样式 */
|
||||
._root {
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
/* 长按复制 */
|
||||
._select {
|
||||
user-select: text;
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
5
uni_modules/uview-ui/components/u-picker-column/props.js
Normal file
5
uni_modules/uview-ui/components/u-picker-column/props.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
props: {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<picker-view-column>
|
||||
<view class="u-picker-column">
|
||||
|
||||
</view>
|
||||
</picker-view-column>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* PickerColumn
|
||||
* @description
|
||||
* @tutorial url
|
||||
* @property {String}
|
||||
* @event {Function}
|
||||
* @example
|
||||
*/
|
||||
export default {
|
||||
name: 'u-picker-column',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
</style>
|
||||
79
uni_modules/uview-ui/components/u-picker/props.js
Normal file
79
uni_modules/uview-ui/components/u-picker/props.js
Normal file
@@ -0,0 +1,79 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否展示picker弹窗
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.picker.show
|
||||
},
|
||||
// 是否展示顶部的操作栏
|
||||
showToolbar: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.picker.showToolbar
|
||||
},
|
||||
// 顶部标题
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.title
|
||||
},
|
||||
// 对象数组,设置每一列的数据
|
||||
columns: {
|
||||
type: Array,
|
||||
default: uni.$u.props.picker.columns
|
||||
},
|
||||
// 是否显示加载中状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.picker.loading
|
||||
},
|
||||
// 各列中,单个选项的高度
|
||||
itemHeight: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.picker.itemHeight
|
||||
},
|
||||
// 取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.cancelText
|
||||
},
|
||||
// 确认按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.confirmText
|
||||
},
|
||||
// 取消按钮的颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.cancelColor
|
||||
},
|
||||
// 确认按钮的颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.confirmColor
|
||||
},
|
||||
// 每列中可见选项的数量
|
||||
visibleItemCount: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.picker.visibleItemCount
|
||||
},
|
||||
// 选项对象中,需要展示的属性键名
|
||||
keyName: {
|
||||
type: String,
|
||||
default: uni.$u.props.picker.keyName
|
||||
},
|
||||
// 是否允许点击遮罩关闭选择器
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.picker.closeOnClickOverlay
|
||||
},
|
||||
// 各列的默认索引
|
||||
defaultIndex: {
|
||||
type: Array,
|
||||
default: uni.$u.props.picker.defaultIndex
|
||||
},
|
||||
// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
|
||||
immediateChange: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.picker.immediateChange
|
||||
}
|
||||
}
|
||||
}
|
||||
286
uni_modules/uview-ui/components/u-picker/u-picker.vue
Normal file
286
uni_modules/uview-ui/components/u-picker/u-picker.vue
Normal file
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<u-popup
|
||||
:show="show"
|
||||
@close="closeHandler"
|
||||
>
|
||||
<view class="u-picker">
|
||||
<u-toolbar
|
||||
v-if="showToolbar"
|
||||
:cancelColor="cancelColor"
|
||||
:confirmColor="confirmColor"
|
||||
:cancelText="cancelText"
|
||||
:confirmText="confirmText"
|
||||
:title="title"
|
||||
@cancel="cancel"
|
||||
@confirm="confirm"
|
||||
></u-toolbar>
|
||||
<picker-view
|
||||
class="u-picker__view"
|
||||
:indicatorStyle="`height: ${$u.addUnit(itemHeight)}`"
|
||||
:value="innerIndex"
|
||||
:immediateChange="immediateChange"
|
||||
:style="{
|
||||
height: `${$u.addUnit(visibleItemCount * itemHeight)}`
|
||||
}"
|
||||
@change="changeHandler"
|
||||
>
|
||||
<picker-view-column
|
||||
v-for="(item, index) in innerColumns"
|
||||
:key="index"
|
||||
class="u-picker__view__column"
|
||||
>
|
||||
<text
|
||||
v-if="$u.test.array(item)"
|
||||
class="u-picker__view__column__item u-line-1"
|
||||
v-for="(item1, index1) in item"
|
||||
:key="index1"
|
||||
:style="{
|
||||
height: $u.addUnit(itemHeight),
|
||||
lineHeight: $u.addUnit(itemHeight),
|
||||
fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal',
|
||||
display: 'block'
|
||||
}"
|
||||
>{{ getItemText(item1) }}</text>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view
|
||||
v-if="loading"
|
||||
class="u-picker--loading"
|
||||
>
|
||||
<u-loading-icon mode="circle"></u-loading-icon>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* u-picker
|
||||
* @description 选择器
|
||||
* @property {Boolean} show 是否显示picker弹窗(默认 false )
|
||||
* @property {Boolean} showToolbar 是否显示顶部的操作栏(默认 true )
|
||||
* @property {String} title 顶部标题
|
||||
* @property {Array} columns 对象数组,设置每一列的数据
|
||||
* @property {Boolean} loading 是否显示加载中状态(默认 false )
|
||||
* @property {String | Number} itemHeight 各列中,单个选项的高度(默认 44 )
|
||||
* @property {String} cancelText 取消按钮的文字(默认 '取消' )
|
||||
* @property {String} confirmText 确认按钮的文字(默认 '确定' )
|
||||
* @property {String} cancelColor 取消按钮的颜色(默认 '#909193' )
|
||||
* @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff' )
|
||||
* @property {String | Number} visibleItemCount 每列中可见选项的数量(默认 5 )
|
||||
* @property {String} keyName 选项对象中,需要展示的属性键名(默认 'text' )
|
||||
* @property {Boolean} closeOnClickOverlay 是否允许点击遮罩关闭选择器(默认 false )
|
||||
* @property {Array} defaultIndex 各列的默认索引
|
||||
* @property {Boolean} immediateChange 是否在手指松开时立即触发change事件(默认 false )
|
||||
* @event {Function} close 关闭选择器时触发
|
||||
* @event {Function} cancel 点击取消按钮触发
|
||||
* @event {Function} change 当选择值变化时触发
|
||||
* @event {Function} confirm 点击确定按钮,返回当前选择的值
|
||||
*/
|
||||
import props from './props.js';
|
||||
export default {
|
||||
name: 'u-picker',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
// 上一次选择的列索引
|
||||
lastIndex: [],
|
||||
// 索引值 ,对应picker-view的value
|
||||
innerIndex: [],
|
||||
// 各列的值
|
||||
innerColumns: [],
|
||||
// 上一次的变化列索引
|
||||
columnIndex: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听默认索引的变化,重新设置对应的值
|
||||
defaultIndex: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setIndexs(n, true)
|
||||
}
|
||||
},
|
||||
// 监听columns参数的变化
|
||||
columns: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
this.setColumns(n)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取item需要显示的文字,判别为对象还是文本
|
||||
getItemText(item) {
|
||||
if (uni.$u.test.object(item)) {
|
||||
return item[this.keyName]
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
},
|
||||
// 关闭选择器
|
||||
closeHandler() {
|
||||
if (this.closeOnClickOverlay) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
// 点击工具栏的取消按钮
|
||||
cancel() {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击工具栏的确定按钮
|
||||
confirm() {
|
||||
this.$emit('confirm', {
|
||||
indexs: this.innerIndex,
|
||||
value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
|
||||
values: this.innerColumns
|
||||
})
|
||||
},
|
||||
// 选择器某一列的数据发生变化时触发
|
||||
changeHandler(e) {
|
||||
const {
|
||||
value
|
||||
} = e.detail
|
||||
let index = 0,
|
||||
columnIndex = 0
|
||||
// 通过对比前后两次的列索引,得出当前变化的是哪一列
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
let item = value[i]
|
||||
if (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0
|
||||
// 设置columnIndex为当前变化列的索引
|
||||
columnIndex = i
|
||||
// index则为变化列中的变化项的索引
|
||||
index = item
|
||||
break // 终止循环,即使少一次循环,也是性能的提升
|
||||
}
|
||||
}
|
||||
this.columnIndex = columnIndex
|
||||
const values = this.innerColumns
|
||||
// 将当前的各项变化索引,设置为"上一次"的索引变化值
|
||||
this.setLastIndex(value)
|
||||
this.setIndexs(value)
|
||||
|
||||
this.$emit('change', {
|
||||
// #ifndef MP-WEIXIN || MP-LARK || MP-TOUTIAO
|
||||
// 微信小程序不能传递this,会因为循环引用而报错
|
||||
picker: this,
|
||||
// #endif
|
||||
value: this.innerColumns.map((item, index) => item[value[index]]),
|
||||
index,
|
||||
indexs: value,
|
||||
// values为当前变化列的数组内容
|
||||
values,
|
||||
columnIndex
|
||||
})
|
||||
},
|
||||
// 设置index索引,此方法可被外部调用设置
|
||||
setIndexs(index, setLastIndex) {
|
||||
this.innerIndex = uni.$u.deepClone(index)
|
||||
if (setLastIndex) {
|
||||
this.setLastIndex(index)
|
||||
}
|
||||
},
|
||||
// 记录上一次的各列索引位置
|
||||
setLastIndex(index) {
|
||||
// 当能进入此方法,意味着当前设置的各列默认索引,即为“上一次”的选中值,需要记录,是因为changeHandler中
|
||||
// 需要拿前后的变化值进行对比,得出当前发生改变的是哪一列
|
||||
this.lastIndex = uni.$u.deepClone(index)
|
||||
},
|
||||
// 设置对应列选项的所有值
|
||||
setColumnValues(columnIndex, values) {
|
||||
// 替换innerColumns数组中columnIndex索引的值为values,使用的是数组的splice方法
|
||||
this.innerColumns.splice(columnIndex, 1, values)
|
||||
// 替换完成之后将修改列之后的已选值置空
|
||||
this.setLastIndex(this.innerIndex.slice(0,columnIndex))
|
||||
// 拷贝一份原有的innerIndex做临时变量,将大于当前变化列的所有的列的默认索引设置为0
|
||||
let tmpIndex = uni.$u.deepClone(this.innerIndex)
|
||||
for (let i = 0; i < this.innerColumns.length; i++) {
|
||||
if (i > this.columnIndex) {
|
||||
tmpIndex[i] = 0
|
||||
}
|
||||
}
|
||||
// 一次性赋值,不能单个修改,否则无效
|
||||
this.setIndexs(tmpIndex)
|
||||
},
|
||||
// 获取对应列的所有选项
|
||||
getColumnValues(columnIndex) {
|
||||
// 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
|
||||
// 索引如果在外部change的回调中调用getColumnValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
|
||||
(async () => {
|
||||
await uni.$u.sleep()
|
||||
})()
|
||||
return this.innerColumns[columnIndex]
|
||||
},
|
||||
// 设置整体各列的columns的值
|
||||
setColumns(columns) {
|
||||
this.innerColumns = uni.$u.deepClone(columns)
|
||||
// 如果在设置各列数据时,没有被设置默认的各列索引defaultIndex,那么用0去填充它,数组长度为列的数量
|
||||
if (this.innerIndex.length === 0) {
|
||||
this.innerIndex = new Array(columns.length).fill(0)
|
||||
}
|
||||
},
|
||||
// 获取各列选中值对应的索引
|
||||
getIndexs() {
|
||||
return this.innerIndex
|
||||
},
|
||||
// 获取各列选中的值
|
||||
getValues() {
|
||||
// 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
|
||||
// 索引如果在外部change的回调中调用getValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
|
||||
(async () => {
|
||||
await uni.$u.sleep()
|
||||
})()
|
||||
return this.innerColumns.map((item, index) => item[this.innerIndex[index]])
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-picker {
|
||||
position: relative;
|
||||
|
||||
&__view {
|
||||
|
||||
&__column {
|
||||
@include flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
|
||||
&__item {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
color: $u-main-color;
|
||||
|
||||
&--disabled {
|
||||
/* #ifndef APP-NVUE */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--loading {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(255, 255, 255, 0.87);
|
||||
z-index: 1000;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
uni_modules/uview-ui/components/u-popup/props.js
Normal file
79
uni_modules/uview-ui/components/u-popup/props.js
Normal file
@@ -0,0 +1,79 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否展示弹窗
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.show
|
||||
},
|
||||
// 是否显示遮罩
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.overlay
|
||||
},
|
||||
// 弹出的方向,可选值为 top bottom right left center
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.popup.mode
|
||||
},
|
||||
// 动画时长,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.popup.duration
|
||||
},
|
||||
// 是否显示关闭图标
|
||||
closeable: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.closeable
|
||||
},
|
||||
// 自定义遮罩的样式
|
||||
overlayStyle: {
|
||||
type: [Object, String],
|
||||
default: uni.$u.props.popup.overlayStyle
|
||||
},
|
||||
// 点击遮罩是否关闭弹窗
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.closeOnClickOverlay
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.popup.zIndex
|
||||
},
|
||||
// 是否为iPhoneX留出底部安全距离
|
||||
safeAreaInsetBottom: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.safeAreaInsetBottom
|
||||
},
|
||||
// 是否留出顶部安全距离(状态栏高度)
|
||||
safeAreaInsetTop: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.safeAreaInsetTop
|
||||
},
|
||||
// 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
|
||||
closeIconPos: {
|
||||
type: String,
|
||||
default: uni.$u.props.popup.closeIconPos
|
||||
},
|
||||
// 是否显示圆角
|
||||
round: {
|
||||
type: [Boolean, String, Number],
|
||||
default: uni.$u.props.popup.round
|
||||
},
|
||||
// mode=center,也即中部弹出时,是否使用缩放模式
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.popup.zoom
|
||||
},
|
||||
// 弹窗背景色,设置为transparent可去除白色背景
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.popup.bgColor
|
||||
},
|
||||
// 遮罩的透明度,0-1之间
|
||||
overlayOpacity: {
|
||||
type: [Number, String],
|
||||
default: uni.$u.props.popup.overlayOpacity
|
||||
}
|
||||
}
|
||||
}
|
||||
304
uni_modules/uview-ui/components/u-popup/u-popup.vue
Normal file
304
uni_modules/uview-ui/components/u-popup/u-popup.vue
Normal file
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<view class="u-popup">
|
||||
<u-overlay
|
||||
:show="show"
|
||||
@click="overlayClick"
|
||||
v-if="overlay"
|
||||
:duration="overlayDuration"
|
||||
:customStyle="overlayStyle"
|
||||
:opacity="overlayOpacity"
|
||||
></u-overlay>
|
||||
<u-transition
|
||||
:show="show"
|
||||
:customStyle="transitionStyle"
|
||||
:mode="position"
|
||||
:duration="duration"
|
||||
@afterEnter="afterEnter"
|
||||
@click="clickHandler"
|
||||
>
|
||||
<view
|
||||
class="u-popup__content"
|
||||
:style="[contentStyle]"
|
||||
@tap.stop="noop"
|
||||
>
|
||||
<u-status-bar v-if="safeAreaInsetTop"></u-status-bar>
|
||||
<slot></slot>
|
||||
<view
|
||||
v-if="closeable"
|
||||
@tap.stop="close"
|
||||
class="u-popup__content__close"
|
||||
:class="['u-popup__content__close--' + closeIconPos]"
|
||||
hover-class="u-popup__content__close--hover"
|
||||
hover-stay-time="150"
|
||||
>
|
||||
<u-icon
|
||||
name="close"
|
||||
color="#909399"
|
||||
size="18"
|
||||
bold
|
||||
></u-icon>
|
||||
</view>
|
||||
<u-safe-bottom v-if="safeAreaInsetBottom"></u-safe-bottom>
|
||||
</view>
|
||||
</u-transition>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
|
||||
/**
|
||||
* popup 弹窗
|
||||
* @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
|
||||
* @tutorial https://www.uviewui.com/components/popup.html
|
||||
* @property {Boolean} show 是否展示弹窗 (默认 false )
|
||||
* @property {Boolean} overlay 是否显示遮罩 (默认 true )
|
||||
* @property {String} mode 弹出方向(默认 'bottom' )
|
||||
* @property {String | Number} duration 动画时长,单位ms (默认 300 )
|
||||
* @property {String | Number} overlayDuration 遮罩层动画时长,单位ms (默认 350 )
|
||||
* @property {Boolean} closeable 是否显示关闭图标(默认 false )
|
||||
* @property {Object | String} overlayStyle 自定义遮罩的样式
|
||||
* @property {String | Number} overlayOpacity 遮罩透明度,0-1之间(默认 0.5)
|
||||
* @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭弹窗 (默认 true )
|
||||
* @property {String | Number} zIndex 层级 (默认 10075 )
|
||||
* @property {Boolean} safeAreaInsetBottom 是否为iPhoneX留出底部安全距离 (默认 true )
|
||||
* @property {Boolean} safeAreaInsetTop 是否留出顶部安全距离(状态栏高度) (默认 false )
|
||||
* @property {String} closeIconPos 自定义关闭图标位置(默认 'top-right' )
|
||||
* @property {String | Number} round 圆角值(默认 0)
|
||||
* @property {Boolean} zoom 当mode=center时 是否开启缩放(默认 true )
|
||||
* @property {Object} customStyle 组件的样式,对象形式
|
||||
* @event {Function} open 弹出层打开
|
||||
* @event {Function} close 弹出层收起
|
||||
* @example <u-popup v-model="show"><text>出淤泥而不染,濯清涟而不妖</text></u-popup>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-popup',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
overlayDuration: this.duration + 50
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(newValue, oldValue) {
|
||||
if (newValue === true) {
|
||||
// #ifdef MP-WEIXIN
|
||||
const children = this.$children
|
||||
this.retryComputedComponentRect(children)
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
transitionStyle() {
|
||||
const style = {
|
||||
zIndex: this.zIndex,
|
||||
position: 'fixed',
|
||||
display: 'flex',
|
||||
}
|
||||
style[this.mode] = 0
|
||||
if (this.mode === 'left') {
|
||||
return uni.$u.deepMerge(style, {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
})
|
||||
} else if (this.mode === 'right') {
|
||||
return uni.$u.deepMerge(style, {
|
||||
bottom: 0,
|
||||
top: 0,
|
||||
})
|
||||
} else if (this.mode === 'top') {
|
||||
return uni.$u.deepMerge(style, {
|
||||
left: 0,
|
||||
right: 0
|
||||
})
|
||||
} else if (this.mode === 'bottom') {
|
||||
return uni.$u.deepMerge(style, {
|
||||
left: 0,
|
||||
right: 0,
|
||||
})
|
||||
} else if (this.mode === 'center') {
|
||||
return uni.$u.deepMerge(style, {
|
||||
alignItems: 'center',
|
||||
'justify-content': 'center',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
})
|
||||
}
|
||||
},
|
||||
contentStyle() {
|
||||
const style = {}
|
||||
// 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置
|
||||
// 不使用css方案,是因为nvue不支持css的iPhoneX安全区查询属性
|
||||
const {
|
||||
safeAreaInsets
|
||||
} = uni.$u.sys()
|
||||
if (this.mode !== 'center') {
|
||||
style.flex = 1
|
||||
}
|
||||
// 背景色,一般用于设置为transparent,去除默认的白色背景
|
||||
if (this.bgColor) {
|
||||
style.backgroundColor = this.bgColor
|
||||
}
|
||||
if(this.round) {
|
||||
const value = uni.$u.addUnit(this.round)
|
||||
if(this.mode === 'top') {
|
||||
style.borderBottomLeftRadius = value
|
||||
style.borderBottomRightRadius = value
|
||||
} else if(this.mode === 'bottom') {
|
||||
style.borderTopLeftRadius = value
|
||||
style.borderTopRightRadius = value
|
||||
} else if(this.mode === 'center') {
|
||||
style.borderRadius = value
|
||||
}
|
||||
}
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
|
||||
},
|
||||
position() {
|
||||
if (this.mode === 'center') {
|
||||
return this.zoom ? 'fade-zoom' : 'fade'
|
||||
}
|
||||
if (this.mode === 'left') {
|
||||
return 'slide-left'
|
||||
}
|
||||
if (this.mode === 'right') {
|
||||
return 'slide-right'
|
||||
}
|
||||
if (this.mode === 'bottom') {
|
||||
return 'slide-up'
|
||||
}
|
||||
if (this.mode === 'top') {
|
||||
return 'slide-down'
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 点击遮罩
|
||||
overlayClick() {
|
||||
if (this.closeOnClickOverlay) {
|
||||
this.$emit('close')
|
||||
}
|
||||
},
|
||||
close(e) {
|
||||
this.$emit('close')
|
||||
},
|
||||
afterEnter() {
|
||||
this.$emit('open')
|
||||
},
|
||||
clickHandler() {
|
||||
// 由于中部弹出时,其u-transition占据了整个页面相当于遮罩,此时需要发出遮罩点击事件,是否无法通过点击遮罩关闭弹窗
|
||||
if(this.mode === 'center') {
|
||||
this.overlayClick()
|
||||
}
|
||||
this.$emit('click')
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
retryComputedComponentRect(children) {
|
||||
// 组件内部需要计算节点的组件
|
||||
const names = ['u-calendar-month', 'u-album', 'u-collapse-item', 'u-dropdown', 'u-index-item', 'u-index-list',
|
||||
'u-line-progress', 'u-list-item', 'u-rate', 'u-read-more', 'u-row', 'u-row-notice', 'u-scroll-list',
|
||||
'u-skeleton', 'u-slider', 'u-steps-item', 'u-sticky', 'u-subsection', 'u-swipe-action-item', 'u-tabbar',
|
||||
'u-tabs', 'u-tooltip'
|
||||
]
|
||||
// 历遍所有的子组件节点
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
// 拿到子组件的子组件
|
||||
const grandChild = child.$children
|
||||
// 判断如果在需要重新初始化的组件数组中名中,并且存在init方法的话,则执行
|
||||
if (names.includes(child.$options.name) && typeof child?.init === 'function') {
|
||||
// 需要进行一定的延时,因为初始化页面需要时间
|
||||
uni.$u.sleep(50).then(() => {
|
||||
child.init()
|
||||
})
|
||||
}
|
||||
// 如果子组件还有孙组件,进行递归历遍
|
||||
if (grandChild.length) {
|
||||
this.retryComputedComponentRect(grandChild)
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
$u-popup-flex:1 !default;
|
||||
$u-popup-content-background-color: #fff !default;
|
||||
|
||||
.u-popup {
|
||||
flex: $u-popup-flex;
|
||||
|
||||
&__content {
|
||||
background-color: $u-popup-content-background-color;
|
||||
position: relative;
|
||||
|
||||
&--round-top {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
&--round-left {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
&--round-right {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&--round-bottom {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
&--round-center {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
|
||||
&--hover {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
&__close--top-left {
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
&__close--top-right {
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
&__close--bottom-left {
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
&__close--bottom-right {
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
5
uni_modules/uview-ui/components/u-safe-bottom/props.js
Normal file
5
uni_modules/uview-ui/components/u-safe-bottom/props.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export default {
|
||||
props: {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-safe-bottom"
|
||||
:style="[style]"
|
||||
:class="[!isNvue && 'u-safe-area-inset-bottom']"
|
||||
>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from "./props.js";
|
||||
/**
|
||||
* SafeBottom 底部安全区
|
||||
* @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
|
||||
* @tutorial https://www.uviewui.com/components/safeAreaInset.html
|
||||
* @property {type} prop_name
|
||||
* @property {Object} customStyle 定义需要用到的外部样式
|
||||
*
|
||||
* @event {Function()}
|
||||
* @example <u-status-bar></u-status-bar>
|
||||
*/
|
||||
export default {
|
||||
name: "u-safe-bottom",
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
safeAreaBottomHeight: 0,
|
||||
isNvue: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
const style = {};
|
||||
// #ifdef APP-NVUE
|
||||
// nvue下,高度使用js计算填充
|
||||
style.height = uni.$u.addUnit(uni.$u.sys().safeAreaInsets.bottom, 'px');
|
||||
// #endif
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle));
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef APP-NVUE
|
||||
// 标识为是否nvue
|
||||
this.isNvue = true;
|
||||
// #endif
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-safe-bottom {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
8
uni_modules/uview-ui/components/u-status-bar/props.js
Normal file
8
uni_modules/uview-ui/components/u-status-bar/props.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export default {
|
||||
props: {
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.statusBar.bgColor
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<view
|
||||
:style="[style]"
|
||||
class="u-status-bar"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* StatbusBar 状态栏占位
|
||||
* @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
|
||||
* @tutorial https://uviewui.com/components/statusBar.html
|
||||
* @property {String} bgColor 背景色 (默认 'transparent' )
|
||||
* @property {String | Object} customStyle 自定义样式
|
||||
* @example <u-status-bar></u-status-bar>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-status-bar',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
const style = {}
|
||||
// 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
|
||||
style.height = uni.$u.addUnit(uni.$u.sys().statusBarHeight, 'px')
|
||||
style.backgroundColor = this.bgColor
|
||||
return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-status-bar {
|
||||
// nvue会默认100%,如果nvue下,显式写100%的话,会导致宽度不为100%而异常
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
84
uni_modules/uview-ui/components/u-tag/props.js
Normal file
84
uni_modules/uview-ui/components/u-tag/props.js
Normal file
@@ -0,0 +1,84 @@
|
||||
export default {
|
||||
props: {
|
||||
// 标签类型info、primary、success、warning、error
|
||||
type: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.type
|
||||
},
|
||||
// 不可用
|
||||
disabled: {
|
||||
type: [Boolean, String],
|
||||
default: uni.$u.props.tag.disabled
|
||||
},
|
||||
// 标签的大小,large,medium,mini
|
||||
size: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.size
|
||||
},
|
||||
// tag的形状,circle(两边半圆形), square(方形,带圆角)
|
||||
shape: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.shape
|
||||
},
|
||||
// 标签文字
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.tag.text
|
||||
},
|
||||
// 背景颜色,默认为空字符串,即不处理
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.bgColor
|
||||
},
|
||||
// 标签字体颜色,默认为空字符串,即不处理
|
||||
color: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.color
|
||||
},
|
||||
// 标签的边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.borderColor
|
||||
},
|
||||
// 关闭按钮图标的颜色
|
||||
closeColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.closeColor
|
||||
},
|
||||
// 点击时返回的索引值,用于区分例遍的数组哪个元素被点击了
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.tag.name
|
||||
},
|
||||
// // 模式选择,dark|light|plain
|
||||
// mode: {
|
||||
// type: String,
|
||||
// default: 'light'
|
||||
// },
|
||||
// 镂空时是否填充背景色
|
||||
plainFill: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.tag.plainFill
|
||||
},
|
||||
// 是否镂空
|
||||
plain: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.tag.plain
|
||||
},
|
||||
// 是否可关闭
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.tag.closable
|
||||
},
|
||||
// 是否显示
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.tag.show
|
||||
},
|
||||
// 内置图标,或绝对路径的图片
|
||||
icon: {
|
||||
type: String,
|
||||
default: uni.$u.props.tag.icon
|
||||
}
|
||||
}
|
||||
}
|
||||
358
uni_modules/uview-ui/components/u-tag/u-tag.vue
Normal file
358
uni_modules/uview-ui/components/u-tag/u-tag.vue
Normal file
@@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:show="show"
|
||||
>
|
||||
<view class="u-tag-wrapper">
|
||||
<view
|
||||
class="u-tag"
|
||||
:class="[`u-tag--${shape}`, !plain && `u-tag--${type}`, plain && `u-tag--${type}--plain`, `u-tag--${size}`, plain && plainFill && `u-tag--${type}--plain--fill`]"
|
||||
@tap.stop="clickHandler"
|
||||
:style="[{
|
||||
marginRight: closable ? '10px' : 0,
|
||||
marginTop: closable ? '10px' : 0,
|
||||
}, style]"
|
||||
>
|
||||
<slot name="icon">
|
||||
<view
|
||||
class="u-tag__icon"
|
||||
v-if="icon"
|
||||
>
|
||||
<image
|
||||
v-if="$u.test.image(icon)"
|
||||
:src="icon"
|
||||
:style="[imgStyle]"
|
||||
></image>
|
||||
<u-icon
|
||||
v-else
|
||||
:color="elIconColor"
|
||||
:name="icon"
|
||||
:size="iconSize"
|
||||
></u-icon>
|
||||
</view>
|
||||
</slot>
|
||||
<text
|
||||
class="u-tag__text"
|
||||
:style="[textColor]"
|
||||
:class="[`u-tag__text--${type}`, plain && `u-tag__text--${type}--plain`, `u-tag__text--${size}`]"
|
||||
>{{ text }}</text>
|
||||
</view>
|
||||
<view
|
||||
class="u-tag__close"
|
||||
:class="[`u-tag__close--${size}`]"
|
||||
v-if="closable"
|
||||
@tap.stop="closeHandler"
|
||||
:style="{backgroundColor: closeColor}"
|
||||
>
|
||||
<u-icon
|
||||
name="close"
|
||||
:size="closeSize"
|
||||
color="#ffffff"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</u-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Tag 标签
|
||||
* @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景
|
||||
* @tutorial https://www.uviewui.com/components/tag.html
|
||||
* @property {String} type 标签类型info、primary、success、warning、error (默认 'primary' )
|
||||
* @property {Boolean | String} disabled 不可用(默认 false )
|
||||
* @property {String} size 标签的大小,large,medium,mini (默认 'medium' )
|
||||
* @property {String} shape tag的形状,circle(两边半圆形), square(方形,带圆角)(默认 'square' )
|
||||
* @property {String | Number} text 标签的文字内容
|
||||
* @property {String} bgColor 背景颜色,默认为空字符串,即不处理
|
||||
* @property {String} color 标签字体颜色,默认为空字符串,即不处理
|
||||
* @property {String} borderColor 镂空形式标签的边框颜色
|
||||
* @property {String} closeColor 关闭按钮图标的颜色(默认 #C6C7CB)
|
||||
* @property {String | Number} name 点击时返回的索引值,用于区分例遍的数组哪个元素被点击了
|
||||
* @property {Boolean} plainFill 镂空时是否填充背景色(默认 false )
|
||||
* @property {Boolean} plain 是否镂空(默认 false )
|
||||
* @property {Boolean} closable 是否可关闭,设置为true,文字右边会出现一个关闭图标(默认 false )
|
||||
* @property {Boolean} show 标签显示与否(默认 true )
|
||||
* @property {String} icon 内置图标,或绝对路径的图片
|
||||
* @event {Function(index)} click 点击标签时触发 index: 传递的index参数值
|
||||
* @event {Function(index)} close closable为true时,点击标签关闭按钮触发 index: 传递的index参数值
|
||||
* @example <u-tag text="标签" type="error" plain plainFill></u-tag>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-tag',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
style() {
|
||||
const style = {}
|
||||
if (this.bgColor) {
|
||||
style.backgroundColor = this.bgColor
|
||||
}
|
||||
if (this.color) {
|
||||
style.color = this.color
|
||||
}
|
||||
if(this.borderColor) {
|
||||
style.borderColor = this.borderColor
|
||||
}
|
||||
return style
|
||||
},
|
||||
// nvue下,文本颜色无法继承父元素
|
||||
textColor() {
|
||||
const style = {}
|
||||
if (this.color) {
|
||||
style.color = this.color
|
||||
}
|
||||
return style
|
||||
},
|
||||
imgStyle() {
|
||||
const width = this.size === 'large' ? '17px' : this.size === 'medium' ? '15px' : '13px'
|
||||
return {
|
||||
width,
|
||||
height: width
|
||||
}
|
||||
},
|
||||
// 文本的样式
|
||||
closeSize() {
|
||||
const size = this.size === 'large' ? 15 : this.size === 'medium' ? 13 : 12
|
||||
return size
|
||||
},
|
||||
// 图标大小
|
||||
iconSize() {
|
||||
const size = this.size === 'large' ? 21 : this.size === 'medium' ? 19 : 16
|
||||
return size
|
||||
},
|
||||
// 图标颜色
|
||||
elIconColor() {
|
||||
return this.iconColor ? this.iconColor : this.plain ? this.type : '#ffffff'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击关闭按钮
|
||||
closeHandler() {
|
||||
this.$emit('close', this.name)
|
||||
},
|
||||
// 点击标签
|
||||
clickHandler() {
|
||||
this.$emit('click', this.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style
|
||||
lang="scss"
|
||||
scoped
|
||||
>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-tag-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.u-tag {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
border-style: solid;
|
||||
|
||||
&--circle {
|
||||
border-radius: 100px;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
&--mini {
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
&--large {
|
||||
font-size: 15px;
|
||||
line-height: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&--mini {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
height: 26px;
|
||||
line-height: 22px;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
&--large {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
background-color: $u-primary;
|
||||
border-width: 1px;
|
||||
border-color: $u-primary;
|
||||
}
|
||||
|
||||
&--primary--plain {
|
||||
border-width: 1px;
|
||||
border-color: $u-primary;
|
||||
}
|
||||
|
||||
&--primary--plain--fill {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
|
||||
&__text--primary {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--primary--plain {
|
||||
color: $u-primary;
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: $u-error;
|
||||
border-width: 1px;
|
||||
border-color: $u-error;
|
||||
}
|
||||
|
||||
&--error--plain {
|
||||
border-width: 1px;
|
||||
border-color: $u-error;
|
||||
}
|
||||
|
||||
&--error--plain--fill {
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
&__text--error {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--error--plain {
|
||||
color: $u-error;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
background-color: $u-warning;
|
||||
border-width: 1px;
|
||||
border-color: $u-warning;
|
||||
}
|
||||
|
||||
&--warning--plain {
|
||||
border-width: 1px;
|
||||
border-color: $u-warning;
|
||||
}
|
||||
|
||||
&--warning--plain--fill {
|
||||
background-color: #fdf6ec;
|
||||
}
|
||||
|
||||
&__text--warning {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--warning--plain {
|
||||
color: $u-warning;
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: $u-success;
|
||||
border-width: 1px;
|
||||
border-color: $u-success;
|
||||
}
|
||||
|
||||
&--success--plain {
|
||||
border-width: 1px;
|
||||
border-color: $u-success;
|
||||
}
|
||||
|
||||
&--success--plain--fill {
|
||||
background-color: #f5fff0;
|
||||
}
|
||||
|
||||
&__text--success {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--success--plain {
|
||||
color: $u-success;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background-color: $u-info;
|
||||
border-width: 1px;
|
||||
border-color: $u-info;
|
||||
}
|
||||
|
||||
&--info--plain {
|
||||
border-width: 1px;
|
||||
border-color: $u-info;
|
||||
}
|
||||
|
||||
&--info--plain--fill {
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
|
||||
&__text--info {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
&__text--info--plain {
|
||||
color: $u-info;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
border-radius: 100px;
|
||||
background-color: #C6C7CB;
|
||||
@include flex(row);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
transform: scale(0.6) translate(80%, -80%);
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
transform: scale(0.6) translate(50%, -50%);
|
||||
/* #endif */
|
||||
|
||||
&--mini {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&--medium {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
&--large {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
291
uni_modules/uview-ui/components/u-toast/u-toast.vue
Normal file
291
uni_modules/uview-ui/components/u-toast/u-toast.vue
Normal file
@@ -0,0 +1,291 @@
|
||||
<template>
|
||||
<view class="u-toast">
|
||||
<u-overlay
|
||||
:show="isShow"
|
||||
:custom-style="overlayStyle"
|
||||
>
|
||||
<view
|
||||
class="u-toast__content"
|
||||
:style="[contentStyle]"
|
||||
:class="['u-type-' + tmpConfig.type, (tmpConfig.type === 'loading' || tmpConfig.loading) ? 'u-toast__content--loading' : '']"
|
||||
>
|
||||
<u-loading-icon
|
||||
v-if="tmpConfig.type === 'loading'"
|
||||
mode="circle"
|
||||
color="rgb(255, 255, 255)"
|
||||
inactiveColor="rgb(120, 120, 120)"
|
||||
size="25"
|
||||
></u-loading-icon>
|
||||
<u-icon
|
||||
v-else-if="tmpConfig.type !== 'defalut' && iconName"
|
||||
:name="iconName"
|
||||
size="17"
|
||||
:color="tmpConfig.type"
|
||||
:customStyle="iconStyle"
|
||||
></u-icon>
|
||||
<u-gap
|
||||
v-if="tmpConfig.type === 'loading' || tmpConfig.loading"
|
||||
height="12"
|
||||
bgColor="transparent"
|
||||
></u-gap>
|
||||
<text
|
||||
class="u-toast__content__text"
|
||||
:class="['u-toast__content__text--' + tmpConfig.type]"
|
||||
style="max-width: 400rpx;"
|
||||
>{{ tmpConfig.message }}</text>
|
||||
</view>
|
||||
</u-overlay>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* toast 消息提示
|
||||
* @description 此组件表现形式类似uni的uni.showToastAPI,但也有不同的地方。
|
||||
* @tutorial https://www.uviewui.com/components/toast.html
|
||||
* @property {String | Number} zIndex toast展示时的zIndex值 (默认 10090 )
|
||||
* @property {Boolean} loading 是否加载中 (默认 false )
|
||||
* @property {String | Number} message 显示的文字内容
|
||||
* @property {String} icon 图标,或者绝对路径的图片
|
||||
* @property {String} type 主题类型 (默认 default)
|
||||
* @property {Boolean} show 是否显示该组件 (默认 false)
|
||||
* @property {Boolean} overlay 是否显示透明遮罩,防止点击穿透 (默认 false )
|
||||
* @property {String} position 位置 (默认 'center' )
|
||||
* @property {Object} params 跳转的参数
|
||||
* @property {String | Number} duration 展示时间,单位ms (默认 2000 )
|
||||
* @property {Boolean} isTab 是否返回的为tab页面 (默认 false )
|
||||
* @property {String} url toast消失后是否跳转页面,有则跳转,优先级高于back参数
|
||||
* @property {Function} complete 执行完后的回调函数
|
||||
* @property {Boolean} back 结束toast是否自动返回上一页 (默认 false )
|
||||
* @property {Object} customStyle 组件的样式,对象形式
|
||||
* @event {Function} show 显示toast,如需一进入页面就显示toast,请在onReady生命周期调用
|
||||
* @example <u-toast ref="uToast" />
|
||||
*/
|
||||
export default {
|
||||
name: 'u-toast',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin],
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
timer: null, // 定时器
|
||||
config: {
|
||||
message: '', // 显示文本
|
||||
type: '', // 主题类型,primary,success,error,warning,black
|
||||
duration: 2000, // 显示的时间,毫秒
|
||||
icon: true, // 显示的图标
|
||||
position: 'center', // toast出现的位置
|
||||
complete: null, // 执行完后的回调函数
|
||||
overlay: false, // 是否防止触摸穿透
|
||||
loading: false, // 是否加载中状态
|
||||
},
|
||||
tmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
iconName() {
|
||||
// 只有不为none,并且type为error|warning|succes|info时候,才显示图标
|
||||
if(!this.tmpConfig.icon || this.tmpConfig.icon == 'none') {
|
||||
return '';
|
||||
}
|
||||
if (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {
|
||||
return uni.$u.type2icon(this.tmpConfig.type)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
overlayStyle() {
|
||||
const style = {
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
display: 'flex'
|
||||
}
|
||||
// 将遮罩设置为100%透明度,避免出现灰色背景
|
||||
style.backgroundColor = 'rgba(0, 0, 0, 0)'
|
||||
return style
|
||||
},
|
||||
iconStyle() {
|
||||
const style = {}
|
||||
// 图标需要一个右边距,以跟右边的文字有隔开的距离
|
||||
style.marginRight = '4px'
|
||||
// #ifdef APP-NVUE
|
||||
// iOSAPP下,图标有1px的向下偏移,这里进行修正
|
||||
if (uni.$u.os() === 'ios') {
|
||||
style.marginTop = '-1px'
|
||||
}
|
||||
// #endif
|
||||
return style
|
||||
},
|
||||
loadingIconColor() {
|
||||
let color = 'rgb(255, 255, 255)'
|
||||
if (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {
|
||||
// loading-icon组件内部会对color参数进行一个透明度处理,该方法要求传入的颜色值
|
||||
// 必须为rgb格式的,所以这里做一个处理
|
||||
color = uni.$u.hexToRgb(uni.$u.color[this.tmpConfig.type])
|
||||
}
|
||||
return color
|
||||
},
|
||||
// 内容盒子的样式
|
||||
contentStyle() {
|
||||
const windowHeight = uni.$u.sys().windowHeight, style = {}
|
||||
let value = 0
|
||||
// 根据top和bottom,对Y轴进行窗体高度的百分比偏移
|
||||
if(this.tmpConfig.position === 'top') {
|
||||
value = - windowHeight * 0.25
|
||||
} else if(this.tmpConfig.position === 'bottom') {
|
||||
value = windowHeight * 0.25
|
||||
}
|
||||
style.transform = `translateY(${value}px)`
|
||||
return style
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 通过主题的形式调用toast,批量生成方法函数
|
||||
['primary', 'success', 'error', 'warning', 'default', 'loading'].map(item => {
|
||||
this[item] = message => this.show({
|
||||
type: item,
|
||||
message
|
||||
})
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
// 显示toast组件,由父组件通过this.$refs.xxx.show(options)形式调用
|
||||
show(options) {
|
||||
// 不将结果合并到this.config变量,避免多次调用u-toast,前后的配置造成混乱
|
||||
this.tmpConfig = uni.$u.deepMerge(this.config, options)
|
||||
// 清除定时器
|
||||
this.clearTimer()
|
||||
this.isShow = true
|
||||
this.timer = setTimeout(() => {
|
||||
// 倒计时结束,清除定时器,隐藏toast组件
|
||||
this.clearTimer()
|
||||
// 判断是否存在callback方法,如果存在就执行
|
||||
typeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()
|
||||
}, this.tmpConfig.duration)
|
||||
},
|
||||
// 隐藏toast组件,由父组件通过this.$refs.xxx.hide()形式调用
|
||||
hide() {
|
||||
this.clearTimer()
|
||||
},
|
||||
clearTimer() {
|
||||
this.isShow = false
|
||||
// 清除定时器
|
||||
clearTimeout(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearTimer()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
$u-toast-color:#fff !default;
|
||||
$u-toast-border-radius:4px !default;
|
||||
$u-toast-border-background-color:#585858 !default;
|
||||
$u-toast-border-font-size:14px !default;
|
||||
$u-toast-border-padding:12px 20px !default;
|
||||
$u-toast-loading-border-padding: 20px 20px !default;
|
||||
$u-toast-content-text-color:#fff !default;
|
||||
$u-toast-content-text-font-size:15px !default;
|
||||
$u-toast-u-icon:10rpx !default;
|
||||
$u-toast-u-type-primary-color:$u-primary !default;
|
||||
$u-toast-u-type-primary-background-color:#ecf5ff !default;
|
||||
$u-toast-u-type-primary-border-color:rgb(215, 234, 254) !default;
|
||||
$u-toast-u-type-primary-border-width:1px !default;
|
||||
$u-toast-u-type-success-color: $u-success !default;
|
||||
$u-toast-u-type-success-background-color: #dbf1e1 !default;
|
||||
$u-toast-u-type-success-border-color: #BEF5C8 !default;
|
||||
$u-toast-u-type-success-border-width: 1px !default;
|
||||
$u-toast-u-type-error-color:$u-error !default;
|
||||
$u-toast-u-type-error-background-color:#fef0f0 !default;
|
||||
$u-toast-u-type-error-border-color:#fde2e2 !default;
|
||||
$u-toast-u-type-error-border-width: 1px !default;
|
||||
$u-toast-u-type-warning-color:$u-warning !default;
|
||||
$u-toast-u-type-warning-background-color:#fdf6ec !default;
|
||||
$u-toast-u-type-warning-border-color:#faecd8 !default;
|
||||
$u-toast-u-type-warning-border-width: 1px !default;
|
||||
$u-toast-u-type-default-color:#fff !default;
|
||||
$u-toast-u-type-default-background-color:#585858 !default;
|
||||
|
||||
.u-toast {
|
||||
&__content {
|
||||
@include flex;
|
||||
padding: $u-toast-border-padding;
|
||||
border-radius: $u-toast-border-radius;
|
||||
background-color: $u-toast-border-background-color;
|
||||
color: $u-toast-color;
|
||||
align-items: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
max-width: 600rpx;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
|
||||
&--loading {
|
||||
flex-direction: column;
|
||||
padding: $u-toast-loading-border-padding;
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: $u-toast-content-text-color;
|
||||
font-size: $u-toast-content-text-font-size;
|
||||
line-height: $u-toast-content-text-font-size;
|
||||
|
||||
&--default {
|
||||
color: $u-toast-content-text-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
color: $u-error;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: $u-primary;
|
||||
}
|
||||
|
||||
&--success {
|
||||
color: $u-success;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: $u-warning;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.u-type-primary {
|
||||
color: $u-toast-u-type-primary-color;
|
||||
background-color: $u-toast-u-type-primary-background-color;
|
||||
border-color: $u-toast-u-type-primary-border-color;
|
||||
border-width: $u-toast-u-type-primary-border-width;
|
||||
}
|
||||
|
||||
.u-type-success {
|
||||
color: $u-toast-u-type-success-color;
|
||||
background-color: $u-toast-u-type-success-background-color;
|
||||
border-color: $u-toast-u-type-success-border-color;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.u-type-error {
|
||||
color: $u-toast-u-type-error-color;
|
||||
background-color: $u-toast-u-type-error-background-color;
|
||||
border-color: $u-toast-u-type-error-border-color;
|
||||
border-width: $u-toast-u-type-error-border-width;
|
||||
}
|
||||
|
||||
.u-type-warning {
|
||||
color: $u-toast-u-type-warning-color;
|
||||
background-color: $u-toast-u-type-warning-background-color;
|
||||
border-color: $u-toast-u-type-warning-border-color;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
.u-type-default {
|
||||
color: $u-toast-u-type-default-color;
|
||||
background-color: $u-toast-u-type-default-background-color;
|
||||
}
|
||||
</style>
|
||||
34
uni_modules/uview-ui/components/u-toolbar/props.js
Normal file
34
uni_modules/uview-ui/components/u-toolbar/props.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否展示工具条
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.toolbar.show
|
||||
},
|
||||
// 取消按钮的文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: uni.$u.props.toolbar.cancelText
|
||||
},
|
||||
// 确认按钮的文字
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: uni.$u.props.toolbar.confirmText
|
||||
},
|
||||
// 取消按钮的颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.toolbar.cancelColor
|
||||
},
|
||||
// 确认按钮的颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: uni.$u.props.toolbar.confirmColor
|
||||
},
|
||||
// 标题文字
|
||||
title: {
|
||||
type: String,
|
||||
default: uni.$u.props.toolbar.title
|
||||
}
|
||||
}
|
||||
}
|
||||
102
uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue
Normal file
102
uni_modules/uview-ui/components/u-toolbar/u-toolbar.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-toolbar"
|
||||
@touchmove.stop.prevent="noop"
|
||||
v-if="show"
|
||||
>
|
||||
<view
|
||||
class="u-toolbar__cancel__wrapper"
|
||||
hover-class="u-hover-class"
|
||||
>
|
||||
<text
|
||||
class="u-toolbar__wrapper__cancel"
|
||||
@tap="cancel"
|
||||
:style="{
|
||||
color: cancelColor
|
||||
}"
|
||||
>{{ cancelText }}</text>
|
||||
</view>
|
||||
<text
|
||||
class="u-toolbar__title u-line-1"
|
||||
v-if="title"
|
||||
>{{ title }}</text>
|
||||
<view
|
||||
class="u-toolbar__confirm__wrapper"
|
||||
hover-class="u-hover-class"
|
||||
>
|
||||
<text
|
||||
class="u-toolbar__wrapper__confirm"
|
||||
@tap="confirm"
|
||||
:style="{
|
||||
color: confirmColor
|
||||
}"
|
||||
>{{ confirmText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
/**
|
||||
* Toolbar 工具条
|
||||
* @description
|
||||
* @tutorial https://www.uviewui.com/components/toolbar.html
|
||||
* @property {Boolean} show 是否展示工具条(默认 true )
|
||||
* @property {String} cancelText 取消按钮的文字(默认 '取消' )
|
||||
* @property {String} confirmText 确认按钮的文字(默认 '确认' )
|
||||
* @property {String} cancelColor 取消按钮的颜色(默认 '#909193' )
|
||||
* @property {String} confirmColor 确认按钮的颜色(默认 '#3c9cff' )
|
||||
* @property {String} title 标题文字
|
||||
* @event {Function}
|
||||
* @example
|
||||
*/
|
||||
export default {
|
||||
name: 'u-toolbar',
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
|
||||
methods: {
|
||||
// 点击取消按钮
|
||||
cancel() {
|
||||
this.$emit('cancel')
|
||||
},
|
||||
// 点击确定按钮
|
||||
confirm() {
|
||||
this.$emit('confirm')
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-toolbar {
|
||||
height: 42px;
|
||||
@include flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
&__wrapper {
|
||||
&__cancel {
|
||||
color: $u-tips-color;
|
||||
font-size: 15px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: $u-main-color;
|
||||
padding: 0 60rpx;
|
||||
font-size: 16px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
&__confirm {
|
||||
color: $u-primary;
|
||||
font-size: 15px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
uni_modules/uview-ui/components/u-transition/nvue.ani-map.js
Normal file
68
uni_modules/uview-ui/components/u-transition/nvue.ani-map.js
Normal file
@@ -0,0 +1,68 @@
|
||||
export default {
|
||||
fade: {
|
||||
enter: { opacity: 0 },
|
||||
'enter-to': { opacity: 1 },
|
||||
leave: { opacity: 1 },
|
||||
'leave-to': { opacity: 0 }
|
||||
},
|
||||
'fade-up': {
|
||||
enter: { opacity: 0, transform: 'translateY(100%)' },
|
||||
'enter-to': { opacity: 1, transform: 'translateY(0)' },
|
||||
leave: { opacity: 1, transform: 'translateY(0)' },
|
||||
'leave-to': { opacity: 0, transform: 'translateY(100%)' }
|
||||
},
|
||||
'fade-down': {
|
||||
enter: { opacity: 0, transform: 'translateY(-100%)' },
|
||||
'enter-to': { opacity: 1, transform: 'translateY(0)' },
|
||||
leave: { opacity: 1, transform: 'translateY(0)' },
|
||||
'leave-to': { opacity: 0, transform: 'translateY(-100%)' }
|
||||
},
|
||||
'fade-left': {
|
||||
enter: { opacity: 0, transform: 'translateX(-100%)' },
|
||||
'enter-to': { opacity: 1, transform: 'translateY(0)' },
|
||||
leave: { opacity: 1, transform: 'translateY(0)' },
|
||||
'leave-to': { opacity: 0, transform: 'translateX(-100%)' }
|
||||
},
|
||||
'fade-right': {
|
||||
enter: { opacity: 0, transform: 'translateX(100%)' },
|
||||
'enter-to': { opacity: 1, transform: 'translateY(0)' },
|
||||
leave: { opacity: 1, transform: 'translateY(0)' },
|
||||
'leave-to': { opacity: 0, transform: 'translateX(100%)' }
|
||||
},
|
||||
'slide-up': {
|
||||
enter: { transform: 'translateY(100%)' },
|
||||
'enter-to': { transform: 'translateY(0)' },
|
||||
leave: { transform: 'translateY(0)' },
|
||||
'leave-to': { transform: 'translateY(100%)' }
|
||||
},
|
||||
'slide-down': {
|
||||
enter: { transform: 'translateY(-100%)' },
|
||||
'enter-to': { transform: 'translateY(0)' },
|
||||
leave: { transform: 'translateY(0)' },
|
||||
'leave-to': { transform: 'translateY(-100%)' }
|
||||
},
|
||||
'slide-left': {
|
||||
enter: { transform: 'translateX(-100%)' },
|
||||
'enter-to': { transform: 'translateY(0)' },
|
||||
leave: { transform: 'translateY(0)' },
|
||||
'leave-to': { transform: 'translateX(-100%)' }
|
||||
},
|
||||
'slide-right': {
|
||||
enter: { transform: 'translateX(100%)' },
|
||||
'enter-to': { transform: 'translateY(0)' },
|
||||
leave: { transform: 'translateY(0)' },
|
||||
'leave-to': { transform: 'translateX(100%)' }
|
||||
},
|
||||
zoom: {
|
||||
enter: { transform: 'scale(0.95)' },
|
||||
'enter-to': { transform: 'scale(1)' },
|
||||
leave: { transform: 'scale(1)' },
|
||||
'leave-to': { transform: 'scale(0.95)' }
|
||||
},
|
||||
'fade-zoom': {
|
||||
enter: { opacity: 0, transform: 'scale(0.95)' },
|
||||
'enter-to': { opacity: 1, transform: 'scale(1)' },
|
||||
leave: { opacity: 1, transform: 'scale(1)' },
|
||||
'leave-to': { opacity: 0, transform: 'scale(0.95)' }
|
||||
}
|
||||
}
|
||||
24
uni_modules/uview-ui/components/u-transition/props.js
Normal file
24
uni_modules/uview-ui/components/u-transition/props.js
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否展示组件
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: uni.$u.props.transition.show
|
||||
},
|
||||
// 使用的动画模式
|
||||
mode: {
|
||||
type: String,
|
||||
default: uni.$u.props.transition.mode
|
||||
},
|
||||
// 动画的执行时间,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: uni.$u.props.transition.duration
|
||||
},
|
||||
// 使用的动画过渡函数
|
||||
timingFunction: {
|
||||
type: String,
|
||||
default: uni.$u.props.transition.timingFunction
|
||||
}
|
||||
}
|
||||
}
|
||||
157
uni_modules/uview-ui/components/u-transition/transition.js
Normal file
157
uni_modules/uview-ui/components/u-transition/transition.js
Normal file
@@ -0,0 +1,157 @@
|
||||
// 定义一个一定时间后自动成功的promise,让调用nextTick方法处,进入下一个then方法
|
||||
const nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 50))
|
||||
// nvue动画模块实现细节抽离在外部文件
|
||||
import animationMap from './nvue.ani-map.js'
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
// 定义类名,通过给元素动态切换类名,赋予元素一定的css动画样式
|
||||
const getClassNames = (name) => ({
|
||||
enter: `u-${name}-enter u-${name}-enter-active`,
|
||||
'enter-to': `u-${name}-enter-to u-${name}-enter-active`,
|
||||
leave: `u-${name}-leave u-${name}-leave-active`,
|
||||
'leave-to': `u-${name}-leave-to u-${name}-leave-active`
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
// 引入nvue(weex)的animation动画模块,文档见:
|
||||
// https://weex.apache.org/zh/docs/modules/animation.html#transition
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
const getStyle = (name) => animationMap[name]
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
// 组件被点击发出事件
|
||||
clickHandler() {
|
||||
this.$emit('click')
|
||||
},
|
||||
// #ifndef APP-NVUE
|
||||
// vue版本的组件进场处理
|
||||
vueEnter() {
|
||||
// 动画进入时的类名
|
||||
const classNames = getClassNames(this.mode)
|
||||
// 定义状态和发出动画进入前事件
|
||||
this.status = 'enter'
|
||||
this.$emit('beforeEnter')
|
||||
this.inited = true
|
||||
this.display = true
|
||||
this.classes = classNames.enter
|
||||
this.$nextTick(async () => {
|
||||
// #ifdef H5
|
||||
await uni.$u.sleep(20)
|
||||
// #endif
|
||||
// 标识动画尚未结束
|
||||
this.$emit('enter')
|
||||
this.transitionEnded = false
|
||||
// 组件动画进入后触发的事件
|
||||
this.$emit('afterEnter')
|
||||
// 赋予组件enter-to类名
|
||||
this.classes = classNames['enter-to']
|
||||
})
|
||||
},
|
||||
// 动画离场处理
|
||||
vueLeave() {
|
||||
// 如果不是展示状态,无需执行逻辑
|
||||
if (!this.display) return
|
||||
const classNames = getClassNames(this.mode)
|
||||
// 标记离开状态和发出事件
|
||||
this.status = 'leave'
|
||||
this.$emit('beforeLeave')
|
||||
// 获得类名
|
||||
this.classes = classNames.leave
|
||||
|
||||
this.$nextTick(() => {
|
||||
// 动画正在离场的状态
|
||||
this.transitionEnded = false
|
||||
this.$emit('leave')
|
||||
// 组件执行动画,到了执行的执行时间后,执行一些额外处理
|
||||
setTimeout(this.onTransitionEnd, this.duration)
|
||||
this.classes = classNames['leave-to']
|
||||
})
|
||||
},
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
// nvue版本动画进场
|
||||
nvueEnter() {
|
||||
// 获得样式的名称
|
||||
const currentStyle = getStyle(this.mode)
|
||||
// 组件动画状态和发出事件
|
||||
this.status = 'enter'
|
||||
this.$emit('beforeEnter')
|
||||
// 展示生成组件元素
|
||||
this.inited = true
|
||||
this.display = true
|
||||
// 在nvue安卓上,由于渲染速度慢,在弹窗,键盘,日历等组件中,渲染其中的内容需要时间
|
||||
// 导致出现弹窗卡顿,这里让其一开始为透明状态,等一定时间渲染完成后,再让其隐藏起来,再让其按正常逻辑出现
|
||||
this.viewStyle = {
|
||||
opacity: 0
|
||||
}
|
||||
// 等待弹窗内容渲染完成
|
||||
this.$nextTick(() => {
|
||||
// 合并样式
|
||||
this.viewStyle = currentStyle.enter
|
||||
Promise.resolve()
|
||||
.then(nextTick)
|
||||
.then(() => {
|
||||
// 组件开始进入前的事件
|
||||
this.$emit('enter')
|
||||
// nvue的transition动画模块需要通过ref调用组件,注意此处的ref不同于vue的this.$refs['u-transition']用法
|
||||
animation.transition(this.$refs['u-transition'].ref, {
|
||||
styles: currentStyle['enter-to'],
|
||||
duration: this.duration,
|
||||
timingFunction: this.timingFunction,
|
||||
needLayout: false,
|
||||
delay: 0
|
||||
}, () => {
|
||||
// 动画执行完毕,发出事件
|
||||
this.$emit('afterEnter')
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
},
|
||||
nvueLeave() {
|
||||
if (!this.display) {
|
||||
return
|
||||
}
|
||||
const currentStyle = getStyle(this.mode)
|
||||
// 定义状态和事件
|
||||
this.status = 'leave'
|
||||
this.$emit('beforeLeave')
|
||||
// 合并样式
|
||||
this.viewStyle = currentStyle.leave
|
||||
// 放到promise中处理执行过程
|
||||
Promise.resolve()
|
||||
.then(nextTick) // 等待几十ms
|
||||
.then(() => {
|
||||
this.transitionEnded = false
|
||||
// 动画正在离场的状态
|
||||
this.$emit('leave')
|
||||
animation.transition(this.$refs['u-transition'].ref, {
|
||||
styles: currentStyle['leave-to'],
|
||||
duration: this.duration,
|
||||
timingFunction: this.timingFunction,
|
||||
needLayout: false,
|
||||
delay: 0
|
||||
}, () => {
|
||||
this.onTransitionEnd()
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
// #endif
|
||||
// 完成过渡后触发
|
||||
onTransitionEnd() {
|
||||
// 如果已经是结束的状态,无需再处理
|
||||
if (this.transitionEnded) return
|
||||
this.transitionEnded = true
|
||||
// 发出组件动画执行后的事件
|
||||
this.$emit(this.status === 'leave' ? 'afterLeave' : 'afterEnter')
|
||||
if (!this.show && this.display) {
|
||||
this.display = false
|
||||
this.inited = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<view
|
||||
v-if="inited"
|
||||
class="u-transition"
|
||||
ref="u-transition"
|
||||
@tap="clickHandler"
|
||||
:class="classes"
|
||||
:style="[mergeStyle]"
|
||||
@touchmove="noop"
|
||||
>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
// 组件的methods方法,由于内容较长,写在外部文件中通过mixin引入
|
||||
import transition from "./transition.js";
|
||||
/**
|
||||
* transition 动画组件
|
||||
* @description
|
||||
* @tutorial
|
||||
* @property {String} show 是否展示组件 (默认 false )
|
||||
* @property {String} mode 使用的动画模式 (默认 'fade' )
|
||||
* @property {String | Number} duration 动画的执行时间,单位ms (默认 '300' )
|
||||
* @property {String} timingFunction 使用的动画过渡函数 (默认 'ease-out' )
|
||||
* @property {Object} customStyle 自定义样式
|
||||
* @event {Function} before-enter 进入前触发
|
||||
* @event {Function} enter 进入中触发
|
||||
* @event {Function} after-enter 进入后触发
|
||||
* @event {Function} before-leave 离开前触发
|
||||
* @event {Function} leave 离开中触发
|
||||
* @event {Function} after-leave 离开后触发
|
||||
* @example
|
||||
*/
|
||||
export default {
|
||||
name: 'u-transition',
|
||||
data() {
|
||||
return {
|
||||
inited: false, // 是否显示/隐藏组件
|
||||
viewStyle: {}, // 组件内部的样式
|
||||
status: '', // 记录组件动画的状态
|
||||
transitionEnded: false, // 组件是否结束的标记
|
||||
display: false, // 组件是否展示
|
||||
classes: '', // 应用的类名
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mergeStyle() {
|
||||
const { viewStyle, customStyle } = this
|
||||
return {
|
||||
// #ifndef APP-NVUE
|
||||
transitionDuration: `${this.duration}ms`,
|
||||
// display: `${this.display ? '' : 'none'}`,
|
||||
transitionTimingFunction: this.timingFunction,
|
||||
// #endif
|
||||
// 避免自定义样式影响到动画属性,所以写在viewStyle前面
|
||||
...uni.$u.addStyle(customStyle),
|
||||
...viewStyle
|
||||
}
|
||||
}
|
||||
},
|
||||
// 将mixin挂在到组件中,uni.$u.mixin实际上为一个vue格式对象
|
||||
mixins: [uni.$u.mpMixin, uni.$u.mixin, transition, props],
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
// vue和nvue分别执行不同的方法
|
||||
// #ifdef APP-NVUE
|
||||
newVal ? this.nvueEnter() : this.nvueLeave()
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
newVal ? this.vueEnter() : this.vueLeave()
|
||||
// #endif
|
||||
},
|
||||
// 表示同时监听初始化时的props的show的意思
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../libs/css/components.scss';
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
// vue版本动画相关的样式抽离在外部文件
|
||||
@import './vue.ani-style.scss';
|
||||
/* #endif */
|
||||
|
||||
.u-transition {}
|
||||
</style>
|
||||
113
uni_modules/uview-ui/components/u-transition/vue.ani-style.scss
Normal file
113
uni_modules/uview-ui/components/u-transition/vue.ani-style.scss
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* vue版本动画内置的动画模式有如下:
|
||||
* fade:淡入
|
||||
* zoom:缩放
|
||||
* fade-zoom:缩放淡入
|
||||
* fade-up:上滑淡入
|
||||
* fade-down:下滑淡入
|
||||
* fade-left:左滑淡入
|
||||
* fade-right:右滑淡入
|
||||
* slide-up:上滑进入
|
||||
* slide-down:下滑进入
|
||||
* slide-left:左滑进入
|
||||
* slide-right:右滑进入
|
||||
*/
|
||||
|
||||
$u-zoom-scale: scale(0.95);
|
||||
|
||||
.u-fade-enter-active,
|
||||
.u-fade-leave-active {
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.u-fade-enter,
|
||||
.u-fade-leave-to {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.u-fade-zoom-enter,
|
||||
.u-fade-zoom-leave-to {
|
||||
transform: $u-zoom-scale;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.u-fade-zoom-enter-active,
|
||||
.u-fade-zoom-leave-active {
|
||||
transition-property: transform, opacity;
|
||||
}
|
||||
|
||||
.u-fade-down-enter-active,
|
||||
.u-fade-down-leave-active,
|
||||
.u-fade-left-enter-active,
|
||||
.u-fade-left-leave-active,
|
||||
.u-fade-right-enter-active,
|
||||
.u-fade-right-leave-active,
|
||||
.u-fade-up-enter-active,
|
||||
.u-fade-up-leave-active {
|
||||
transition-property: opacity, transform;
|
||||
}
|
||||
|
||||
.u-fade-up-enter,
|
||||
.u-fade-up-leave-to {
|
||||
transform: translate3d(0, 100%, 0);
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.u-fade-down-enter,
|
||||
.u-fade-down-leave-to {
|
||||
transform: translate3d(0, -100%, 0);
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.u-fade-left-enter,
|
||||
.u-fade-left-leave-to {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.u-fade-right-enter,
|
||||
.u-fade-right-leave-to {
|
||||
transform: translate3d(100%, 0, 0);
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.u-slide-down-enter-active,
|
||||
.u-slide-down-leave-active,
|
||||
.u-slide-left-enter-active,
|
||||
.u-slide-left-leave-active,
|
||||
.u-slide-right-enter-active,
|
||||
.u-slide-right-leave-active,
|
||||
.u-slide-up-enter-active,
|
||||
.u-slide-up-leave-active {
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.u-slide-up-enter,
|
||||
.u-slide-up-leave-to {
|
||||
transform: translate3d(0, 100%, 0)
|
||||
}
|
||||
|
||||
.u-slide-down-enter,
|
||||
.u-slide-down-leave-to {
|
||||
transform: translate3d(0, -100%, 0)
|
||||
}
|
||||
|
||||
.u-slide-left-enter,
|
||||
.u-slide-left-leave-to {
|
||||
transform: translate3d(-100%, 0, 0)
|
||||
}
|
||||
|
||||
.u-slide-right-enter,
|
||||
.u-slide-right-leave-to {
|
||||
transform: translate3d(100%, 0, 0)
|
||||
}
|
||||
|
||||
.u-zoom-enter-active,
|
||||
.u-zoom-leave-active {
|
||||
transition-property: transform
|
||||
}
|
||||
|
||||
.u-zoom-enter,
|
||||
.u-zoom-leave-to {
|
||||
transform: $u-zoom-scale
|
||||
}
|
||||
15
uni_modules/uview-ui/components/uview-ui/uview-ui.vue
Normal file
15
uni_modules/uview-ui/components/uview-ui/uview-ui.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
Reference in New Issue
Block a user