初始化仓库
This commit is contained in:
307
components/d-dropdown/d-dropdown.vue
Normal file
307
components/d-dropdown/d-dropdown.vue
Normal file
@@ -0,0 +1,307 @@
|
||||
<template>
|
||||
<view class="u-dropdown" :style="dropDownShow ? '' :'overflow:hidden'">
|
||||
<view class="u-dropdown__menu" :style="{
|
||||
height: $u.addUnit(height)
|
||||
}" :class="{
|
||||
'u-border-bottom': borderBottom
|
||||
}">
|
||||
<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index"
|
||||
@tap.stop="menuClick(index)">
|
||||
<view class="u-flex">
|
||||
<text class="u-dropdown__menu__item__text" :style="{
|
||||
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
|
||||
fontSize: $u.addUnit(titleSize)
|
||||
}">{{item.title}}</text>
|
||||
<view class="u-dropdown__menu__item__arrow" :class="{
|
||||
'u-dropdown__menu__item__arrow--rotate': index === current
|
||||
}">
|
||||
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)"
|
||||
:color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="u-dropdown__content" :style="[contentStyle, {
|
||||
transition: `opacity ${duration / 1000}s linear`,
|
||||
top: $u.addUnit(height),
|
||||
height: contentHeight + 'px'
|
||||
}]" @tap="maskClick" @touchmove.stop.prevent>
|
||||
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="u-dropdown__content__mask"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* dropdown 下拉菜单
|
||||
* @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
|
||||
* @tutorial http://uviewui.com/components/dropdown.html
|
||||
* @property {String} active-color 标题和选项卡选中的颜色(默认#2979ff)
|
||||
* @property {String} inactive-color 标题和选项卡未选中的颜色(默认#606266)
|
||||
* @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单(默认true)
|
||||
* @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单(默认true)
|
||||
* @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
|
||||
* @property {String | Number} height 标题菜单的高度,单位任意(默认80)
|
||||
* @property {String | Number} border-radius 菜单展开内容下方的圆角值,单位任意(默认0)
|
||||
* @property {Boolean} border-bottom 标题菜单是否显示下边框(默认false)
|
||||
* @property {String | Number} title-size 标题的字体大小,单位任意,数值默认为rpx单位(默认28)
|
||||
* @event {Function} open 下拉菜单被打开时触发
|
||||
* @event {Function} close 下拉菜单被关闭时触发
|
||||
* @example <u-dropdown></u-dropdown>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-dropdown',
|
||||
props: {
|
||||
// 菜单标题和选项的激活态颜色
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#2979ff'
|
||||
},
|
||||
// 菜单标题和选项的未激活态颜色
|
||||
inactiveColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 点击遮罩是否关闭菜单
|
||||
closeOnClickMask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 点击当前激活项标题是否关闭菜单
|
||||
closeOnClickSelf: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 过渡时间
|
||||
duration: {
|
||||
type: [Number, String],
|
||||
default: 300
|
||||
},
|
||||
// 标题菜单的高度,单位任意,数值默认为rpx单位
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: 80
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题的字体大小
|
||||
titleSize: {
|
||||
type: [Number, String],
|
||||
default: 28
|
||||
},
|
||||
// 下拉出来的内容部分的圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 菜单右侧的icon图标
|
||||
menuIcon: {
|
||||
type: String,
|
||||
default: 'arrow-down'
|
||||
},
|
||||
// 菜单右侧图标的大小
|
||||
menuIconSize: {
|
||||
type: [Number, String],
|
||||
default: 26
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDropdown: true, // 是否打开下来菜单,
|
||||
menuList: [], // 显示的菜单
|
||||
active: false, // 下拉菜单的状态
|
||||
// 当前是第几个菜单处于激活状态,小程序中此处不能写成false或者"",否则后续将current赋值为0,
|
||||
// 无能的TX没有使用===而是使用==判断,导致程序认为前后二者没有变化,从而不会触发视图更新
|
||||
current: 99999,
|
||||
// 外层内容的样式,初始时处于底层,且透明
|
||||
contentStyle: {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
},
|
||||
// 让某个菜单保持高亮的状态
|
||||
highlightIndex: 99999,
|
||||
contentHeight: 0,
|
||||
dropDownShow: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 下拉出来部分的样式
|
||||
popupStyle() {
|
||||
let style = {};
|
||||
// 进行Y轴位移,展开状态时,恢复原位。收齐状态时,往上位移100%,进行隐藏
|
||||
style.transform = `translateY(${this.active ? 0 : '-100%'})`
|
||||
style['transition-duration'] = this.duration / 1000 + 's';
|
||||
style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
|
||||
this.children = [];
|
||||
},
|
||||
mounted() {
|
||||
this.getContentHeight();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 当某个子组件内容变化时,触发父组件的init,父组件再让每一个子组件重新初始化一遍
|
||||
// 以保证数据的正确性
|
||||
this.menuList = [];
|
||||
this.children.map(child => {
|
||||
child.init();
|
||||
})
|
||||
},
|
||||
// 点击菜单
|
||||
menuClick(index) {
|
||||
// 判断是否被禁用
|
||||
if (this.menuList[index].disabled) return;
|
||||
// 如果点击时的索引和当前激活项索引相同,意味着点击了激活项,需要收起下拉菜单
|
||||
if (index === this.current && this.closeOnClickSelf) {
|
||||
this.close();
|
||||
// 等动画结束后,再移除下拉菜单中的内容,否则直接移除,也就没有下拉菜单收起的效果了
|
||||
setTimeout(() => {
|
||||
this.children[index].active = false;
|
||||
}, this.duration)
|
||||
return;
|
||||
}
|
||||
this.open(index);
|
||||
},
|
||||
// 打开下拉菜单
|
||||
open(index) {
|
||||
this.dropDownShow = true
|
||||
// 重置高亮索引,否则会造成多个菜单同时高亮
|
||||
// this.highlightIndex = 9999;
|
||||
// 展开时,设置下拉内容的样式
|
||||
this.contentStyle = {
|
||||
zIndex: 11,
|
||||
}
|
||||
// 标记展开状态以及当前展开项的索引
|
||||
this.active = true;
|
||||
this.current = index;
|
||||
// 历遍所有的子元素,将索引匹配的项标记为激活状态,因为子元素是通过v-if控制切换的
|
||||
// 之所以不是因display: none,是因为nvue没有display这个属性
|
||||
this.children.map((val, idx) => {
|
||||
val.active = index == idx ? true : false;
|
||||
})
|
||||
this.$emit('open', this.current);
|
||||
},
|
||||
// 设置下拉菜单处于收起状态
|
||||
close() {
|
||||
this.dropDownShow = false
|
||||
this.$emit('close', this.current);
|
||||
// 设置为收起状态,同时current归位,设置为空字符串
|
||||
this.active = false;
|
||||
this.current = 99999;
|
||||
// 下拉内容的样式进行调整,不透明度设置为0
|
||||
this.contentStyle = {
|
||||
zIndex: -1,
|
||||
opacity: 0
|
||||
}
|
||||
},
|
||||
// 点击遮罩
|
||||
maskClick() {
|
||||
// 如果不允许点击遮罩,直接返回
|
||||
if (!this.closeOnClickMask) return;
|
||||
this.close();
|
||||
},
|
||||
// 外部手动设置某个菜单高亮
|
||||
highlight(index = undefined) {
|
||||
this.highlightIndex = index !== undefined ? index : 99999;
|
||||
},
|
||||
// 获取下拉菜单内容的高度
|
||||
getContentHeight() {
|
||||
// 这里的原理为,因为dropdown组件是相对定位的,它的下拉出来的内容,必须给定一个高度
|
||||
// 才能让遮罩占满菜单一下,直到屏幕底部的高度
|
||||
// this.$u.sys()为uView封装的获取设备信息的方法
|
||||
let windowHeight = this.$u.sys().windowHeight;
|
||||
this.$uGetRect('.u-dropdown__menu').then(res => {
|
||||
// 这里获取的是dropdown的尺寸,在H5上,uniapp获取尺寸是有bug的(以前提出修复过,后来又出现了此bug,目前hx2.8.11版本)
|
||||
// H5端bug表现为元素尺寸的top值为导航栏底部到到元素的上边沿的距离,但是元素的bottom值确是导航栏顶部到元素底部的距离
|
||||
// 二者是互相矛盾的,本质原因是H5端导航栏非原生,uni的开发者大意造成
|
||||
// 这里取菜单栏的botton值合理的,不能用res.top,否则页面会造成滚动
|
||||
this.contentHeight = windowHeight - res.bottom;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// 定义混入指令,用于在非nvue环境下的flex定义,因为nvue没有display属性,会报错
|
||||
@mixin vue-flex($direction: row) {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: $direction;
|
||||
/* #endif */
|
||||
}
|
||||
.u-dropdown {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
&__menu {
|
||||
@include vue-flex;
|
||||
position: relative;
|
||||
z-index: 11;
|
||||
height: 80rpx;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
@include vue-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__text {
|
||||
font-size: 28rpx;
|
||||
color: $u-content-color;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
margin-left: 6rpx;
|
||||
transition: transform .3s;
|
||||
align-items: center;
|
||||
@include vue-flex;
|
||||
|
||||
&--rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: absolute;
|
||||
z-index: 8;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
|
||||
|
||||
&__mask {
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
background: rgba(0, 0, 0, .3);
|
||||
width: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&__popup {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: all 0.3s;
|
||||
transform: translate3D(0, -100%, 0);
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
459
components/d-input/d-input.vue
Normal file
459
components/d-input/d-input.vue
Normal file
@@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<view
|
||||
class="u-input"
|
||||
:class="{
|
||||
'u-input--border': border,
|
||||
'u-input--error': validateState
|
||||
}"
|
||||
:style="{
|
||||
padding: `0 ${border ? 20 : 34}rpx`,
|
||||
borderColor: borderColor,
|
||||
textAlign: inputAlign,
|
||||
borderRadius:borderRadius
|
||||
}"
|
||||
@tap.stop="inputClick"
|
||||
>
|
||||
<view
|
||||
class="u-input__prefix-icon"
|
||||
v-if="prefixIcon || $slots.prefix"
|
||||
>
|
||||
<slot name="prefix">
|
||||
<u-icon
|
||||
:name="prefixIcon"
|
||||
:size="prefixIconSize"
|
||||
:customStyle="prefixIconStyle"
|
||||
:color="prefixIconColor"
|
||||
></u-icon>
|
||||
</slot>
|
||||
</view>
|
||||
<textarea
|
||||
v-if="type == 'textarea'"
|
||||
class="u-input__input u-input__textarea"
|
||||
:style="[getStyle]"
|
||||
:value="defaultValue"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled"
|
||||
:maxlength="inputMaxlength"
|
||||
:fixed="fixed"
|
||||
:focus="focus"
|
||||
:autoHeight="autoHeight"
|
||||
:selection-end="uSelectionEnd"
|
||||
:selection-start="uSelectionStart"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
:show-confirm-bar="showConfirmbar"
|
||||
@input="handleInput"
|
||||
@blur="handleBlur"
|
||||
@focus="onFocus"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
class="u-input__input"
|
||||
:type="type == 'password' ? 'text' : type"
|
||||
:style="[getStyle]"
|
||||
:value="defaultValue"
|
||||
:password="type == 'password' && !showPassword"
|
||||
:placeholder="placeholder"
|
||||
:placeholderStyle="placeholderStyle"
|
||||
:disabled="disabled || type === 'select'"
|
||||
:maxlength="inputMaxlength"
|
||||
:focus="focus"
|
||||
:confirmType="confirmType"
|
||||
:cursor-spacing="getCursorSpacing"
|
||||
:selection-end="uSelectionEnd"
|
||||
:selection-start="uSelectionStart"
|
||||
:show-confirm-bar="showConfirmbar"
|
||||
@focus="onFocus"
|
||||
@blur="handleBlur"
|
||||
@input="handleInput"
|
||||
@confirm="onConfirm"
|
||||
/>
|
||||
<view class="u-input__right-icon u-flex">
|
||||
<view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
|
||||
<u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
|
||||
</view>
|
||||
<view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
|
||||
<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
|
||||
</view>
|
||||
<view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
|
||||
'u-input__right-icon--select--reverse': selectOpen
|
||||
}">
|
||||
<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
/**
|
||||
* input 输入框
|
||||
* @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
|
||||
* @tutorial http://uviewui.com/components/input.html
|
||||
* @property {String} type 模式选择,见官网说明
|
||||
* @property {Boolean} clearable 是否显示右侧的清除图标(默认true)
|
||||
* @property {} v-model 用于双向绑定输入框的值
|
||||
* @property {String} input-align 输入框文字的对齐方式(默认left)
|
||||
* @property {String} placeholder placeholder显示值(默认 '请输入内容')
|
||||
* @property {Boolean} disabled 是否禁用输入框(默认false)
|
||||
* @property {String Number} maxlength 输入框的最大可输入长度(默认140)
|
||||
* @property {String Number} selection-start 光标起始位置,自动聚焦时有效,需与selection-end搭配使用(默认-1)
|
||||
* @property {String Number} maxlength 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认-1)
|
||||
* @property {String Number} cursor-spacing 指定光标与键盘的距离,单位px(默认0)
|
||||
* @property {String} placeholderStyle placeholder的样式,字符串形式,如"color: red;"(默认 "color: #c0c4cc;")
|
||||
* @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type为text时生效(默认done)
|
||||
* @property {Object} custom-style 自定义输入框的样式,对象形式
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
|
||||
* @property {Boolean} password-icon type为password时,是否显示右侧的密码查看图标(默认true)
|
||||
* @property {Boolean} border 是否显示边框(默认false)
|
||||
* @property {String} border-color 输入框的边框颜色(默认#dcdfe6)
|
||||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @property {String Number} height 高度,单位rpx(text类型时为70,textarea时为100)
|
||||
* @example <u-input v-model="value" :type="type" :border="border" />
|
||||
*/
|
||||
export default {
|
||||
name: 'u-input',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 输入框的类型,textarea,text,number
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
inputAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请输入内容'
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
placeholderStyle: {
|
||||
type: String,
|
||||
default: 'color: #c0c4cc;'
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 输入框的自定义样式
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 图标的自定义样式
|
||||
prefixIconStyle: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动获得焦点
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 密码类型时,是否显示右侧的密码图标
|
||||
passwordIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// input|textarea是否显示边框
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 输入框的边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#dcdfe6'
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态
|
||||
// open-打开,close-关闭
|
||||
selectOpen: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 高度,单位rpx
|
||||
height: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 是否可清空
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 指定光标与键盘的距离,单位 px
|
||||
cursorSpacing: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
// 光标起始位置,自动聚焦时有效,需与selection-end搭配使用
|
||||
selectionStart: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
// 光标结束位置,自动聚焦时有效,需与selection-start搭配使用
|
||||
selectionEnd: {
|
||||
type: [Number, String],
|
||||
default: -1
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示键盘上方带有”完成“按钮那一栏
|
||||
showConfirmbar:{
|
||||
type:Boolean,
|
||||
default:true
|
||||
},
|
||||
// 左边图标名称
|
||||
prefixIcon:{
|
||||
type:String,
|
||||
default:''
|
||||
},
|
||||
// 左边图标大小
|
||||
prefixIconSize:{
|
||||
type:[String,Number],
|
||||
default:'30rpx'
|
||||
},
|
||||
// 左边图标颜色
|
||||
prefixIconColor:{
|
||||
type:String,
|
||||
default:''
|
||||
},
|
||||
// 边框圆角
|
||||
borderRadius:{
|
||||
type:String,
|
||||
default:'6rpx'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultValue: this.value,
|
||||
inputHeight: 70, // input的高度
|
||||
textareaHeight: 100, // textarea的高度
|
||||
validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
|
||||
focused: false, // 当前是否处于获得焦点的状态
|
||||
showPassword: false, // 是否预览密码
|
||||
lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(nVal, oVal) {
|
||||
this.defaultValue = nVal;
|
||||
// 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
|
||||
if(nVal != oVal && this.type == 'select') this.handleInput({
|
||||
detail: {
|
||||
value: nVal
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength);
|
||||
},
|
||||
getStyle() {
|
||||
let style = {};
|
||||
// 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
|
||||
style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
|
||||
this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
|
||||
style = Object.assign(style, this.customStyle);
|
||||
return style;
|
||||
},
|
||||
//
|
||||
getCursorSpacing() {
|
||||
return Number(this.cursorSpacing);
|
||||
},
|
||||
// 光标起始位置
|
||||
uSelectionStart() {
|
||||
return String(this.selectionStart);
|
||||
},
|
||||
// 光标结束位置
|
||||
uSelectionEnd() {
|
||||
return String(this.selectionEnd);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 监听u-form-item发出的错误事件,将输入框边框变红色
|
||||
this.$on('on-form-item-error', this.onFormItemError);
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 派发 (向上查找) (一个)
|
||||
* @param componentName // 需要找的组件的名称
|
||||
* @param eventName // 事件名称
|
||||
* @param params // 需要传递的参数
|
||||
*/
|
||||
dispatch(componentName, eventName, params) {
|
||||
let parent = this.$parent || this.$root;//$parent 找到最近的父节点 $root 根节点
|
||||
let name = parent.$options.name; // 获取当前组件实例的name
|
||||
// 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点
|
||||
// 循环出当前名称的一样的组件实例
|
||||
while (parent && (!name||name!==componentName)) {
|
||||
parent = parent.$parent;
|
||||
if (parent) {
|
||||
name = parent.$options.name;
|
||||
}
|
||||
}
|
||||
// 有节点表示当前找到了name一样的实例
|
||||
if (parent) {
|
||||
parent.$emit.apply(parent,[eventName].concat(params))
|
||||
}
|
||||
},
|
||||
/**
|
||||
* change 事件
|
||||
* @param event
|
||||
*/
|
||||
handleInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if(this.trim) value = this.$u.trim(value);
|
||||
// vue 原生的方法 return 出去
|
||||
this.$emit('input', value);
|
||||
// 当前model 赋值
|
||||
this.defaultValue = value;
|
||||
// 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
|
||||
// 尚未更新到u-form-item,导致获取的值为空,从而校验混论
|
||||
// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if(this.$u.trim(value) == this.lastValue) return ;
|
||||
this.lastValue = value;
|
||||
// #endif
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-change', value);
|
||||
}, 40)
|
||||
},
|
||||
/**
|
||||
* blur 事件
|
||||
* @param event
|
||||
*/
|
||||
handleBlur(event) {
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100)
|
||||
// vue 原生的方法 return 出去
|
||||
this.$emit('blur', event.detail.value);
|
||||
setTimeout(() => {
|
||||
// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
|
||||
// #ifdef MP-TOUTIAO
|
||||
if(this.$u.trim(value) == this.lastValue) return ;
|
||||
this.lastValue = value;
|
||||
// #endif
|
||||
// 将当前的值发送到 u-form-item 进行校验
|
||||
this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
|
||||
}, 40)
|
||||
},
|
||||
onFormItemError(status) {
|
||||
this.validateState = status;
|
||||
},
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus');
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.$emit('input', '');
|
||||
},
|
||||
inputClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin vue-flex($direction: row) {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: $direction;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-input {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
background-color: #f9fafb;
|
||||
@include vue-flex;
|
||||
&__prefix-icon{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
&__input {
|
||||
//height: $u-form-item-height;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__textarea {
|
||||
width: auto;
|
||||
font-size: 28rpx;
|
||||
color: $u-main-color;
|
||||
padding: 10rpx 0;
|
||||
line-height: normal;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&--border {
|
||||
border-radius: 6rpx;
|
||||
border-radius: 4px;
|
||||
border: 1px solid $u-form-item-border-color;
|
||||
}
|
||||
|
||||
&--error {
|
||||
border-color: $u-type-error!important;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
|
||||
&__item {
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
&--select {
|
||||
transition: transform .4s;
|
||||
|
||||
&--reverse {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
277
components/d-radio/d-radio.vue
Normal file
277
components/d-radio/d-radio.vue
Normal file
@@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<view class="u-radio" :style="[radioStyle]">
|
||||
<view class="u-radio__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
|
||||
<u-icon class="u-radio__icon-wrap__icon" name="checkbox-mark" :size="elIconSize" :color="iconColor" />
|
||||
</view>
|
||||
<view class="u-radio__label" @tap="onClickLabel" :style="{
|
||||
fontSize: $u.addUnit(labelSize)
|
||||
}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* radio 单选框
|
||||
* @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio-group使用
|
||||
* @tutorial https://www.uviewui.com/components/radio.html
|
||||
* @property {String Number} icon-size 图标大小,单位rpx(默认24)
|
||||
* @property {String Number} label-size label字体大小,单位rpx(默认28)
|
||||
* @property {String Number} name radio组件的标示符
|
||||
* @property {String} shape 形状,见上方说明(默认circle)
|
||||
* @property {Boolean} disabled 是否禁用(默认false)
|
||||
* @property {Boolean} label-disabled 点击文本是否可以操作radio(默认true)
|
||||
* @property {String} active-color 选中时的颜色,如设置parent的active-color将失效
|
||||
* @event {Function} change 某个radio状态发生变化时触发(选中状态)
|
||||
* @example <u-radio :label-disabled="false">门掩黄昏,无计留春住</u-radio>
|
||||
*/
|
||||
export default {
|
||||
name: "d-radio",
|
||||
props: {
|
||||
// radio的名称
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 形状,square为方形,circle为原型
|
||||
shape: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁止点击提示语选中复选框
|
||||
labelDisabled: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标的大小,单位rpx
|
||||
iconSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的字体大小,rpx单位
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
|
||||
// 故只能使用如此方法
|
||||
parentData: {
|
||||
iconSize: null,
|
||||
labelDisabled: null,
|
||||
disabled: null,
|
||||
shape: null,
|
||||
activeColor: null,
|
||||
size: null,
|
||||
width: null,
|
||||
height: null,
|
||||
value: null,
|
||||
wrap: null
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.parent = false;
|
||||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
|
||||
this.updateParentData();
|
||||
this.parent.children.push(this);
|
||||
},
|
||||
computed: {
|
||||
// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置
|
||||
elDisabled() {
|
||||
return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData
|
||||
.disabled : false;
|
||||
},
|
||||
// 是否禁用label点击
|
||||
elLabelDisabled() {
|
||||
return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this
|
||||
.parentData.labelDisabled : false;
|
||||
},
|
||||
// 组件尺寸,对应size的值,默认值为34rpx
|
||||
elSize() {
|
||||
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 34);
|
||||
},
|
||||
// 组件的勾选图标的尺寸,默认20
|
||||
elIconSize() {
|
||||
return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 20);
|
||||
},
|
||||
// 组件选中激活时的颜色
|
||||
elActiveColor() {
|
||||
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor :
|
||||
'primary');
|
||||
},
|
||||
// 组件的形状
|
||||
elShape() {
|
||||
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
|
||||
},
|
||||
// 设置radio的状态,要求radio的name等于parent的value时才为选中状态
|
||||
iconStyle() {
|
||||
let style = {};
|
||||
if (this.elActiveColor && this.parentData.value == this.name && !this.elDisabled) {
|
||||
style.borderColor = this.elActiveColor;
|
||||
style.backgroundColor = this.elActiveColor;
|
||||
}
|
||||
style.width = this.$u.addUnit(this.elSize);
|
||||
style.height = this.$u.addUnit(this.elSize);
|
||||
return style;
|
||||
},
|
||||
iconColor() {
|
||||
return this.name == this.parentData.value ? '#ffffff' : 'transparent';
|
||||
},
|
||||
iconClass() {
|
||||
let classes = [];
|
||||
classes.push('u-radio__icon-wrap--' + this.elShape);
|
||||
if (this.name == this.parentData.value) classes.push('u-radio__icon-wrap--checked');
|
||||
if (this.elDisabled) classes.push('u-radio__icon-wrap--disabled');
|
||||
if (this.name == this.parentData.value && this.elDisabled) classes.push(
|
||||
'u-radio__icon-wrap--disabled--checked');
|
||||
// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
|
||||
return classes.join(' ');
|
||||
},
|
||||
radioStyle() {
|
||||
let style = {};
|
||||
if (this.parentData.width) {
|
||||
style.width = this.$u.addUnit(this.parentData.width);
|
||||
// #ifdef MP
|
||||
// 各家小程序因为它们特殊的编译结构,使用float布局
|
||||
style.float = 'left';
|
||||
// #endif
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局
|
||||
style.flex = `0 0 ${this.$u.addUnit(this.parentData.width)}`;
|
||||
// #endif
|
||||
}
|
||||
if (this.parentData.wrap) {
|
||||
style.width = '100%';
|
||||
// #ifndef MP
|
||||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行
|
||||
style.flex = '0 0 100%';
|
||||
// #endif
|
||||
}
|
||||
return style;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateParentData() {
|
||||
this.getParentData('u-radio-group');
|
||||
},
|
||||
onClickLabel() {
|
||||
if (!this.elLabelDisabled && !this.elDisabled) {
|
||||
this.setRadioCheckedStatus();
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
if (!this.elDisabled) {
|
||||
this.setRadioCheckedStatus();
|
||||
}
|
||||
},
|
||||
emitEvent() {
|
||||
// u-radio的name不等于父组件的v-model的值时(意味着未选中),才发出事件,避免多次点击触发事件
|
||||
if (this.parentData.value != this.name) this.$emit('change', this.name);
|
||||
},
|
||||
// 改变组件选中状态
|
||||
// 这里的改变的依据是,更改本组件的parentData.value值为本组件的name值,同时通过父组件遍历所有u-radio实例
|
||||
// 将本组件外的其他u-radio的parentData.value都设置为空(由computed计算后,都被取消选中状态),因而只剩下一个为选中状态
|
||||
setRadioCheckedStatus() {
|
||||
this.emitEvent();
|
||||
if (this.parent) {
|
||||
this.parent.setValue(this.name);
|
||||
this.parentData.value = this.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// 定义混入指令,用于在非nvue环境下的flex定义,因为nvue没有display属性,会报错
|
||||
@mixin vue-flex($direction: row) {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: $direction;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.u-radio {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
line-height: 1.8;
|
||||
|
||||
&__icon-wrap {
|
||||
color: $u-content-color;
|
||||
@include vue-flex;
|
||||
flex: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
width: 42rpx;
|
||||
height: 42rpx;
|
||||
color: transparent;
|
||||
text-align: center;
|
||||
transition-property: color, border-color, background-color;
|
||||
font-size: 20px;
|
||||
border: 1px solid #c8c9cc;
|
||||
transition-duration: 0.2s;
|
||||
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
|
||||
&__icon {
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
&--circle {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&--checked {
|
||||
color: #fff;
|
||||
background-color: #2979ff;
|
||||
border-color: #2979ff;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
background-color: #ebedf0;
|
||||
border-color: #c8c9cc;
|
||||
}
|
||||
|
||||
&--disabled--checked {
|
||||
color: #c8c9cc !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
word-wrap: break-word;
|
||||
margin-left: 10rpx;
|
||||
margin-right: 24rpx;
|
||||
color: $u-content-color;
|
||||
font-size: 30rpx;
|
||||
|
||||
&--disabled {
|
||||
color: #c8c9cc;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user