@@ -0,0 +1,21 @@ | |||
/dist | |||
/node_modules | |||
/unpackage | |||
# MAC隐藏文件 | |||
.DS_Store | |||
# 包管理日志 | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
pnpm-debug.log* | |||
# 编辑器配置 | |||
.idea | |||
.vscode | |||
*.suo | |||
*.ntvs* | |||
*.njsproj | |||
*.sln | |||
*.sw? |
@@ -0,0 +1,11 @@ | |||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/ | |||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数 | |||
"version": "0.0", | |||
"configurations": [{ | |||
"type": "uniCloud", | |||
"default": { | |||
"launchtype": "remote" | |||
} | |||
} | |||
] | |||
} |
@@ -0,0 +1,27 @@ | |||
<template> | |||
<view> | |||
</view> | |||
</template> | |||
<script> | |||
//app.js | |||
var config = require("./config"); | |||
export default { | |||
onLaunch: function(options) {}, | |||
onShow(options) { | |||
}, | |||
onHide() {}, | |||
methods: { | |||
} | |||
}; | |||
</script> | |||
<style> | |||
@import "./app.css"; | |||
</style> | |||
<style lang="scss"> | |||
@import "uview-ui/index.scss"; | |||
/*每个页面公共css */ | |||
</style> |
@@ -0,0 +1,155 @@ | |||
/**app.wxss**/ | |||
.container { | |||
} | |||
/*小程序*/ | |||
page { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: flex-start; | |||
font-family: PingFangSC-Regular; | |||
} | |||
.placeholder{ | |||
color:#B2B2B2; | |||
} | |||
.navigator-hover { | |||
background-color:transparent; | |||
opacity:1; | |||
} | |||
.item-flex{ | |||
display: flex; | |||
justify-content: space-between; | |||
} | |||
.flex{ | |||
display: flex; | |||
align-items: center; | |||
border-bottom: 1rpx solid #CAD8E9; | |||
} | |||
.no-data{ | |||
display:flex; | |||
flex-direction:column; | |||
justify-content: center; | |||
align-items: center; | |||
font-size: 30rpx; | |||
color: #9CA3AF; | |||
margin-top:150rpx; | |||
} | |||
.no-data image{ | |||
width:100rpx; | |||
height:100rpx; | |||
display:block; | |||
} | |||
.no-data text{ | |||
line-height:42rpx; | |||
margin-top:30rpx; | |||
} | |||
.flxed-btn{ | |||
position: fixed; | |||
bottom: 0; | |||
font-size: 30rpx; | |||
color: #386DB6; | |||
text-align: center; | |||
width:100%; | |||
height:80rpx; | |||
background: #FFFFFF; | |||
align-items:center; | |||
justify-content: center; | |||
box-shadow: 0 -2px 10px 0 rgba(205,205,212,0.50); | |||
} | |||
/*搜索框*/ | |||
.fixed-con{ | |||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
} | |||
.search{ | |||
align-items: center; | |||
background-color:#fff; | |||
} | |||
.search input,.search .areaInput{ | |||
font-size:28rpx; | |||
color:#999999; | |||
display:flex; | |||
background:#EFEFF4; | |||
border-radius:30px; | |||
width:100%; | |||
height:60rpx; | |||
padding:0 60rpx 0 68rpx; | |||
background-image:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAQAAAACj/OVAAAD/ElEQVR42r2YWWwURxCG1+IyNpe5DZY5hAMIR/AAgpdIiCPAC4d5QDyECCGhCAl4AARywAGDBYRTIgpCQkIOiAeuh/prxru2pUWRATsyIA4DIkHEQQaSEA4vJhyGQQZPzwzbM9u726bnbbq6v6rq6urqDoUSNuRjFcpRR00Uo1Zqpia6QEdoBYaGdDeMwUZcJEv+4R1qeX14lCZYxXA6jnd+MNf3lo6kbWu0D37ESwWYbWsLtkZ7pIwzp9BDdZj4/jImpIQzFuP/uLWq42IsMCabeee7I9+cwkVUQpfi7HxuzEsSZmXwlk8maeSVZp6PasN4De571xPrkovKUg/sGTZEM4NHRLKxGc89o9SRXOSOSlSZA9TGVQ6hGheylWep4Qop5sIdjHZOIsy64RfX2McKezOSjTspuUW+HNcbuiYS3+jC/ZxiXjrmmmNVoGh1PzwTor/Wd0kNeL476sUs/wQmAuwWgk+rB6WeoYxh9EJYWeIrFs51tjoXp5cUaYcANkf7+AmtEPY9QFZ6QM6hJwL5jZ9Dw4pLrRY6IvxwQipQ05Ne2TnTGJw+MDzKcap0c9BCoVGtpmP7hpjxa1n3AaHR95qAO8WMZbLuiNBnqh4gzxUznpIBRUozvtADNCYK4GUZ8D+7u6anJpcOFS69K+t+3d4Z01XrRTvT2/Y5H8mi1N4ULfqA9rmKBzLgI9sBZi89QDNPrOE1GfB3u7titB4gTRLAaGBiM6dpCpr5gckN+0X3D5qAewLPHmOeAF7U5FKxSPyVTJ8s5zTUcR/CGDHbS7ObXITFRl2rAVjqFJp+uW+5EPk33WyD/tQsZlvkt28GoEV4fUua67dPeOthQDFGZUIs5neTULKvwLnk8bYAwareTgrHb4nuE74prQdddxaHc4J1W+cqYstTwVkZOOPMwcsSaZfpFAZkoTR5HO91ja+1MhT87yrwLDrqs4fkY7Pc1rWdEkp5meaIc6xt0DnV8OGRkpeOJhSoaLrBM+gFlSXal5yD3faJ+snd+R6PVLFyrdtKsuhvLvZzDxdisxPdHyCvPT5qrBiuVnPF4rS9ge1Yguk8NjIwPM6Yyd/yLtyOs+qKMQHfef7cRb6KY8fjz6QfTVp528c6m4s9qv6hFAnIopJ4OwOewE5yoctHuzy9t8O5atXlYDpErYlhdNr4Mi4SDntkbirfOZGP1Tjrh0U9r5dHotUJpzyyDaovIu1HDpbiJ1TTLdynJ2hEPcqxOjIiaExDV6r0KHcV/UMd2yLZdMGDvBzu28FIzsE17xL4XsJ1tXCu+/WHLNTpKraD8qz3+a8y1NGNC12H+xtjdqjjmzHZfm/E0tDnaTyjrdbhTaHP17jIfsF7Dw8miq+sdgNuAAAAAElFTkSuQmCC'); | |||
background-size:28rpx; | |||
background-position:28rpx center; | |||
background-repeat:no-repeat; | |||
} | |||
.same-tips{ | |||
position:fixed; | |||
z-index:900; | |||
top:20rpx; | |||
left:50%; | |||
opacity:1; | |||
display:block; | |||
margin-left:-355rpx; | |||
width:710rpx; | |||
height:100rpx; | |||
background:rgba(0,0,0,0.7); | |||
box-shadow:0rpx 3rpx 11rpx 0rpx rgba(0,0,0,0.28); | |||
border-radius:10rpx; | |||
font-size:26rpx; | |||
font-weight:400; | |||
color:#FFFFFF; | |||
line-height:37rpx; | |||
} | |||
.same-tips-con{ | |||
height:100%; | |||
padding:0 46rpx 0 20rpx; | |||
display:flex; | |||
align-items: center; | |||
} | |||
.same-tips-img{ | |||
position:absolute; | |||
right:20rpx; | |||
top:10rpx; | |||
width:30rpx; | |||
height:30rpx; | |||
} | |||
.same-tips-img image{ | |||
width:30rpx; | |||
height:30rpx; | |||
} | |||
.same-tips-show{ | |||
top:-120rpx; | |||
animation: same-tips-show 0.5s linear; | |||
} | |||
.same-tips-show-none{ | |||
top:-120rpx; | |||
} | |||
@keyframes same-tips-show | |||
{ | |||
from { | |||
transform: translateY(100%); | |||
} | |||
to { | |||
transform: translateY(0); | |||
} | |||
} | |||
/*权限 begin*/ | |||
.mod-tip{ | |||
margin-top:313rpx; | |||
} | |||
.mod-tip .mod-tip-images{ | |||
display:block; | |||
width:180rpx; | |||
height:180rpx; | |||
margin:0 auto; | |||
} | |||
.mod-tip .mod-tip-text{ | |||
text-align:center; | |||
font-size:30rpx; | |||
color:#858B9B; | |||
line-height:42rpx; | |||
margin-top:40rpx; | |||
} | |||
/*权限 end*/ | |||
@@ -0,0 +1,57 @@ | |||
.zan-loadmore{ | |||
position:fixed; | |||
width:65%; | |||
margin:0 auto; | |||
line-height:20px; | |||
font-size:14px; | |||
text-align:center; | |||
display: block; | |||
vertical-align: middle | |||
} | |||
.zan-loading{ | |||
width:20px; | |||
height:20px; | |||
display:inline-block; | |||
vertical-align:middle; | |||
animation:weuiLoading 1s steps(12,end) infinite; | |||
} | |||
.zan-loadmore .zan-loading{ | |||
margin-right:4px | |||
} | |||
.zan-loadmore__tips{ | |||
display:inline-block; | |||
vertical-align:middle; | |||
height:20px; | |||
line-height:20px; | |||
color:#999 | |||
} | |||
.zan-loadmore--nodata,.zan-loadmore--nomore{ | |||
border-top:1rpx solid #e5e5e5; | |||
} | |||
.zan-loadmore--nodata{ | |||
/* margin-top:40rpx */ | |||
} | |||
.zan-loadmore--nodata .zan-loadmore__tips{ | |||
position:relative; | |||
top:-11px; | |||
padding:0 6px | |||
} | |||
.zan-loadmore--nomore .zan-loadmore__tips{ | |||
position:relative; | |||
top:-11px; | |||
padding:0 6px | |||
} | |||
.zan-loadmore__dot{ | |||
position:absolute; | |||
left:50%; | |||
top:10px; | |||
margin-left:-2px; | |||
margin-top:-2px; | |||
content:" "; | |||
width:4px; | |||
height:4px; | |||
border-radius:50%; | |||
background-color:#e5e5e5; | |||
display:inline-block; | |||
vertical-align:middle; | |||
} |
@@ -0,0 +1,72 @@ | |||
<template> | |||
<view> | |||
<block v-if="mtype==2"> | |||
<view class="zan-loadmore zan-loadmore--nomore" :hidden="hidden"> | |||
<view class="zan-loadmore__tips" :style="'background-color:' + tipcolor"> | |||
<view class="zan-loadmore__dot"></view> | |||
</view> | |||
</view> | |||
</block> | |||
<block v-else-if="mtype==3"> | |||
<view class="zan-loadmore zan-loadmore--nodata" :hidden="hidden"> | |||
<view class="zan-loadmore__tips" :style="'background-color:' + tipcolor">{{ nodata_str}}</view> | |||
</view> | |||
</block> | |||
<block v-else-if="mtype==1"> | |||
<view class="zan-loadmore" :hidden="hidden"> | |||
<image class="zan-loading" src="https://qufang.oss-cn-beijing.aliyuncs.com/upload/icon/xcx/jjycrm/loadmore.png"></image> | |||
<view class="zan-loadmore__tips" :style="'background-color:' + tipcolor">加载中...</view> | |||
</view> | |||
</block> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
props: { | |||
mtype: { | |||
type: Number, | |||
default: 1 | |||
}, | |||
nodata_str: { | |||
type: String, | |||
default: '暂无数据' | |||
}, | |||
hidden: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
tipcolor: { | |||
type: String, | |||
default: "#F5F5F5" | |||
} | |||
}, | |||
watch: { | |||
mtype: function (newVal, oldVal) { | |||
this.setData({ | |||
mtype: newVal | |||
}); | |||
}, | |||
nodata_str: function (newVal, oldVal) { | |||
this.setData({ | |||
nodata_str: newVal | |||
}); | |||
}, | |||
hidden: function (newVal, oldVal) { | |||
this.setData({ | |||
hidden: newVal | |||
}); | |||
} | |||
}, | |||
methods: {} | |||
}; | |||
</script> | |||
<style> | |||
@import "./index.css"; | |||
</style> |
@@ -0,0 +1,144 @@ | |||
/* components/pickerMultiSelect.wxss */ | |||
page { | |||
height: 100%; | |||
width: 100%; | |||
} | |||
.showPicker { | |||
width: 100%; | |||
height: 80rpx; | |||
line-height: 80rpx; | |||
font-size: 30rpx; | |||
background-color: paleturquoise; | |||
text-align: center; | |||
} | |||
.shade-container { | |||
position: fixed; | |||
height: 100%; | |||
width: 100%; | |||
top: 0; | |||
right: 0; | |||
display: flex; | |||
justify-content: space-around; | |||
background-color: rgba(0, 0, 0, 0.5); | |||
/* transform: translateX(-200%); | |||
transition: all 0.5s ease; */ | |||
/* display: block; */ | |||
z-index: 9999; | |||
} | |||
.hide-container { | |||
/* position: fixed; | |||
height: 100%; | |||
width: 100%; | |||
top: 0; | |||
right: -200%; | |||
z-index: 9999; | |||
display: flex; | |||
justify-content: space-between; | |||
transform: translateX(100%); | |||
transition: all 0.5s ease-in; */ | |||
display: none | |||
} | |||
.left-shade { | |||
width: 30vw; | |||
height: 100%; | |||
} | |||
.right-choose { | |||
width: 70vw; | |||
height: 100%; | |||
background-color: #fff; | |||
padding: 40rpx; | |||
z-index: 12313; | |||
} | |||
.picker-container { | |||
height: calc(100% - 200rpx); | |||
overflow-x: hidden; | |||
overflow-y: scroll; | |||
margin-top: 40rpx; | |||
} | |||
.picker-container::-webkit-scrollbar { | |||
display: none; | |||
} | |||
.picker-item { | |||
width: calc(100% - 8rpx); | |||
height: 50rpx; | |||
line-height: 50rpx; | |||
font-size: 24rpx; | |||
text-align: center; | |||
margin-top: 20rpx; | |||
border: 2rpx solid #eaeaea; | |||
border-radius: 8rpx; | |||
} | |||
.picker-item-choose { | |||
border: 2rpx solid rgb(110, 216, 84); | |||
} | |||
.picker-item:nth-of-type(1) { | |||
margin: 0; | |||
} | |||
.button-container { | |||
width: 100%; | |||
height: 80rpx; | |||
display: flex; | |||
justify-content: space-between; | |||
font-size: 24rpx; | |||
text-align: center; | |||
border-bottom: 2rpx solid #eaeaea; | |||
} | |||
.cancal { | |||
width: 100rpx; | |||
height: 40rpx; | |||
line-height: 40rpx; | |||
border: 2rpx solid #ddd; | |||
border-radius: 8rpx; | |||
} | |||
.sure { | |||
width: 100rpx; | |||
height: 40rpx; | |||
line-height: 40rpx; | |||
border: 2rpx solid rgb(132, 235, 132); | |||
border-radius: 8rpx; | |||
} | |||
.list{ | |||
width: 100%; | |||
padding: 25rpx 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
border-bottom: 1px solid #EAF1FF; | |||
box-sizing: border-box | |||
} | |||
.listName{ | |||
font-size:28rpx; | |||
color:rgba(87,99,117,1); | |||
max-width: 500rpx; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
} | |||
.listName2{ | |||
font-size:28rpx; | |||
color:#BCC0C8; | |||
max-width: 500rpx; | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
white-space: nowrap; | |||
margin-right: -360rpx; | |||
} | |||
.listImg{ | |||
width: 14rpx; | |||
height: 26rpx | |||
} |
@@ -0,0 +1,184 @@ | |||
<template> | |||
<view> | |||
<!-- components/pickerMultiSelect.wxml --> | |||
<!-- <view class='showPicker' bindtap='showPicker'>MyPicker</view> --> | |||
<view class="list" @tap="showPickerFun"> | |||
<view class="listName">{{title}}</view> | |||
<view class="listName2">{{value}}</view> | |||
<image src="https://qufang.oss-cn-beijing.aliyuncs.com/upload/icon/xcx/jjycrm/qf/more.png" class="listImg"></image> | |||
</view> | |||
<view :class="showPicker ? 'shade-container' : 'hide-container'" v-if="firstShow"> | |||
<view class="left-shade" @tap="hidePicker"></view> | |||
<view class="right-choose"> | |||
<view class="button-container"> | |||
<view class="cancal" @tap="cancal">取消</view> | |||
<view class="sure" @tap="sure">确定</view> | |||
</view> | |||
<view class="picker-container"> | |||
<view v-for="(item, index) in myChooseList" :key="index" :class="'picker-item ' + ( item.flag ? 'picker-item-choose' : '' )" @tap="chooseItem" :data-value="item.value"> | |||
{{ item.label }} | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
// components/pickerMultiSelect.js | |||
export default { | |||
data() { | |||
return { | |||
showPicker: false, | |||
firstShow: false, | |||
myList: this.list, | |||
myChooseList:[], | |||
flag: false | |||
}; | |||
}, | |||
components: {}, | |||
mounted(){ | |||
this.myChooseList=this.chooseList; | |||
}, | |||
props: { | |||
chooseList: { | |||
type: Array | |||
}, | |||
multiple: { | |||
type: Boolean | |||
}, | |||
title: { | |||
type: String | |||
}, | |||
value: { | |||
type: String | |||
}, | |||
list: { | |||
type: Array||String | |||
} | |||
}, | |||
methods: { | |||
// 点击picker元素事件 | |||
chooseItem(e) { | |||
if (this.multiple) { | |||
// 多选事件 | |||
let val = e.target.dataset.value; | |||
let arr = this.myChooseList; | |||
let flag = ''; | |||
let index = null; | |||
for (let i = 0, len = arr.length; i < len; i++) { | |||
if (arr[i].value == val) { | |||
index = i; | |||
flag = `chooseList[${i}].flag`; | |||
} | |||
} | |||
if (!this.myChooseList[index].flag) { | |||
this.setData({ | |||
[flag]: true | |||
}); | |||
} else { | |||
this.setData({ | |||
[flag]: false | |||
}); | |||
} | |||
} else { | |||
// 单选事件 | |||
let val = e.target.dataset.value; | |||
let arr = this.myChooseList; | |||
let flag = ''; | |||
let index = null; | |||
for (let i = 0, len = arr.length; i < len; i++) { | |||
index = i; | |||
flag = `chooseList[${i}].flag`; | |||
if (arr[i].value == val) { | |||
this.setData({ | |||
[flag]: true | |||
}); | |||
} else { | |||
this.setData({ | |||
[flag]: false | |||
}); | |||
} | |||
} | |||
} | |||
}, | |||
// 展示picker | |||
showPickerFun() { | |||
this.myChooseList=this.chooseList | |||
if (!this.firstShow) { | |||
this.setData({ | |||
firstShow: true | |||
}); | |||
} | |||
this.setData({ | |||
showPicker: true | |||
}); // 加载时重新渲染已选择元素 | |||
let arr = this.myChooseList; | |||
let array = this.myList; | |||
let flag = ''; | |||
let index = null; | |||
for (let i = 0, len = arr.length; i < len; i++) { | |||
index = i; | |||
flag = `chooseList[${i}].flag`; | |||
if (!array.includes(arr[i].value)) { | |||
this.setData({ | |||
[flag]: false | |||
}); | |||
} else { | |||
this.setData({ | |||
[flag]: true | |||
}); | |||
} | |||
} | |||
}, | |||
// 隐藏picker | |||
hidePicker() { | |||
this.setData({ | |||
showPicker: false | |||
}); | |||
}, | |||
// 取消按钮事件 | |||
cancal() { | |||
this.hidePicker(); | |||
}, | |||
// 确定按钮事件 | |||
sure() { | |||
var list = []; | |||
for (let item of this.myChooseList) { | |||
if (item.flag) { | |||
list.push(item.value); | |||
} | |||
} | |||
this.setData({ | |||
myList:list | |||
}); | |||
this.hidePicker(); | |||
this.$emit('chooseEvent', { | |||
detail: { | |||
chooseArray: this.myList | |||
} | |||
}); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style> | |||
@import "./pickerMultiSelect.css"; | |||
</style> |
@@ -0,0 +1,178 @@ | |||
<template> | |||
<view class="verifical-input"> | |||
<input | |||
class="verifical-input-hidden" | |||
type="number" | |||
:maxlength="blockNum" | |||
:focus="isFucus" | |||
hold-keyboard="true" | |||
@input = "changeCodeInput" | |||
v-model="inputCode" | |||
value="" /> | |||
<view class="verifical-input-real"> | |||
<block v-for="(item,index) in blockNum" :key="index"> | |||
<view :class="['real-block',{'block-active':index === activeIndex && codeType == 'block','line-active':index === activeIndex && codeType == 'line','block-arror':errorType},codeType == 'block'?'block-content':'line-content']"> | |||
<text class="real-block-line" v-if="index === activeIndex || (errorType && index === 0)"></text> | |||
<text class="real-block-number">{{inputText[index]}}</text> | |||
</view> | |||
</block> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props:{ | |||
//验证码个数 | |||
blockNum:{ | |||
type:Number, | |||
default:4 | |||
}, | |||
//验证码类型 | |||
codeType:{ | |||
type:String, | |||
default:'block' | |||
}, | |||
/* isFocus:{ | |||
type:Number, | |||
default:4 | |||
} */ | |||
}, | |||
data() { | |||
return { | |||
activeIndex:0, //激活的方块 | |||
inputText:'', //输入的验证码 | |||
isFucus:true, //是否自动聚焦 | |||
inputCode:'', //输入的值 | |||
errorType:false //错误提示 | |||
} | |||
}, | |||
watch: { | |||
errorType: { | |||
immediate: true, | |||
handler: function(newValue) { | |||
if (newValue === true) { | |||
this.inputText.length = 0; | |||
this.inputCode = ''; | |||
this.isFucus = true; | |||
} | |||
} | |||
} | |||
}, | |||
methods: { | |||
changeCodeInput(event){ | |||
this.errorType = false; | |||
this.inputText = (event.target.value).split(''); | |||
this.activeIndex = this.inputText.length; | |||
if(this.activeIndex == this.blockNum){ | |||
this.isFucus = false; | |||
this.$emit('verificationCode',event.target.value) | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
$main-color:blue;/* 主题色 */ | |||
$error-color:red;/* 错误颜色 */ | |||
.flex-row{ | |||
display: flex; | |||
flex-direction: row; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
.flex-column{ | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
.verifical-input{ | |||
position: relative; | |||
overflow: hidden; | |||
padding: 0 5px; | |||
.verifical-input-hidden{ | |||
position: absolute; | |||
top:0; | |||
left:-200%; | |||
width: 300%; | |||
height: 100%; | |||
background: none; | |||
color: #FFFFFF; | |||
} | |||
.verifical-input-real{ | |||
width: 100%; | |||
@extend .flex-row; | |||
.real-block{ | |||
width: 100rpx; | |||
height: 100rpx; | |||
margin-right: 20rpx; | |||
&:last-child{ | |||
margin-right: 0; | |||
} | |||
@extend .flex-row; | |||
.real-block-line{ | |||
display: inline-block; | |||
width: 6rpx; | |||
height: 46rpx; | |||
background: #333333; | |||
animation: line 1s infinite ease; | |||
} | |||
.real-block-number{ | |||
font-size: 48rpx; | |||
font-weight: 600; | |||
} | |||
} | |||
.line-content{ | |||
border-bottom: 2rpx solid rgba(187, 187, 187, 100); | |||
} | |||
.block-content{ | |||
border-radius: 12rpx; | |||
border: 2rpx solid rgba(187, 187, 187, 100); | |||
} | |||
.block-active{ | |||
border: 4rpx solid $main-color!important; | |||
} | |||
.line-active{ | |||
border-bottom: 2rpx solid $main-color; | |||
} | |||
} | |||
} | |||
/* 错误弹框 */ | |||
.block-arror{ | |||
border-color: $error-color!important; | |||
animation: error .5s ease; | |||
} | |||
@keyframes line { | |||
0% { | |||
opacity: .9; | |||
}, | |||
50% { | |||
opacity: 0; | |||
}, | |||
100% { | |||
opacity: .9; | |||
} | |||
} | |||
@keyframes error { | |||
0% { | |||
transform: translateX(-5px); | |||
}, | |||
20% { | |||
transform: translateX(5px); | |||
}, | |||
40% { | |||
transform: translateX(-5px); | |||
}, | |||
60% { | |||
transform: translateX(5px); | |||
}, | |||
80% { | |||
transform: translateX(-5px); | |||
}, | |||
100% { | |||
transform: translateX(0); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,23 @@ | |||
/** | |||
* 小程序配置文件 | |||
*/ | |||
// 此处主机域名修改成腾讯云解决方案分配的域名 | |||
// var host = 'http://121.42.63.138:9091/autoSR/api'; // 测试站 | |||
// var host = 'http://192.168.31.163:8080/autoSR/api'; // 长龙 | |||
// var host = 'http://192.168.31.130:8080/autoSR/api'; // 佳豪 | |||
// var host = 'http://10.2.1.104:8081/autoSR/api'; // 刘敏 | |||
var host = 'https://zkgj.quhouse.com/api'; // 质控正式 | |||
// var host = 'https://hfju.com/api'; // 数智正式 | |||
var iMServiceHost = 'https://im.quhouse.com/'; //IM的后端地址正式 | |||
var config = { | |||
service: { | |||
host, | |||
iMServiceHost, | |||
//登录 | |||
login: `${host}/user/login`, | |||
} | |||
}; | |||
module.exports = config; |
@@ -0,0 +1,24 @@ | |||
import Vue from 'vue'; | |||
import App from './App'; | |||
import dayjs from './utils/dayjs.min.js' | |||
Vue.config.productionTip = false; | |||
Vue.prototype.$dayjs = dayjs; | |||
// 引入全局uView | |||
import uView from 'uview-ui' | |||
Vue.use(uView); | |||
import http from '@/utils/http.js' | |||
Vue.use(http, app) | |||
App.mpType = 'app'; | |||
const app = new Vue({ | |||
...App | |||
}); | |||
app.$mount(); |
@@ -0,0 +1,119 @@ | |||
{ | |||
"name" : "智控管家", | |||
"appid" : "__UNI__6CC21FF", | |||
"description" : "智控管家", | |||
"versionName" : "1.1.0", | |||
"versionCode" : "100", | |||
"transformPx" : false, | |||
"app-plus" : { | |||
"usingComponents" : true, | |||
"nvueCompiler" : "uni-app", | |||
"compilerVersion" : 3, | |||
"splashscreen" : { | |||
"alwaysShowBeforeRender" : true, | |||
"waiting" : true, | |||
"autoclose" : true, | |||
"delay" : 0 | |||
}, | |||
"modules" : { | |||
"Push" : {}, | |||
"UIWebview" : {}, | |||
"Webview-x5" : {} | |||
}, | |||
"distribute" : { | |||
"android" : { | |||
"permissions" : [ | |||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", | |||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", | |||
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>", | |||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", | |||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>", | |||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>", | |||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>", | |||
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>", | |||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>", | |||
"<uses-permission android:name=\"android.permission.CAMERA\"/>", | |||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>", | |||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>", | |||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>", | |||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>", | |||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>", | |||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>", | |||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>", | |||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>", | |||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>", | |||
"<uses-feature android:name=\"android.hardware.camera\"/>", | |||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>", | |||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>" | |||
], | |||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ] | |||
}, | |||
"ios" : { | |||
"idfa" : false | |||
}, | |||
"sdkConfigs" : { | |||
"ad" : {}, | |||
"oauth" : {} | |||
}, | |||
"icons" : { | |||
"android" : { | |||
"hdpi" : "unpackage/res/icons/72x72.png", | |||
"xhdpi" : "unpackage/res/icons/96x96.png", | |||
"xxhdpi" : "unpackage/res/icons/144x144.png", | |||
"xxxhdpi" : "unpackage/res/icons/192x192.png" | |||
}, | |||
"ios" : { | |||
"appstore" : "unpackage/res/icons/1024x1024.png", | |||
"ipad" : { | |||
"app" : "unpackage/res/icons/76x76.png", | |||
"app@2x" : "unpackage/res/icons/152x152.png", | |||
"notification" : "unpackage/res/icons/20x20.png", | |||
"notification@2x" : "unpackage/res/icons/40x40.png", | |||
"proapp@2x" : "unpackage/res/icons/167x167.png", | |||
"settings" : "unpackage/res/icons/29x29.png", | |||
"settings@2x" : "unpackage/res/icons/58x58.png", | |||
"spotlight" : "unpackage/res/icons/40x40.png", | |||
"spotlight@2x" : "unpackage/res/icons/80x80.png" | |||
}, | |||
"iphone" : { | |||
"app@2x" : "unpackage/res/icons/120x120.png", | |||
"app@3x" : "unpackage/res/icons/180x180.png", | |||
"notification@2x" : "unpackage/res/icons/40x40.png", | |||
"notification@3x" : "unpackage/res/icons/60x60.png", | |||
"settings@2x" : "unpackage/res/icons/58x58.png", | |||
"settings@3x" : "unpackage/res/icons/87x87.png", | |||
"spotlight@2x" : "unpackage/res/icons/80x80.png", | |||
"spotlight@3x" : "unpackage/res/icons/120x120.png" | |||
} | |||
} | |||
} | |||
} | |||
}, | |||
"quickapp" : {}, | |||
"mp-weixin" : { | |||
"appid" : "wx8f883dca5ecc5510", | |||
"setting" : { | |||
"urlCheck" : false, | |||
"es6" : true, | |||
"postcss" : true, | |||
"minified" : true | |||
}, | |||
"usingComponents" : true, | |||
"permission" : {}, | |||
"plugins" : { | |||
"WechatSI" : { | |||
"version" : "0.3.4", | |||
"provider" : "wx069ba97219f66d99" | |||
} | |||
} | |||
}, | |||
"mp-alipay" : { | |||
"usingComponents" : true | |||
}, | |||
"mp-baidu" : { | |||
"usingComponents" : true | |||
}, | |||
"mp-toutiao" : { | |||
"usingComponents" : true | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
{ | |||
"lockfileVersion": 1 | |||
} |
@@ -0,0 +1,131 @@ | |||
{ | |||
"easycom": { | |||
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue" | |||
}, | |||
//这个pages里只放这五个页面,新增页面时请对应的放到下面的分包里 | |||
"pages": [{ | |||
"path": "pages/index/guide", | |||
"style": { | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white", | |||
"navigationStyle": "custom" | |||
} | |||
}, | |||
{ | |||
"path": "pages/index/index", | |||
"style": { | |||
"navigationBarTitleText": "首页", | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white" | |||
} | |||
}, | |||
{ | |||
"path": "pages/index/customer", | |||
"style": { | |||
"navigationBarTitleText": "接待", | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white" | |||
} | |||
},{ | |||
"path": "pages/index/learning", | |||
"style": { | |||
"navigationBarTitleText": "学习", | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white" | |||
} | |||
},{ | |||
"path": "pages/index/personal", | |||
"style": { | |||
"navigationBarTitleText": "个人", | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white" | |||
} | |||
} | |||
], | |||
//这下面是分包 | |||
"subPackages": [ | |||
{ | |||
"root": "pages/login",//登录相关 | |||
"name": "login", | |||
"pages": [ | |||
{ | |||
"path": "index", | |||
"style": { | |||
"navigationBarBackgroundColor": "#008EF2", | |||
"navigationBarTextStyle": "white" | |||
} | |||
} | |||
] | |||
}, | |||
{ | |||
"root": "pages/mine",//个人中心 | |||
"name": "mine", | |||
"pages": [ | |||
] | |||
}, | |||
{ | |||
"root": "pages/learning",//学习 | |||
"name": "learning", | |||
"pages": [ | |||
] | |||
}, | |||
{ | |||
"root": "pages/center",//中心逻辑的模块都放到这里 | |||
"name": "center", | |||
"pages": [ | |||
] | |||
} | |||
], | |||
"permission": { | |||
"scope.userLocation": { | |||
"desc": "你的位置信息将用于小程序位置接口的效果展示" | |||
} | |||
}, | |||
"networkTimeout": { | |||
"request": 10000, | |||
"downloadFile": 10000 | |||
}, | |||
"debug": false, | |||
"tabBar": { | |||
"color": "#8E8E8E", | |||
"selectedColor": "#1296db", | |||
"borderStyle": "white", | |||
"list": [{ | |||
"pagePath": "pages/index/index", | |||
"iconPath": "/static/images/tabBar/home.png", | |||
"selectedIconPath": "/static/images/tabBar/homeActive.png", | |||
"text": "楼盘" | |||
}, | |||
{ | |||
"pagePath": "pages/index/customer", | |||
"iconPath": "/static/images/tabBar/customer.png", | |||
"selectedIconPath": "/static/images/tabBar/customerActive.png", | |||
"text": "接待" | |||
}, | |||
{ | |||
"pagePath": "pages/index/learning", | |||
"iconPath": "/static/images/tabBar/voice.png", | |||
"selectedIconPath": "/static/images/tabBar/voiceActive.png", | |||
"text": "学习" | |||
}, | |||
{ | |||
"pagePath": "pages/index/personal", | |||
"iconPath": "/static/images/tabBar/user.png", | |||
"selectedIconPath": "/static/images/tabBar/userActive.png", | |||
"text": "个人" | |||
} | |||
] | |||
}, | |||
"sitemapLocation": "sitemap.json", | |||
"globalStyle": { | |||
"backgroundColor": "#F6F6F6", | |||
"backgroundTextStyle": "dark", | |||
"navigationBarBackgroundColor": "#fff", | |||
"navigationBarTitleText": "数智工牌", | |||
"navigationBarTextStyle": "black" | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
<template> | |||
<view> | |||
接待 | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,29 @@ | |||
<template> | |||
<view> | |||
引导页 | |||
<view @click="tologin()" style="width: 200rpx;height: 80rpx;text-align: center;line-height: 80rpx;margin: 0 auto;border: 1px solid red;margin-top: 400rpx;">登录</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
tologin(){ | |||
wx.navigateTo({ | |||
url: '/pages/login/index' | |||
}); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,24 @@ | |||
<template> | |||
<view> | |||
首页 | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,24 @@ | |||
<template> | |||
<view> | |||
学习 | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,24 @@ | |||
<template> | |||
<view> | |||
个人 | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,29 @@ | |||
<template> | |||
<view> | |||
登录页 | |||
<view @click="tologin()" style="width: 200rpx;height: 80rpx;text-align: center;line-height: 80rpx;margin: 0 auto;border: 1px solid red;margin-top: 400rpx;">确认</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return {}; | |||
}, | |||
components: {}, | |||
onLoad() {}, | |||
onShow() {}, | |||
methods: { | |||
tologin(){ | |||
uni.switchTab({ | |||
url: '/pages/index/index' | |||
}); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
</style> |
@@ -0,0 +1,7 @@ | |||
{ | |||
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", | |||
"rules": [{ | |||
"action": "allow", | |||
"page": "*" | |||
}] | |||
} |
@@ -0,0 +1,5 @@ | |||
/** | |||
* 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量 | |||
* uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用 | |||
*/ | |||
@import 'uview-ui/theme.scss'; |
@@ -0,0 +1,141 @@ | |||
## 2.1.3-20210513(2021-05-13) | |||
- 秋云图表组件 修改uCharts变更chartData数据为updateData方法,支持带滚动条的数据动态打点 | |||
- 秋云图表组件 增加onWindowResize防抖方法 fix by ど誓言,如尘般染指流年づ | |||
- 秋云图表组件 H5或者APP变更chartData数据显示loading图表时,原数据闪现的bug | |||
- 秋云图表组件 props增加errorReload禁用错误点击重新加载的方法 | |||
- uCharts.js 增加tooltip显示category(x轴对应点位)标题的功能,opts.extra.tooltip.showCategory,默认为false | |||
- uCharts.js 修复mix混合图只有柱状图时,tooltip的分割线显示位置不正确的bug | |||
- uCharts.js 修复开启滚动条,图表在拖动中动态打点,滚动条位置不正确的bug | |||
- uCharts.js 修复饼图类数据格式为echarts数据格式,series为空数组报错的bug | |||
- 示例项目 修改uCharts.js更新到v2.1.2版本后,@getIndex方法获取索引值变更为e.currentIndex.index | |||
- 示例项目 pages/updata/updata.vue增加滚动条拖动更新(数据动态打点)的demo | |||
- 示例项目 pages/other/other.vue增加errorReload禁用错误点击重新加载的demo | |||
## 2.1.2-20210509(2021-05-09) | |||
秋云图表组件 修复APP端初始化时就传入chartData或lacaldata不显示图表的bug | |||
## 2.1.1-20210509(2021-05-09) | |||
- 秋云图表组件 变更ECharts的eopts配置在renderjs内执行,支持在config-echarts.js配置文件内写function配置。 | |||
- 秋云图表组件 修复APP端报错Prop being mutated: "onmouse"错误的bug。 | |||
- 秋云图表组件 修复APP端报错Error: Not Found:Page[6][-1,27] at view.umd.min.js:1的bug。 | |||
## 2.1.0-20210507(2021-05-07) | |||
- 秋云图表组件 修复初始化时就有数据或者数据更新的时候loading加载动画闪动的bug | |||
- uCharts.js 修复x轴format方法categories为字符串类型时返回NaN的bug | |||
- uCharts.js 修复series.textColor、legend.fontColor未执行全局默认颜色的bug | |||
## 2.1.0-20210506(2021-05-06) | |||
- 秋云图表组件 修复极个别情况下报错item.properties undefined的bug | |||
- 秋云图表组件 修复极个别情况下关闭加载动画reshow不起作用,无法显示图表的bug | |||
- 示例项目 pages/ucharts/ucharts.vue 增加时间轴折线图(type="tline")、时间轴区域图(type="tarea")、散点图(type="scatter")、气泡图demo(type="bubble")、倒三角形漏斗图(opts.extra.funnel.type="triangle")、金字塔形漏斗图(opts.extra.funnel.type="pyramid") | |||
- 示例项目 pages/format-u/format-u.vue 增加X轴format格式化示例 | |||
- uCharts.js 升级至v2.1.0版本 | |||
- uCharts.js 修复 玫瑰图面积模式点击tooltip位置不正确的bug | |||
- uCharts.js 修复 玫瑰图点击图例,只剩一个类别显示空白的bug | |||
- uCharts.js 修复 饼图类图点击图例,其他图表tooltip位置某些情况下不准的bug | |||
- uCharts.js 修复 x轴为矢量轴(时间轴)情况下,点击tooltip位置不正确的bug | |||
- uCharts.js 修复 词云图获取点击索引偶尔不准的bug | |||
- uCharts.js 增加 直角坐标系图表X轴format格式化方法(原生uCharts.js用法请使用formatter) | |||
- uCharts.js 增加 漏斗图扩展配置,倒三角形(opts.extra.funnel.type="triangle"),金字塔形(opts.extra.funnel.type="pyramid") | |||
- uCharts.js 增加 散点图(opts.type="scatter")、气泡图(opts.type="bubble") | |||
- 后期计划 完善散点图、气泡图,增加markPoints标记点,增加横向条状图。 | |||
## 2.0.0-20210502(2021-05-02) | |||
- uCharts.js 修复词云图获取点击索引不正确的bug | |||
## 2.0.0-20210501(2021-05-01) | |||
- 秋云图表组件 修复QQ小程序、百度小程序在关闭动画效果情况下,v-for循环使用图表,显示不正确的bug | |||
## 2.0.0-20210426(2021-04-26) | |||
- 秋云图表组件 修复QQ小程序不支持canvas2d的bug | |||
- 秋云图表组件 修复钉钉小程序某些情况点击坐标计算错误的bug | |||
- uCharts.js 增加 extra.column.categoryGap 参数,柱状图类每个category点位(X轴点)柱子组之间的间距 | |||
- uCharts.js 增加 yAxis.data[i].titleOffsetY 参数,标题纵向偏移距离,负数为向上偏移,正数向下偏移 | |||
- uCharts.js 增加 yAxis.data[i].titleOffsetX 参数,标题横向偏移距离,负数为向左偏移,正数向右偏移 | |||
- uCharts.js 增加 extra.gauge.labelOffset 参数,仪表盘标签文字径向便宜距离,默认13px | |||
## 2.0.0-20210422-2(2021-04-22) | |||
秋云图表组件 修复 formatterAssign 未判断 args[key] == null 的情况导致栈溢出的 bug | |||
## 2.0.0-20210422(2021-04-22) | |||
- 秋云图表组件 修复H5、APP、支付宝小程序、微信小程序canvas2d模式下横屏模式的bug | |||
## 2.0.0-20210421(2021-04-21) | |||
- uCharts.js 修复多行图例的情况下,图例在上方或者下方时,图例float为左侧或者右侧时,第二行及以后的图例对齐方式不正确的bug | |||
## 2.0.0-20210420(2021-04-20) | |||
- 秋云图表组件 修复微信小程序开启canvas2d模式后,windows版微信小程序不支持canvas2d模式的bug | |||
- 秋云图表组件 修改非uni_modules版本为v2.0版本qiun-data-charts组件 | |||
## 2.0.0-20210419(2021-04-19) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑; | |||
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。 | |||
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。 | |||
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn) | |||
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- uCharts.js 修复混合图中柱状图单独设置颜色不生效的bug | |||
- uCharts.js 修复多Y轴单独设置fontSize时,开启canvas2d后,未对应放大字体的bug | |||
## 2.0.0-20210418(2021-04-18) | |||
- 秋云图表组件 增加directory配置,修复H5端history模式下如果发布到二级目录无法正确加载echarts.min.js的bug | |||
## 2.0.0-20210416(2021-04-16) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑; | |||
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。 | |||
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。 | |||
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn) | |||
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- 秋云图表组件 修复APP端某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员 | |||
- 示例项目 修复APP端v-for循环某些情况下报错`Not Found Page`的bug,fix by 高级bug开发技术员 | |||
- uCharts.js 修复非直角坐标系tooltip提示窗右侧超出未变换方向显示的bug | |||
## 2.0.0-20210415(2021-04-15) | |||
- 秋云图表组件 修复H5端发布到二级目录下echarts无法加载的bug | |||
- 秋云图表组件 修复某些情况下echarts.off('finished')移除监听事件报错的bug | |||
## 2.0.0-20210414(2021-04-14) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,如仍不好用,请重启电脑; | |||
## 如果是cli项目,请尝试清理node_modules,重新install,还不行就删除项目,再重新install。 | |||
## 此问题已于DCloud官方确认,HBuilderX下个版本会修复。 | |||
## 其他图表不显示问题详见[常见问题选项卡](https://demo.ucharts.cn) | |||
## <font color=#FF0000> 新手请先完整阅读帮助文档及常见问题3遍,右侧蓝色按钮示例项目请看2遍! </font> | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在项目中的应用参见 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- 秋云图表组件 修复H5端在cli项目下ECharts引用地址错误的bug | |||
- 示例项目 增加ECharts的formatter用法的示例(详见示例项目format-e.vue) | |||
- uCharts.js 增加圆环图中心背景色的配置extra.ring.centerColor | |||
- uCharts.js 修复微信小程序安卓端柱状图开启透明色后显示不正确的bug | |||
## 2.0.0-20210413(2021-04-13) | |||
- 秋云图表组件 修复百度小程序多个图表真机未能正确获取根元素dom尺寸的bug | |||
- 秋云图表组件 修复百度小程序横屏模式方向不正确的bug | |||
- 秋云图表组件 修改ontouch时,@getTouchStart@getTouchMove@getTouchEnd的触发条件 | |||
- uCharts.js 修复饼图类数据格式series属性不生效的bug | |||
- uCharts.js 增加时序区域图 详见示例项目中ucharts.vue | |||
## 2.0.0-20210412-2(2021-04-12) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX。如仍不好用,请重启电脑,此问题已于DCloud官方确认,HBuilderX下个版本会修复。 | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- 秋云图表组件 修复uCharts在APP端横屏模式下不能正确渲染的bug | |||
- 示例项目 增加ECharts柱状图渐变色、圆角柱状图、横向柱状图(条状图)的示例 | |||
## 2.0.0-20210412(2021-04-12) | |||
- 秋云图表组件 修复created中判断echarts导致APP端无法识别,改回mounted中判断echarts初始化 | |||
- uCharts.js 修复2d模式下series.textOffset未乘像素比的bug | |||
## 2.0.0-20210411(2021-04-11) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用如果提示未注册<qiun-data-charts>组件,请重启HBuilderX,并清空小程序开发者工具缓存。 | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在uniCloudAdmin中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- uCharts.js 折线图区域图增加connectNulls断点续连的功能,详见示例项目中ucharts.vue | |||
- 秋云图表组件 变更初始化方法为created,变更type2d默认值为true,优化2d模式下组件初始化后dom获取不到的bug | |||
- 秋云图表组件 修复左右布局时,右侧图表点击坐标错误的bug,修复tooltip柱状图自定义颜色显示object的bug | |||
## 2.0.0-20210410(2021-04-10) | |||
- 修复左右布局时,右侧图表点击坐标错误的bug,修复柱状图自定义颜色tooltip显示object的bug | |||
- 增加标记线及柱状图自定义颜色的demo | |||
## 2.0.0-20210409(2021-04-08) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- uCharts.js 修复钉钉小程序百度小程序measureText不准确的bug,修复2d模式下饼图类activeRadius为按比例放大的bug | |||
- 修复组件在支付宝小程序端点击位置不准确的bug | |||
## 2.0.0-20210408(2021-04-07) | |||
- 修复组件在支付宝小程序端不能显示的bug(目前支付宝小程不能点击交互,后续修复) | |||
- uCharts.js 修复高分屏下柱状图类,圆弧进度条 自定义宽度不能按比例放大的bug | |||
## 2.0.0-20210407(2021-04-06) | |||
## v1.0版本已停更,建议转uni_modules版本组件方式调用,点击右侧【使用HBuilderX导入插件】即可体验,DEMO演示及在线生成工具(v2.0文档)[https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## 增加 通过tofix和unit快速格式化y轴的demo add by `howcode` | |||
## 增加 图表组件在uniCloudAdmin中的应用 [UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
## 2.0.0-20210406(2021-04-05) | |||
# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页 | |||
## 2.0.0(2021-04-05) | |||
# 秋云图表组件+uCharts v2.0版本同步上线,使用方法详见https://demo.ucharts.cn帮助页 |
@@ -0,0 +1,162 @@ | |||
<template> | |||
<view class="container loading1"> | |||
<view class="shape shape1"></view> | |||
<view class="shape shape2"></view> | |||
<view class="shape shape3"></view> | |||
<view class="shape shape4"></view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'loading1', | |||
data() { | |||
return { | |||
}; | |||
} | |||
} | |||
</script> | |||
<style scoped="true"> | |||
.container { | |||
width: 30px; | |||
height: 30px; | |||
position: relative; | |||
} | |||
.container.loading1 { | |||
-webkit-transform: rotate(45deg); | |||
transform: rotate(45deg); | |||
} | |||
.container .shape { | |||
position: absolute; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 1px; | |||
} | |||
.container .shape.shape1 { | |||
left: 0; | |||
background-color: #1890FF; | |||
} | |||
.container .shape.shape2 { | |||
right: 0; | |||
background-color: #91CB74; | |||
} | |||
.container .shape.shape3 { | |||
bottom: 0; | |||
background-color: #FAC858; | |||
} | |||
.container .shape.shape4 { | |||
bottom: 0; | |||
right: 0; | |||
background-color: #EE6666; | |||
} | |||
.loading1 .shape1 { | |||
-webkit-animation: animation1shape1 0.5s ease 0s infinite alternate; | |||
animation: animation1shape1 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation1shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(16px, 16px); | |||
transform: translate(16px, 16px); | |||
} | |||
} | |||
@keyframes animation1shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(16px, 16px); | |||
transform: translate(16px, 16px); | |||
} | |||
} | |||
.loading1 .shape2 { | |||
-webkit-animation: animation1shape2 0.5s ease 0s infinite alternate; | |||
animation: animation1shape2 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation1shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-16px, 16px); | |||
transform: translate(-16px, 16px); | |||
} | |||
} | |||
@keyframes animation1shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-16px, 16px); | |||
transform: translate(-16px, 16px); | |||
} | |||
} | |||
.loading1 .shape3 { | |||
-webkit-animation: animation1shape3 0.5s ease 0s infinite alternate; | |||
animation: animation1shape3 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation1shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(16px, -16px); | |||
transform: translate(16px, -16px); | |||
} | |||
} | |||
@keyframes animation1shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(16px, -16px); | |||
transform: translate(16px, -16px); | |||
} | |||
} | |||
.loading1 .shape4 { | |||
-webkit-animation: animation1shape4 0.5s ease 0s infinite alternate; | |||
animation: animation1shape4 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation1shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-16px, -16px); | |||
transform: translate(-16px, -16px); | |||
} | |||
} | |||
@keyframes animation1shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-16px, -16px); | |||
transform: translate(-16px, -16px); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,170 @@ | |||
<template> | |||
<view class="container loading2"> | |||
<view class="shape shape1"></view> | |||
<view class="shape shape2"></view> | |||
<view class="shape shape3"></view> | |||
<view class="shape shape4"></view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'loading2', | |||
data() { | |||
return { | |||
}; | |||
} | |||
} | |||
</script> | |||
<style scoped="true"> | |||
.container { | |||
width: 30px; | |||
height: 30px; | |||
position: relative; | |||
} | |||
.container.loading2 { | |||
-webkit-transform: rotate(10deg); | |||
transform: rotate(10deg); | |||
} | |||
.container.loading2 .shape { | |||
border-radius: 5px; | |||
} | |||
.container.loading2{ | |||
-webkit-animation: rotation 1s infinite; | |||
animation: rotation 1s infinite; | |||
} | |||
.container .shape { | |||
position: absolute; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 1px; | |||
} | |||
.container .shape.shape1 { | |||
left: 0; | |||
background-color: #1890FF; | |||
} | |||
.container .shape.shape2 { | |||
right: 0; | |||
background-color: #91CB74; | |||
} | |||
.container .shape.shape3 { | |||
bottom: 0; | |||
background-color: #FAC858; | |||
} | |||
.container .shape.shape4 { | |||
bottom: 0; | |||
right: 0; | |||
background-color: #EE6666; | |||
} | |||
.loading2 .shape1 { | |||
-webkit-animation: animation2shape1 0.5s ease 0s infinite alternate; | |||
animation: animation2shape1 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation2shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(20px, 20px); | |||
transform: translate(20px, 20px); | |||
} | |||
} | |||
@keyframes animation2shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(20px, 20px); | |||
transform: translate(20px, 20px); | |||
} | |||
} | |||
.loading2 .shape2 { | |||
-webkit-animation: animation2shape2 0.5s ease 0s infinite alternate; | |||
animation: animation2shape2 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation2shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-20px, 20px); | |||
transform: translate(-20px, 20px); | |||
} | |||
} | |||
@keyframes animation2shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-20px, 20px); | |||
transform: translate(-20px, 20px); | |||
} | |||
} | |||
.loading2 .shape3 { | |||
-webkit-animation: animation2shape3 0.5s ease 0s infinite alternate; | |||
animation: animation2shape3 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation2shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(20px, -20px); | |||
transform: translate(20px, -20px); | |||
} | |||
} | |||
@keyframes animation2shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(20px, -20px); | |||
transform: translate(20px, -20px); | |||
} | |||
} | |||
.loading2 .shape4 { | |||
-webkit-animation: animation2shape4 0.5s ease 0s infinite alternate; | |||
animation: animation2shape4 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation2shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-20px, -20px); | |||
transform: translate(-20px, -20px); | |||
} | |||
} | |||
@keyframes animation2shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-20px, -20px); | |||
transform: translate(-20px, -20px); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,173 @@ | |||
<template> | |||
<view class="container loading3"> | |||
<view class="shape shape1"></view> | |||
<view class="shape shape2"></view> | |||
<view class="shape shape3"></view> | |||
<view class="shape shape4"></view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'loading3', | |||
data() { | |||
return { | |||
}; | |||
} | |||
} | |||
</script> | |||
<style scoped="true"> | |||
.container { | |||
width: 30px; | |||
height: 30px; | |||
position: relative; | |||
} | |||
.container.loading3 { | |||
-webkit-animation: rotation 1s infinite; | |||
animation: rotation 1s infinite; | |||
} | |||
.container.loading3 .shape1 { | |||
border-top-left-radius: 10px; | |||
} | |||
.container.loading3 .shape2 { | |||
border-top-right-radius: 10px; | |||
} | |||
.container.loading3 .shape3 { | |||
border-bottom-left-radius: 10px; | |||
} | |||
.container.loading3 .shape4 { | |||
border-bottom-right-radius: 10px; | |||
} | |||
.container .shape { | |||
position: absolute; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 1px; | |||
} | |||
.container .shape.shape1 { | |||
left: 0; | |||
background-color: #1890FF; | |||
} | |||
.container .shape.shape2 { | |||
right: 0; | |||
background-color: #91CB74; | |||
} | |||
.container .shape.shape3 { | |||
bottom: 0; | |||
background-color: #FAC858; | |||
} | |||
.container .shape.shape4 { | |||
bottom: 0; | |||
right: 0; | |||
background-color: #EE6666; | |||
} | |||
.loading3 .shape1 { | |||
-webkit-animation: animation3shape1 0.5s ease 0s infinite alternate; | |||
animation: animation3shape1 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation3shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(5px, 5px); | |||
transform: translate(5px, 5px); | |||
} | |||
} | |||
@keyframes animation3shape1 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(5px, 5px); | |||
transform: translate(5px, 5px); | |||
} | |||
} | |||
.loading3 .shape2 { | |||
-webkit-animation: animation3shape2 0.5s ease 0s infinite alternate; | |||
animation: animation3shape2 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation3shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-5px, 5px); | |||
transform: translate(-5px, 5px); | |||
} | |||
} | |||
@keyframes animation3shape2 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-5px, 5px); | |||
transform: translate(-5px, 5px); | |||
} | |||
} | |||
.loading3 .shape3 { | |||
-webkit-animation: animation3shape3 0.5s ease 0s infinite alternate; | |||
animation: animation3shape3 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation3shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(5px, -5px); | |||
transform: translate(5px, -5px); | |||
} | |||
} | |||
@keyframes animation3shape3 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(5px, -5px); | |||
transform: translate(5px, -5px); | |||
} | |||
} | |||
.loading3 .shape4 { | |||
-webkit-animation: animation3shape4 0.5s ease 0s infinite alternate; | |||
animation: animation3shape4 0.5s ease 0s infinite alternate; | |||
} | |||
@-webkit-keyframes animation3shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-5px, -5px); | |||
transform: translate(-5px, -5px); | |||
} | |||
} | |||
@keyframes animation3shape4 { | |||
from { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
to { | |||
-webkit-transform: translate(-5px, -5px); | |||
transform: translate(-5px, -5px); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,222 @@ | |||
<template> | |||
<view class="container loading5"> | |||
<view class="shape shape1"></view> | |||
<view class="shape shape2"></view> | |||
<view class="shape shape3"></view> | |||
<view class="shape shape4"></view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'loading5', | |||
data() { | |||
return { | |||
}; | |||
} | |||
} | |||
</script> | |||
<style scoped="true"> | |||
.container { | |||
width: 30px; | |||
height: 30px; | |||
position: relative; | |||
} | |||
.container.loading5 .shape { | |||
width: 15px; | |||
height: 15px; | |||
} | |||
.container .shape { | |||
position: absolute; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 1px; | |||
} | |||
.container .shape.shape1 { | |||
left: 0; | |||
background-color: #1890FF; | |||
} | |||
.container .shape.shape2 { | |||
right: 0; | |||
background-color: #91CB74; | |||
} | |||
.container .shape.shape3 { | |||
bottom: 0; | |||
background-color: #FAC858; | |||
} | |||
.container .shape.shape4 { | |||
bottom: 0; | |||
right: 0; | |||
background-color: #EE6666; | |||
} | |||
.loading5 .shape1 { | |||
animation: animation5shape1 2s ease 0s infinite reverse; | |||
} | |||
@-webkit-keyframes animation5shape1 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, 15px); | |||
transform: translate(0, 15px); | |||
} | |||
50% { | |||
-webkit-transform: translate(15px, 15px); | |||
transform: translate(15px, 15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(15px, 0); | |||
transform: translate(15px, 0); | |||
} | |||
} | |||
@keyframes animation5shape1 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, 15px); | |||
transform: translate(0, 15px); | |||
} | |||
50% { | |||
-webkit-transform: translate(15px, 15px); | |||
transform: translate(15px, 15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(15px, 0); | |||
transform: translate(15px, 0); | |||
} | |||
} | |||
.loading5 .shape2 { | |||
animation: animation5shape2 2s ease 0s infinite reverse; | |||
} | |||
@-webkit-keyframes animation5shape2 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(-15px, 0); | |||
transform: translate(-15px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(-15px, 15px); | |||
transform: translate(-15px, 15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, 15px); | |||
transform: translate(0, 15px); | |||
} | |||
} | |||
@keyframes animation5shape2 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(-15px, 0); | |||
transform: translate(-15px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(-15px, 15px); | |||
transform: translate(-15px, 15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, 15px); | |||
transform: translate(0, 15px); | |||
} | |||
} | |||
.loading5 .shape3 { | |||
animation: animation5shape3 2s ease 0s infinite reverse; | |||
} | |||
@-webkit-keyframes animation5shape3 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(15px, 0); | |||
transform: translate(15px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(15px, -15px); | |||
transform: translate(15px, -15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, -15px); | |||
transform: translate(0, -15px); | |||
} | |||
} | |||
@keyframes animation5shape3 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(15px, 0); | |||
transform: translate(15px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(15px, -15px); | |||
transform: translate(15px, -15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, -15px); | |||
transform: translate(0, -15px); | |||
} | |||
} | |||
.loading5 .shape4 { | |||
animation: animation5shape4 2s ease 0s infinite reverse; | |||
} | |||
@-webkit-keyframes animation5shape4 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, -15px); | |||
transform: translate(0, -15px); | |||
} | |||
50% { | |||
-webkit-transform: translate(-15px, -15px); | |||
transform: translate(-15px, -15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(-15px, 0); | |||
transform: translate(-15px, 0); | |||
} | |||
} | |||
@keyframes animation5shape4 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, -15px); | |||
transform: translate(0, -15px); | |||
} | |||
50% { | |||
-webkit-transform: translate(-15px, -15px); | |||
transform: translate(-15px, -15px); | |||
} | |||
75% { | |||
-webkit-transform: translate(-15px, 0); | |||
transform: translate(-15px, 0); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,229 @@ | |||
<template> | |||
<view class="container loading6"> | |||
<view class="shape shape1"></view> | |||
<view class="shape shape2"></view> | |||
<view class="shape shape3"></view> | |||
<view class="shape shape4"></view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'loading6', | |||
data() { | |||
return { | |||
}; | |||
} | |||
} | |||
</script> | |||
<style scoped="true"> | |||
.container { | |||
width: 30px; | |||
height: 30px; | |||
position: relative; | |||
} | |||
.container.loading6 { | |||
-webkit-animation: rotation 1s infinite; | |||
animation: rotation 1s infinite; | |||
} | |||
.container.loading6 .shape { | |||
width: 12px; | |||
height: 12px; | |||
border-radius: 2px; | |||
} | |||
.container .shape { | |||
position: absolute; | |||
width: 10px; | |||
height: 10px; | |||
border-radius: 1px; | |||
} | |||
.container .shape.shape1 { | |||
left: 0; | |||
background-color: #1890FF; | |||
} | |||
.container .shape.shape2 { | |||
right: 0; | |||
background-color: #91CB74; | |||
} | |||
.container .shape.shape3 { | |||
bottom: 0; | |||
background-color: #FAC858; | |||
} | |||
.container .shape.shape4 { | |||
bottom: 0; | |||
right: 0; | |||
background-color: #EE6666; | |||
} | |||
.loading6 .shape1 { | |||
-webkit-animation: animation6shape1 2s linear 0s infinite normal; | |||
animation: animation6shape1 2s linear 0s infinite normal; | |||
} | |||
@-webkit-keyframes animation6shape1 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, 18px); | |||
transform: translate(0, 18px); | |||
} | |||
50% { | |||
-webkit-transform: translate(18px, 18px); | |||
transform: translate(18px, 18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(18px, 0); | |||
transform: translate(18px, 0); | |||
} | |||
} | |||
@keyframes animation6shape1 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, 18px); | |||
transform: translate(0, 18px); | |||
} | |||
50% { | |||
-webkit-transform: translate(18px, 18px); | |||
transform: translate(18px, 18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(18px, 0); | |||
transform: translate(18px, 0); | |||
} | |||
} | |||
.loading6 .shape2 { | |||
-webkit-animation: animation6shape2 2s linear 0s infinite normal; | |||
animation: animation6shape2 2s linear 0s infinite normal; | |||
} | |||
@-webkit-keyframes animation6shape2 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(-18px, 0); | |||
transform: translate(-18px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(-18px, 18px); | |||
transform: translate(-18px, 18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, 18px); | |||
transform: translate(0, 18px); | |||
} | |||
} | |||
@keyframes animation6shape2 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(-18px, 0); | |||
transform: translate(-18px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(-18px, 18px); | |||
transform: translate(-18px, 18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, 18px); | |||
transform: translate(0, 18px); | |||
} | |||
} | |||
.loading6 .shape3 { | |||
-webkit-animation: animation6shape3 2s linear 0s infinite normal; | |||
animation: animation6shape3 2s linear 0s infinite normal; | |||
} | |||
@-webkit-keyframes animation6shape3 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(18px, 0); | |||
transform: translate(18px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(18px, -18px); | |||
transform: translate(18px, -18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, -18px); | |||
transform: translate(0, -18px); | |||
} | |||
} | |||
@keyframes animation6shape3 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(18px, 0); | |||
transform: translate(18px, 0); | |||
} | |||
50% { | |||
-webkit-transform: translate(18px, -18px); | |||
transform: translate(18px, -18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(0, -18px); | |||
transform: translate(0, -18px); | |||
} | |||
} | |||
.loading6 .shape4 { | |||
-webkit-animation: animation6shape4 2s linear 0s infinite normal; | |||
animation: animation6shape4 2s linear 0s infinite normal; | |||
} | |||
@-webkit-keyframes animation6shape4 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, -18px); | |||
transform: translate(0, -18px); | |||
} | |||
50% { | |||
-webkit-transform: translate(-18px, -18px); | |||
transform: translate(-18px, -18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(-18px, 0); | |||
transform: translate(-18px, 0); | |||
} | |||
} | |||
@keyframes animation6shape4 { | |||
0% { | |||
-webkit-transform: translate(0, 0); | |||
transform: translate(0, 0); | |||
} | |||
25% { | |||
-webkit-transform: translate(0, -18px); | |||
transform: translate(0, -18px); | |||
} | |||
50% { | |||
-webkit-transform: translate(-18px, -18px); | |||
transform: translate(-18px, -18px); | |||
} | |||
75% { | |||
-webkit-transform: translate(-18px, 0); | |||
transform: translate(-18px, 0); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,36 @@ | |||
<template> | |||
<view> | |||
<Loading1 v-if="loadingType==1"/> | |||
<Loading2 v-if="loadingType==2"/> | |||
<Loading3 v-if="loadingType==3"/> | |||
<Loading4 v-if="loadingType==4"/> | |||
<Loading5 v-if="loadingType==5"/> | |||
</view> | |||
</template> | |||
<script> | |||
import Loading1 from "./loading1.vue"; | |||
import Loading2 from "./loading2.vue"; | |||
import Loading3 from "./loading3.vue"; | |||
import Loading4 from "./loading4.vue"; | |||
import Loading5 from "./loading5.vue"; | |||
export default { | |||
components:{Loading1,Loading2,Loading3,Loading4,Loading5}, | |||
name: 'qiun-loading', | |||
props: { | |||
loadingType: { | |||
type: Number, | |||
default: 2 | |||
}, | |||
}, | |||
data() { | |||
return { | |||
}; | |||
}, | |||
} | |||
</script> | |||
<style> | |||
</style> |
@@ -0,0 +1,420 @@ | |||
/* | |||
* uCharts® | |||
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台 | |||
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved. | |||
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) | |||
* 复制使用请保留本段注释,感谢支持开源! | |||
* | |||
* uCharts®官方网站 | |||
* https://www.uCharts.cn | |||
* | |||
* 开源地址: | |||
* https://gitee.com/uCharts/uCharts | |||
* | |||
* uni-app插件市场地址: | |||
* http://ext.dcloud.net.cn/plugin?id=271 | |||
* | |||
*/ | |||
// 通用配置项 | |||
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性 | |||
const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc']; | |||
module.exports = { | |||
//demotype为自定义图表类型 | |||
"type": ["pie", "ring", "rose", "funnel", "line", "column", "area", "radar", "gauge","candle","demotype"], | |||
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型例如最后的"demotype" | |||
"categories": ["line", "column", "area", "radar", "gauge", "candle","demotype"], | |||
//instance为实例变量承载属性,option为eopts承载属性,不要删除 | |||
"instance": {}, | |||
"option": {}, | |||
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换 | |||
"formatter":{ | |||
"tooltipDemo1":function(res){ | |||
let result = '' | |||
for (let i in res) { | |||
if (i == 0) { | |||
result += res[i].axisValueLabel + '年销售额' | |||
} | |||
let value = '--' | |||
if (res[i].data !== null) { | |||
value = res[i].data | |||
} | |||
// #ifdef H5 | |||
result += '\n' + res[i].seriesName + ':' + value + ' 万元' | |||
// #endif | |||
// #ifdef APP-PLUS | |||
result += '<br/>' + res[i].marker + res[i].seriesName + ':' + value + ' 万元' | |||
// #endif | |||
} | |||
return result; | |||
}, | |||
legendFormat:function(name){ | |||
return "自定义图例+"+name; | |||
}, | |||
yAxisFormatDemo:function (value, index) { | |||
return value + '元'; | |||
}, | |||
seriesFormatDemo:function(res){ | |||
return res.name + '年' + res.value + '元'; | |||
} | |||
}, | |||
//这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在eopts参数,会将demotype与eopts中option合并后渲染图表。 | |||
"demotype":{ | |||
"color": color, | |||
//在这里填写echarts的option即可 | |||
}, | |||
//下面是自定义配置,请添加项目所需的通用配置 | |||
"column": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'axis' | |||
}, | |||
"grid": { | |||
"top": 30, | |||
"bottom": 50, | |||
"right": 15, | |||
"left": 40 | |||
}, | |||
"legend": { | |||
"bottom": 'left', | |||
}, | |||
"toolbox": { | |||
"show": false, | |||
}, | |||
"xAxis": { | |||
"type": 'category', | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
"boundaryGap": true, | |||
"data": [] | |||
}, | |||
"yAxis": { | |||
"type": 'value', | |||
"axisTick": { | |||
"show": false, | |||
}, | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'bar', | |||
"data": [], | |||
"barwidth": 20, | |||
"label": { | |||
"show": true, | |||
"color": "#666666", | |||
"position": 'top', | |||
}, | |||
}, | |||
}, | |||
"line": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'axis' | |||
}, | |||
"grid": { | |||
"top": 30, | |||
"bottom": 50, | |||
"right": 15, | |||
"left": 40 | |||
}, | |||
"legend": { | |||
"bottom": 'left', | |||
}, | |||
"toolbox": { | |||
"show": false, | |||
}, | |||
"xAxis": { | |||
"type": 'category', | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
"boundaryGap": true, | |||
"data": [] | |||
}, | |||
"yAxis": { | |||
"type": 'value', | |||
"axisTick": { | |||
"show": false, | |||
}, | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'line', | |||
"data": [], | |||
"barwidth": 20, | |||
"label": { | |||
"show": true, | |||
"color": "#666666", | |||
"position": 'top', | |||
}, | |||
}, | |||
}, | |||
"area": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'axis' | |||
}, | |||
"grid": { | |||
"top": 30, | |||
"bottom": 50, | |||
"right": 15, | |||
"left": 40 | |||
}, | |||
"legend": { | |||
"bottom": 'left', | |||
}, | |||
"toolbox": { | |||
"show": false, | |||
}, | |||
"xAxis": { | |||
"type": 'category', | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
"boundaryGap": true, | |||
"data": [] | |||
}, | |||
"yAxis": { | |||
"type": 'value', | |||
"axisTick": { | |||
"show": false, | |||
}, | |||
"axisLabel": { | |||
"color": '#666666' | |||
}, | |||
"axisLine": { | |||
"lineStyle": { | |||
"color": '#CCCCCC' | |||
} | |||
}, | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'line', | |||
"data": [], | |||
"areaStyle": {}, | |||
"label": { | |||
"show": true, | |||
"color": "#666666", | |||
"position": 'top', | |||
}, | |||
}, | |||
}, | |||
"pie": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'item' | |||
}, | |||
"grid": { | |||
"top": 40, | |||
"bottom": 30, | |||
"right": 15, | |||
"left": 15 | |||
}, | |||
"legend": { | |||
"bottom": 'left', | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'pie', | |||
"data": [], | |||
"radius": '50%', | |||
"label": { | |||
"show": true, | |||
"color": "#666666", | |||
"position": 'top', | |||
}, | |||
}, | |||
}, | |||
"ring": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'item' | |||
}, | |||
"grid": { | |||
"top": 40, | |||
"bottom": 30, | |||
"right": 15, | |||
"left": 15 | |||
}, | |||
"legend": { | |||
"bottom": 'left', | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'pie', | |||
"data": [], | |||
"radius": ['40%', '70%'], | |||
"avoidLabelOverlap": false, | |||
"label": { | |||
"show": true, | |||
"color": "#666666", | |||
"position": 'top', | |||
}, | |||
"labelLine": { | |||
"show": true | |||
}, | |||
}, | |||
}, | |||
"rose": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'item' | |||
}, | |||
"legend": { | |||
"top": 'bottom' | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'pie', | |||
"data": [], | |||
"radius": "55%", | |||
"center": ['50%', '50%'], | |||
"rosetype": 'area', | |||
}, | |||
}, | |||
"funnel": { | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"tooltip": { | |||
"trigger": 'item', | |||
"formatter": "{b} : {c}%" | |||
}, | |||
"legend": { | |||
"top": 'bottom' | |||
}, | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'funnel', | |||
"left": '10%', | |||
"top": 60, | |||
"bottom": 60, | |||
"width": '80%', | |||
"min": 0, | |||
"max": 100, | |||
"minSize": '0%', | |||
"maxSize": '100%', | |||
"sort": 'descending', | |||
"gap": 2, | |||
"label": { | |||
"show": true, | |||
"position": 'inside' | |||
}, | |||
"labelLine": { | |||
"length": 10, | |||
"lineStyle": { | |||
"width": 1, | |||
"type": 'solid' | |||
} | |||
}, | |||
"itemStyle": { | |||
"bordercolor": '#fff', | |||
"borderwidth": 1 | |||
}, | |||
"emphasis": { | |||
"label": { | |||
"fontSize": 20 | |||
} | |||
}, | |||
"data": [], | |||
}, | |||
}, | |||
"gauge": { | |||
"color": color, | |||
"tooltip": { | |||
"formatter": '{a} <br/>{b} : {c}%' | |||
}, | |||
"seriesTemplate": { | |||
"name": '业务指标', | |||
"type": 'gauge', | |||
"detail": {"formatter": '{value}%'}, | |||
"data": [{"value": 50, "name": '完成率'}] | |||
}, | |||
}, | |||
"candle": { | |||
"xAxis": { | |||
"data": [] | |||
}, | |||
"yAxis": {}, | |||
"color": color, | |||
"title": { | |||
"text": '' | |||
}, | |||
"dataZoom": [{ | |||
"type": 'inside', | |||
"xAxisIndex": [0, 1], | |||
"start": 10, | |||
"end": 100 | |||
}, | |||
{ | |||
"show": true, | |||
"xAxisIndex": [0, 1], | |||
"type": 'slider', | |||
"bottom": 10, | |||
"start": 10, | |||
"end": 100 | |||
} | |||
], | |||
"seriesTemplate": { | |||
"name": '', | |||
"type": 'k', | |||
"data": [], | |||
}, | |||
} | |||
} |
@@ -0,0 +1,870 @@ | |||
/* | |||
* uCharts® | |||
* 高性能跨平台图表库,支持H5、APP、小程序(微信/支付宝/百度/头条/QQ/360)、Vue、Taro等支持canvas的框架平台 | |||
* Copyright (c) 2021 QIUN®秋云 https://www.ucharts.cn All rights reserved. | |||
* Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) | |||
* 复制使用请保留本段注释,感谢支持开源! | |||
* | |||
* uCharts®官方网站 | |||
* https://www.uCharts.cn | |||
* | |||
* 开源地址: | |||
* https://gitee.com/uCharts/uCharts | |||
* | |||
* uni-app插件市场地址: | |||
* http://ext.dcloud.net.cn/plugin?id=271 | |||
* | |||
*/ | |||
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性 | |||
const color = ['#1890FF', '#91CB74', '#FAC858', '#EE6666', '#73C0DE', '#3CA272', '#FC8452', '#9A60B4', '#ea7ccc']; | |||
//事件转换函数,主要用作格式化x轴为时间轴,根据需求自行修改 | |||
const formatDateTime = (timeStamp, returnType)=>{ | |||
var date = new Date(); | |||
date.setTime(timeStamp * 1000); | |||
var y = date.getFullYear(); | |||
var m = date.getMonth() + 1; | |||
m = m < 10 ? ('0' + m) : m; | |||
var d = date.getDate(); | |||
d = d < 10 ? ('0' + d) : d; | |||
var h = date.getHours(); | |||
h = h < 10 ? ('0' + h) : h; | |||
var minute = date.getMinutes(); | |||
var second = date.getSeconds(); | |||
minute = minute < 10 ? ('0' + minute) : minute; | |||
second = second < 10 ? ('0' + second) : second; | |||
if(returnType == 'full'){return y + '-' + m + '-' + d + ' '+ h +':' + minute + ':' + second;} | |||
if(returnType == 'y-m-d'){return y + '-' + m + '-' + d;} | |||
if(returnType == 'h:m'){return h +':' + minute;} | |||
if(returnType == 'h:m:s'){return h +':' + minute +':' + second;} | |||
return [y, m, d, h, minute, second]; | |||
} | |||
module.exports = { | |||
//demotype为自定义图表类型,一般不需要自定义图表类型,只需要改根节点上对应的类型即可 | |||
"type":["pie","ring","rose","word","funnel","map","arcbar","line","column","area","radar","gauge","candle","mix","tline","tarea","scatter","bubble","demotype"], | |||
"range":["饼状图","圆环图","玫瑰图","词云图","漏斗图","地图","圆弧进度条","折线图","柱状图","区域图","雷达图","仪表盘","K线图","混合图","时间轴折线","时间轴区域","散点图","气泡图","自定义类型"], | |||
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型,例如最后的"demotype" | |||
//自定义类型时需要注意"tline","tarea","scatter","bubble"等时间轴(矢量x轴)类图表,没有categories,不需要加入categories | |||
"categories":["line","column","area","radar","gauge","candle","mix","demotype"], | |||
//instance为实例变量承载属性,不要删除 | |||
"instance":{}, | |||
//option为opts及eopts承载属性,不要删除 | |||
"option":{}, | |||
//下面是自定义format配置,因除H5端外的其他端无法通过props传递函数,只能通过此属性对应下标的方式来替换 | |||
"formatter":{ | |||
"yAxisDemo1":function(val){return val+'元'}, | |||
"yAxisDemo2":function(val){return val.toFixed(2)}, | |||
"xAxisDemo1":function(val){return val+'年'}, | |||
"xAxisDemo2":function(val){return formatDateTime(val,'h:m')}, | |||
"seriesDemo1":function(val){return val+'元'}, | |||
"tooltipDemo1":function(item, category, index, opts){ | |||
if(index==0){ | |||
return '随便用'+item.data+'年' | |||
}else{ | |||
return '其他我没改'+item.data+'天' | |||
} | |||
}, | |||
"pieDemo":function(val, index, series){ | |||
if(index !== undefined){ | |||
return series[index].name+':'+series[index].data+'元' | |||
} | |||
}, | |||
}, | |||
//这里演示了自定义您的图表类型的option,可以随意命名,之后在组件上 type="demotype" 后,组件会调用这个花括号里的option,如果组件上还存在opts参数,会将demotype与opts中option合并后渲染图表。 | |||
"demotype":{ | |||
//我这里把曲线图当做了自定义图表类型,您可以根据需要随意指定类型或配置 | |||
"type": "line", | |||
"color": color, | |||
"padding": [15,10,0,15], | |||
"xAxis": { | |||
"disableGrid": true, | |||
}, | |||
"yAxis": { | |||
"gridType": "dash", | |||
"dashLength": 2, | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"line": { | |||
"type": "curve", | |||
"width": 2 | |||
}, | |||
} | |||
}, | |||
//下面是自定义配置,请添加项目所需的通用配置 | |||
"pie":{ | |||
"type": "pie", | |||
"color": color, | |||
"padding": [5,5,5,5], | |||
"extra": { | |||
"pie": { | |||
"activeOpacity": 0.5, | |||
"activeRadius": 10, | |||
"offsetAngle": 0, | |||
"labelWidth": 15, | |||
"border": true, | |||
"borderWidth": 3, | |||
"borderColor": "#FFFFFF" | |||
}, | |||
} | |||
}, | |||
"ring":{ | |||
"type": "ring", | |||
"padding": [15,5,5,5], | |||
"rotate": false, | |||
"dataLabel": true, | |||
"color":[ '#66AFF5', '#FABD2B', '#6F8EDC', '#FFCF8F', '#F98120', '#1CC99E', '#9474FB', | |||
'#657292','#7A6A99','#BF5D52','#EE6666','#77B7E4','#E6A065','#9D5139','#C1AA88','#F87F7A', | |||
'#F6CF74','#7F5506','#88BB9B','#6E99AA','#5789D0' | |||
], | |||
"legend": { | |||
"show": true, | |||
"position": "bottom", | |||
"float": "center", | |||
"padding": 5, | |||
"margin": 5, | |||
"backgroundColor": "rgba(0,0,0,0)", | |||
"borderColor": "rgba(0,0,0,0)", | |||
"borderWidth": 0, | |||
"fontSize": 10, | |||
"fontColor": "#666666", | |||
"lineHeight": 25, | |||
"hiddenColor": "#CECECE", | |||
"itemGap": 6 | |||
}, | |||
"title": { | |||
"name": "", | |||
"fontSize": 15, | |||
"color": "#666666" | |||
}, | |||
"subtitle": { | |||
"name": "70", | |||
"fontSize": 25, | |||
"color": "#7cb5ec" | |||
}, | |||
"extra": { | |||
"ring": { | |||
"ringWidth":30, | |||
"activeOpacity": 0.5, | |||
"activeRadius": 10, | |||
"offsetAngle": 0, | |||
"labelWidth": 15, | |||
"border": true, | |||
"borderWidth": 3, | |||
"borderColor": "#FFFFFF" | |||
}, | |||
}, | |||
}, | |||
"rose":{ | |||
"type": "rose", | |||
"color": color, | |||
"padding": [5,5,5,5], | |||
"legend": { | |||
"show": true, | |||
"position": "left", | |||
"lineHeight": 25, | |||
}, | |||
"extra": { | |||
"rose": { | |||
"type": "area", | |||
"minRadius": 50, | |||
"activeOpacity": 0.5, | |||
"activeRadius": 10, | |||
"offsetAngle": 0, | |||
"labelWidth": 15, | |||
"border": false, | |||
"borderWidth": 2, | |||
"borderColor": "#FFFFFF" | |||
}, | |||
} | |||
}, | |||
"word":{ | |||
"type": "word", | |||
"color": color, | |||
"extra": { | |||
"word": { | |||
"type": "normal", | |||
"autoColors": false | |||
} | |||
} | |||
}, | |||
"funnel":{ | |||
"type": "funnel", | |||
"color": color, | |||
"padding": [15,15,0,15], | |||
"extra": { | |||
"funnel": { | |||
"activeOpacity": 0.3, | |||
"activeWidth": 10, | |||
"border": true, | |||
"borderWidth": 2, | |||
"borderColor": "#FFFFFF", | |||
"fillOpacity": 1, | |||
"labelAlign": "right" | |||
}, | |||
} | |||
}, | |||
"map":{ | |||
"type": "map", | |||
"color": color, | |||
"padding": [0,0,0,0], | |||
"dataLabel": true, | |||
"extra": { | |||
"map": { | |||
"border": true, | |||
"borderWidth": 1, | |||
"borderColor": "#666666", | |||
"fillOpacity": 0.6, | |||
"activeBorderColor": "#F04864", | |||
"activeFillColor": "#FACC14", | |||
"activeFillOpacity": 1 | |||
}, | |||
} | |||
}, | |||
"arcbar":{ | |||
"type": "arcbar", | |||
"color": color, | |||
"title": { | |||
"name": "百分比", | |||
"fontSize": 25, | |||
"color": "#00FF00" | |||
}, | |||
"subtitle": { | |||
"name": "默认标题", | |||
"fontSize": 15, | |||
"color": "#666666" | |||
}, | |||
"extra": { | |||
"arcbar": { | |||
"type": "default", | |||
"width": 12, | |||
"backgroundColor": "#E9E9E9", | |||
"startAngle": 0.75, | |||
"endAngle": 0.25, | |||
"gap": 2 | |||
} | |||
} | |||
}, | |||
"line":{ | |||
"type": "line", | |||
"canvasId": "", | |||
"canvas2d": true, | |||
"background": "none", | |||
"animation": true, | |||
"timing": "easeOut", | |||
"duration": 1000, | |||
"color": [ | |||
"#1890FF", | |||
"#91CB74", | |||
"#FAC858", | |||
"#EE6666", | |||
"#73C0DE", | |||
"#3CA272", | |||
"#FC8452", | |||
"#9A60B4", | |||
"#ea7ccc" | |||
], | |||
"padding": [ | |||
15, | |||
0, | |||
0, | |||
4 | |||
], | |||
"rotate": false, | |||
"errorReload": true, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"enableScroll": true, | |||
"touchMoveLimit": 60, | |||
"enableMarkLine": false, | |||
"dataLabel": true, | |||
"dataPointShape": true, | |||
"dataPointShapeType": "solid", | |||
"xAxis": { | |||
"disabled": false, | |||
"axisLine": true, | |||
"axisLineColor": "#CCCCCC", | |||
"calibration": false, | |||
"fontColor": "#666666", | |||
"fontSize": 13, | |||
"rotateLabel": true, | |||
"itemCount": 3, | |||
"boundaryGap": "center", | |||
"disableGrid": true, | |||
"gridColor": "#CCCCCC", | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"gridEval": 1, | |||
"scrollShow": true, | |||
"scrollAlign": "left", | |||
"scrollColor": "#A6A6A6", | |||
"scrollBackgroundColor": "#EFEBEF" | |||
}, | |||
"yAxis": { | |||
"disabled": false, | |||
"disableGrid": false, | |||
"splitNumber": 5, | |||
"gridType": "dash", | |||
"dashLength": 2, | |||
"gridColor": "#CCCCCC", | |||
"padding": 10, | |||
"showTitle": false, | |||
"min":0, | |||
"max":100, | |||
"data": [] | |||
}, | |||
"legend": { | |||
"show": true, | |||
"position": "bottom", | |||
"float": "center", | |||
"padding": 5, | |||
"margin": 5, | |||
"backgroundColor": "rgba(0,0,0,0)", | |||
"borderColor": "rgba(0,0,0,0)", | |||
"borderWidth": 0, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"lineHeight": 11, | |||
"hiddenColor": "#CECECE", | |||
"itemGap": 10 | |||
}, | |||
"extra": { | |||
"line": { | |||
"type": "straight", | |||
"width": 2 | |||
}, | |||
"tooltip": { | |||
"showBox": true, | |||
"showArrow": true, | |||
"showCategory": false, | |||
"borderWidth": 0, | |||
"borderRadius": 0, | |||
"borderColor": "#000000", | |||
"borderOpacity": 0.7, | |||
"bgColor": "#000000", | |||
"bgOpacity": 0.7, | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"gridColor": "#CCCCCC", | |||
"fontColor": "#FFFFFF", | |||
"splitLine": true, | |||
"horizentalLine": false, | |||
"xAxisLabel": false, | |||
"yAxisLabel": false, | |||
"labelBgColor": "#FFFFFF", | |||
"labelBgOpacity": 0.7, | |||
"labelFontColor": "#666666" | |||
}, | |||
"markLine": { | |||
"type": "solid", | |||
"dashLength": 4, | |||
"data": [] | |||
} | |||
} | |||
}, | |||
"tline":{ | |||
"type": "line", | |||
"color": color, | |||
"padding": [15,10,0,15], | |||
"xAxis": { | |||
"disableGrid": false, | |||
"boundaryGap":"justify", | |||
}, | |||
"yAxis": { | |||
"gridType": "dash", | |||
"dashLength": 2, | |||
"data":[ | |||
{ | |||
"min":0, | |||
"max":80 | |||
} | |||
] | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"line": { | |||
"type": "curve", | |||
"width": 2 | |||
}, | |||
} | |||
}, | |||
"tarea":{ | |||
"type": "area", | |||
"color": color, | |||
"padding": [15,10,0,15], | |||
"xAxis": { | |||
"disableGrid": true, | |||
"boundaryGap":"justify", | |||
}, | |||
"yAxis": { | |||
"gridType": "dash", | |||
"dashLength": 2, | |||
"data":[ | |||
{ | |||
"min":0, | |||
"max":80 | |||
} | |||
] | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"area": { | |||
"type": "curve", | |||
"opacity": 0.2, | |||
"addLine": true, | |||
"width": 2, | |||
"gradient": true | |||
}, | |||
} | |||
}, | |||
"column":{ | |||
"type": "column", | |||
"canvasId": "", | |||
"canvas2d": true, | |||
"background": "none", | |||
"animation": true, | |||
"timing": "easeOut", | |||
"duration": 1000, | |||
"color": [ | |||
"#1890FF", | |||
"#91CB74", | |||
"#FAC858", | |||
"#EE6666", | |||
"#73C0DE", | |||
"#3CA272", | |||
"#FC8452", | |||
"#9A60B4", | |||
"#ea7ccc" | |||
], | |||
"padding": [ | |||
15, | |||
15, | |||
0, | |||
5 | |||
], | |||
"rotate": false, | |||
"errorReload": true, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"enableScroll": true, | |||
"touchMoveLimit": 60, | |||
"enableMarkLine": false, | |||
"dataLabel": true, | |||
"dataPointShape": true, | |||
"dataPointShapeType": "solid", | |||
"xAxis": { | |||
"disabled": false, | |||
"axisLine": true, | |||
"axisLineColor": "#CCCCCC", | |||
"calibration": false, | |||
"fontColor": "#666666", | |||
"fontSize": 13, | |||
"rotateLabel": false, | |||
"itemCount": 7, | |||
"boundaryGap": "center", | |||
"disableGrid": true, | |||
"gridColor": "#CCCCCC", | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"gridEval": 1, | |||
"scrollShow": true, | |||
"scrollAlign": "left", | |||
"scrollColor": "#A6A6A6", | |||
"scrollBackgroundColor": "#EFEBEF" | |||
}, | |||
"yAxis": { | |||
"disabled": false, | |||
"disableGrid": false, | |||
"splitNumber": 5, | |||
"gridType": "dash", | |||
"dashLength": 8, | |||
"gridColor": "#CCCCCC", | |||
"padding": 10, | |||
"showTitle": false, | |||
"min":0, | |||
"max":100, | |||
"data": [] | |||
}, | |||
"legend": { | |||
"show": true, | |||
"position": "bottom", | |||
"float": "center", | |||
"padding": 5, | |||
"margin": 5, | |||
"backgroundColor": "rgba(0,0,0,0)", | |||
"borderColor": "rgba(0,0,0,0)", | |||
"borderWidth": 0, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"lineHeight": 11, | |||
"hiddenColor": "#CECECE", | |||
"itemGap": 10 | |||
}, | |||
"extra": { | |||
"column": { | |||
"type": "group", | |||
"width": 30, | |||
"seriesGap": 2, | |||
"categoryGap": 3, | |||
"barBorderCircle": false, | |||
"linearType": "none", | |||
"linearOpacity": 1, | |||
"colorStop": 0, | |||
"meterBorder": 1, | |||
"meterFillColor": "#FFFFFF", | |||
"activeBgColor": "#000000", | |||
"activeBgOpacity": 0.08, | |||
"meterBorde": 1 | |||
}, | |||
"tooltip": { | |||
"showBox": true, | |||
"showArrow": true, | |||
"showCategory": false, | |||
"borderWidth": 0, | |||
"borderRadius": 0, | |||
"borderColor": "#000000", | |||
"borderOpacity": 0.7, | |||
"bgColor": "#000000", | |||
"bgOpacity": 0.7, | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"gridColor": "#CCCCCC", | |||
"fontColor": "#FFFFFF", | |||
"splitLine": true, | |||
"horizentalLine": false, | |||
"xAxisLabel": false, | |||
"yAxisLabel": false, | |||
"labelBgColor": "#FFFFFF", | |||
"labelBgOpacity": 0.7, | |||
"labelFontColor": "#666666" | |||
}, | |||
"markLine": { | |||
"type": "solid", | |||
"dashLength": 4, | |||
"data": [] | |||
} | |||
} | |||
}, | |||
"area":{ | |||
"type": "area", | |||
"color": color, | |||
"padding": [15,15,0,15], | |||
"xAxis": { | |||
"disableGrid": true, | |||
}, | |||
"yAxis": { | |||
"gridType": "dash", | |||
"dashLength": 2, | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"area": { | |||
"type": "straight", | |||
"opacity": 0.2, | |||
"addLine": true, | |||
"width": 2, | |||
"gradient": false | |||
}, | |||
} | |||
}, | |||
"radar":{ | |||
"type": "radar", | |||
"canvasId": "", | |||
"canvas2d": false, | |||
"background": "none", | |||
"animation": true, | |||
"timing": "easeOut", | |||
"duration": 1000, | |||
"color": [ | |||
"#1890FF", | |||
"#91CB74", | |||
"#FAC858", | |||
"#EE6666", | |||
"#73C0DE", | |||
"#3CA272", | |||
"#FC8452", | |||
"#9A60B4", | |||
"#ea7ccc" | |||
], | |||
"padding": [ | |||
5, | |||
5, | |||
5, | |||
5 | |||
], | |||
"rotate": false, | |||
"errorReload": true, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"enableScroll": false, | |||
"touchMoveLimit": 60, | |||
"enableMarkLine": false, | |||
"dataLabel": true, | |||
"dataPointShape": true, | |||
"dataPointShapeType": "solid", | |||
"legend": { | |||
"show": false, | |||
"position": "right", | |||
"float": "center", | |||
"padding": 5, | |||
"margin": 5, | |||
"backgroundColor": "rgba(0,0,0,0)", | |||
"borderColor": "rgba(0,0,0,0)", | |||
"borderWidth": 0, | |||
"fontSize": 13, | |||
"fontColor": "#666666", | |||
"lineHeight": 25, | |||
"hiddenColor": "#CECECE", | |||
"itemGap": 10 | |||
}, | |||
"extra": { | |||
"radar": { | |||
"gridType": "radar", | |||
"gridColor": "#CCCCCC", | |||
"gridCount": 3, | |||
"labelColor": "#666666", | |||
"opacity": 0.2, | |||
"border": false, | |||
"borderWidth": 2, | |||
"max": 100 | |||
}, | |||
"tooltip": { | |||
"showBox": true, | |||
"showArrow": true, | |||
"showCategory": false, | |||
"borderWidth": 0, | |||
"borderRadius": 0, | |||
"borderColor": "#000000", | |||
"borderOpacity": 0.7, | |||
"bgColor": "#000000", | |||
"bgOpacity": 0.7, | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"gridColor": "#CCCCCC", | |||
"fontColor": "#FFFFFF", | |||
"splitLine": true, | |||
"horizentalLine": false, | |||
"xAxisLabel": false, | |||
"yAxisLabel": false, | |||
"labelBgColor": "#FFFFFF", | |||
"labelBgOpacity": 0.7, | |||
"labelFontColor": "#666666" | |||
} | |||
} | |||
}, | |||
"gauge":{ | |||
"type": "gauge", | |||
"color": color, | |||
"title": { | |||
"name": "66Km/H", | |||
"fontSize": 25, | |||
"color": "#2fc25b", | |||
"offsetY": 50 | |||
}, | |||
"subtitle": { | |||
"name": "实时速度", | |||
"fontSize": 15, | |||
"color": "#1890ff", | |||
"offsetY": -50 | |||
}, | |||
"extra": { | |||
"gauge": { | |||
"type": "default", | |||
"width": 30, | |||
"labelColor": "#666666", | |||
"startAngle": 0.75, | |||
"endAngle": 0.25, | |||
"startNumber": 0, | |||
"endNumber": 100, | |||
"labelFormat": "", | |||
"splitLine": { | |||
"fixRadius": 0, | |||
"splitNumber": 10, | |||
"width": 30, | |||
"color": "#FFFFFF", | |||
"childNumber": 5, | |||
"childWidth": 12 | |||
}, | |||
"pointer": { | |||
"width": 24, | |||
"color": "auto" | |||
} | |||
} | |||
} | |||
}, | |||
"candle":{ | |||
"type": "candle", | |||
"color": color, | |||
"padding": [15,15,0,15], | |||
"enableScroll": true, | |||
"enableMarkLine": true, | |||
"dataLabel": false, | |||
"xAxis": { | |||
"labelCount": 4, | |||
"itemCount": 40, | |||
"disableGrid": true, | |||
"gridColor": "#CCCCCC", | |||
"gridType": "solid", | |||
"dashLength": 4, | |||
"scrollShow": true, | |||
"scrollAlign": "left", | |||
"scrollColor": "#A6A6A6", | |||
"scrollBackgroundColor": "#EFEBEF" | |||
}, | |||
"yAxis": { | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"candle": { | |||
"color": { | |||
"upLine": "#f04864", | |||
"upFill": "#f04864", | |||
"downLine": "#2fc25b", | |||
"downFill": "#2fc25b" | |||
}, | |||
"average": { | |||
"show": true, | |||
"name": ["MA5","MA10","MA30"], | |||
"day": [5,10,20], | |||
"color": ["#1890ff","#2fc25b","#facc14"] | |||
} | |||
}, | |||
"markLine": { | |||
"type": "dash", | |||
"dashLength": 5, | |||
"data": [ | |||
{ | |||
"value": 2150, | |||
"lineColor": "#f04864", | |||
"showLabel": true | |||
}, | |||
{ | |||
"value": 2350, | |||
"lineColor": "#f04864", | |||
"showLabel": true | |||
} | |||
] | |||
} | |||
} | |||
}, | |||
"mix":{ | |||
"type": "mix", | |||
"color": color, | |||
"padding": [15,15,0,15], | |||
"xAxis": { | |||
"disableGrid": true, | |||
}, | |||
"yAxis": { | |||
"disabled": false, | |||
"disableGrid": false, | |||
"splitNumber": 5, | |||
"gridType": "dash", | |||
"dashLength": 4, | |||
"gridColor": "#CCCCCC", | |||
"padding": 10, | |||
"showTitle": true, | |||
"data": [] | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"mix": { | |||
"column": { | |||
"width": 20 | |||
} | |||
}, | |||
} | |||
}, | |||
"scatter":{ | |||
"type": "scatter", | |||
"color":color, | |||
"padding":[15,15,0,15], | |||
"dataLabel":false, | |||
"xAxis": { | |||
"disableGrid": false, | |||
"gridType":"dash", | |||
"splitNumber":5, | |||
"boundaryGap":"justify", | |||
"min":0 | |||
}, | |||
"yAxis": { | |||
"disableGrid": false, | |||
"gridType":"dash", | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"scatter": { | |||
}, | |||
} | |||
}, | |||
"bubble":{ | |||
"type": "bubble", | |||
"color":color, | |||
"padding":[15,15,0,15], | |||
"xAxis": { | |||
"disableGrid": false, | |||
"gridType":"dash", | |||
"splitNumber":5, | |||
"boundaryGap":"justify", | |||
"min":0, | |||
"max":250 | |||
}, | |||
"yAxis": { | |||
"disableGrid": false, | |||
"gridType":"dash", | |||
"data":[{ | |||
"min":0, | |||
"max":150 | |||
}] | |||
}, | |||
"legend": { | |||
}, | |||
"extra": { | |||
"bubble": { | |||
"border":2, | |||
"opacity": 0.5, | |||
}, | |||
} | |||
} | |||
} |
@@ -0,0 +1,12 @@ | |||
# uCharts JSSDK说明 | |||
1、如不使用uCharts组件,可直接引用u-charts.js,打包编译后会`自动压缩`,压缩后体积约为`98kb`。 | |||
2、如果100kb的体积仍需压缩,请手动删除u-charts.js内您不需要的图表类型,如k线图candle。 | |||
3、config-ucharts.js为uCharts组件的用户配置文件,升级前请`自行备份config-ucharts.js`文件,以免被强制覆盖。 | |||
3、config-echarts.js为ECharts组件的用户配置文件,升级前请`自行备份config-echarts.js`文件,以免被强制覆盖。 | |||
# v1.0转v2.0注意事项 | |||
1、opts.colors变更为opts.color | |||
2、ring圆环图的扩展配置由extra.pie变更为extra.ring | |||
3、混合图借用的扩展配置由extra.column变更为extra.mix.column | |||
4、全部涉及到format的格式化属性变更为formatter | |||
5、不需要再传canvasId及$this参数,如果通过uChats获取context,可能会导致this实例混乱,导致小程序开发者工具报错。如果不使用qiun-data-charts官方组件,需要在new uCharts()实例化之前,自行获取canvas的上下文context(ctx),并传入new中的context(opts.context)。为了能跨更多的端,给您带来的不便敬请谅解。 |
@@ -0,0 +1,201 @@ | |||
Apache License | |||
Version 2.0, January 2004 | |||
http://www.apache.org/licenses/ | |||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||
1. Definitions. | |||
"License" shall mean the terms and conditions for use, reproduction, | |||
and distribution as defined by Sections 1 through 9 of this document. | |||
"Licensor" shall mean the copyright owner or entity authorized by | |||
the copyright owner that is granting the License. | |||
"Legal Entity" shall mean the union of the acting entity and all | |||
other entities that control, are controlled by, or are under common | |||
control with that entity. For the purposes of this definition, | |||
"control" means (i) the power, direct or indirect, to cause the | |||
direction or management of such entity, whether by contract or | |||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||
outstanding shares, or (iii) beneficial ownership of such entity. | |||
"You" (or "Your") shall mean an individual or Legal Entity | |||
exercising permissions granted by this License. | |||
"Source" form shall mean the preferred form for making modifications, | |||
including but not limited to software source code, documentation | |||
source, and configuration files. | |||
"Object" form shall mean any form resulting from mechanical | |||
transformation or translation of a Source form, including but | |||
not limited to compiled object code, generated documentation, | |||
and conversions to other media types. | |||
"Work" shall mean the work of authorship, whether in Source or | |||
Object form, made available under the License, as indicated by a | |||
copyright notice that is included in or attached to the work | |||
(an example is provided in the Appendix below). | |||
"Derivative Works" shall mean any work, whether in Source or Object | |||
form, that is based on (or derived from) the Work and for which the | |||
editorial revisions, annotations, elaborations, or other modifications | |||
represent, as a whole, an original work of authorship. For the purposes | |||
of this License, Derivative Works shall not include works that remain | |||
separable from, or merely link (or bind by name) to the interfaces of, | |||
the Work and Derivative Works thereof. | |||
"Contribution" shall mean any work of authorship, including | |||
the original version of the Work and any modifications or additions | |||
to that Work or Derivative Works thereof, that is intentionally | |||
submitted to Licensor for inclusion in the Work by the copyright owner | |||
or by an individual or Legal Entity authorized to submit on behalf of | |||
the copyright owner. For the purposes of this definition, "submitted" | |||
means any form of electronic, verbal, or written communication sent | |||
to the Licensor or its representatives, including but not limited to | |||
communication on electronic mailing lists, source code control systems, | |||
and issue tracking systems that are managed by, or on behalf of, the | |||
Licensor for the purpose of discussing and improving the Work, but | |||
excluding communication that is conspicuously marked or otherwise | |||
designated in writing by the copyright owner as "Not a Contribution." | |||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||
on behalf of whom a Contribution has been received by Licensor and | |||
subsequently incorporated within the Work. | |||
2. Grant of Copyright License. Subject to the terms and conditions of | |||
this License, each Contributor hereby grants to You a perpetual, | |||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
copyright license to reproduce, prepare Derivative Works of, | |||
publicly display, publicly perform, sublicense, and distribute the | |||
Work and such Derivative Works in Source or Object form. | |||
3. Grant of Patent License. Subject to the terms and conditions of | |||
this License, each Contributor hereby grants to You a perpetual, | |||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||
(except as stated in this section) patent license to make, have made, | |||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||
where such license applies only to those patent claims licensable | |||
by such Contributor that are necessarily infringed by their | |||
Contribution(s) alone or by combination of their Contribution(s) | |||
with the Work to which such Contribution(s) was submitted. If You | |||
institute patent litigation against any entity (including a | |||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||
or a Contribution incorporated within the Work constitutes direct | |||
or contributory patent infringement, then any patent licenses | |||
granted to You under this License for that Work shall terminate | |||
as of the date such litigation is filed. | |||
4. Redistribution. You may reproduce and distribute copies of the | |||
Work or Derivative Works thereof in any medium, with or without | |||
modifications, and in Source or Object form, provided that You | |||
meet the following conditions: | |||
(a) You must give any other recipients of the Work or | |||
Derivative Works a copy of this License; and | |||
(b) You must cause any modified files to carry prominent notices | |||
stating that You changed the files; and | |||
(c) You must retain, in the Source form of any Derivative Works | |||
that You distribute, all copyright, patent, trademark, and | |||
attribution notices from the Source form of the Work, | |||
excluding those notices that do not pertain to any part of | |||
the Derivative Works; and | |||
(d) If the Work includes a "NOTICE" text file as part of its | |||
distribution, then any Derivative Works that You distribute must | |||
include a readable copy of the attribution notices contained | |||
within such NOTICE file, excluding those notices that do not | |||
pertain to any part of the Derivative Works, in at least one | |||
of the following places: within a NOTICE text file distributed | |||
as part of the Derivative Works; within the Source form or | |||
documentation, if provided along with the Derivative Works; or, | |||
within a display generated by the Derivative Works, if and | |||
wherever such third-party notices normally appear. The contents | |||
of the NOTICE file are for informational purposes only and | |||
do not modify the License. You may add Your own attribution | |||
notices within Derivative Works that You distribute, alongside | |||
or as an addendum to the NOTICE text from the Work, provided | |||
that such additional attribution notices cannot be construed | |||
as modifying the License. | |||
You may add Your own copyright statement to Your modifications and | |||
may provide additional or different license terms and conditions | |||
for use, reproduction, or distribution of Your modifications, or | |||
for any such Derivative Works as a whole, provided Your use, | |||
reproduction, and distribution of the Work otherwise complies with | |||
the conditions stated in this License. | |||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||
any Contribution intentionally submitted for inclusion in the Work | |||
by You to the Licensor shall be under the terms and conditions of | |||
this License, without any additional terms or conditions. | |||
Notwithstanding the above, nothing herein shall supersede or modify | |||
the terms of any separate license agreement you may have executed | |||
with Licensor regarding such Contributions. | |||
6. Trademarks. This License does not grant permission to use the trade | |||
names, trademarks, service marks, or product names of the Licensor, | |||
except as required for reasonable and customary use in describing the | |||
origin of the Work and reproducing the content of the NOTICE file. | |||
7. Disclaimer of Warranty. Unless required by applicable law or | |||
agreed to in writing, Licensor provides the Work (and each | |||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||
implied, including, without limitation, any warranties or conditions | |||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||
appropriateness of using or redistributing the Work and assume any | |||
risks associated with Your exercise of permissions under this License. | |||
8. Limitation of Liability. In no event and under no legal theory, | |||
whether in tort (including negligence), contract, or otherwise, | |||
unless required by applicable law (such as deliberate and grossly | |||
negligent acts) or agreed to in writing, shall any Contributor be | |||
liable to You for damages, including any direct, indirect, special, | |||
incidental, or consequential damages of any character arising as a | |||
result of this License or out of the use or inability to use the | |||
Work (including but not limited to damages for loss of goodwill, | |||
work stoppage, computer failure or malfunction, or any and all | |||
other commercial damages or losses), even if such Contributor | |||
has been advised of the possibility of such damages. | |||
9. Accepting Warranty or Additional Liability. While redistributing | |||
the Work or Derivative Works thereof, You may choose to offer, | |||
and charge a fee for, acceptance of support, warranty, indemnity, | |||
or other liability obligations and/or rights consistent with this | |||
License. However, in accepting such obligations, You may act only | |||
on Your own behalf and on Your sole responsibility, not on behalf | |||
of any other Contributor, and only if You agree to indemnify, | |||
defend, and hold each Contributor harmless for any liability | |||
incurred by, or claims asserted against, such Contributor by reason | |||
of your accepting any such warranty or additional liability. | |||
END OF TERMS AND CONDITIONS | |||
APPENDIX: How to apply the Apache License to your work. | |||
To apply the Apache License to your work, attach the following | |||
boilerplate notice, with the fields enclosed by brackets "[]" | |||
replaced with your own identifying information. (Don't include | |||
the brackets!) The text should be enclosed in the appropriate | |||
comment syntax for the file format. We also recommend that a | |||
file or class name and description of purpose be included on the | |||
same "printed page" as the copyright notice for easier | |||
identification within third-party archives. | |||
Copyright [yyyy] [name of copyright owner] | |||
Licensed under the Apache License, Version 2.0 (the "License"); | |||
you may not use this file except in compliance with the License. | |||
You may obtain a copy of the License at | |||
http://www.apache.org/licenses/LICENSE-2.0 | |||
Unless required by applicable law or agreed to in writing, software | |||
distributed under the License is distributed on an "AS IS" BASIS, | |||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
See the License for the specific language governing permissions and | |||
limitations under the License. |
@@ -0,0 +1,80 @@ | |||
{ | |||
"id": "qiun-data-charts", | |||
"displayName": "秋云 ucharts echarts 高性能跨全端图表组件", | |||
"version": "2.1.3-20210513", | |||
"description": "uCharts v2.1上线,新增多个图表类型!全新官方图表组件,支持H5及APP用ECharts渲染图表,uniapp可视化首选组件", | |||
"keywords": [ | |||
"ucharts", | |||
"echarts", | |||
"f2", | |||
"图表", | |||
"可视化" | |||
], | |||
"repository": "https://gitee.com/uCharts/uCharts", | |||
"engines": { | |||
"HBuilderX": "^3.1.0" | |||
}, | |||
"dcloudext": { | |||
"category": [ | |||
"前端组件", | |||
"通用组件" | |||
], | |||
"sale": { | |||
"regular": { | |||
"price": "0.00" | |||
}, | |||
"sourcecode": { | |||
"price": "0.00" | |||
} | |||
}, | |||
"contact": { | |||
"qq": "474119" | |||
}, | |||
"declaration": { | |||
"ads": "无", | |||
"data": "插件不采集任何数据", | |||
"permissions": "无" | |||
}, | |||
"npmurl": "" | |||
}, | |||
"uni_modules": { | |||
"dependencies": [], | |||
"encrypt": [], | |||
"platforms": { | |||
"cloud": { | |||
"tcb": "y", | |||
"aliyun": "y" | |||
}, | |||
"client": { | |||
"App": { | |||
"app-vue": "y", | |||
"app-nvue": "n" | |||
}, | |||
"H5-mobile": { | |||
"Safari": "y", | |||
"Android Browser": "y", | |||
"微信浏览器(Android)": "y", | |||
"QQ浏览器(Android)": "y" | |||
}, | |||
"H5-pc": { | |||
"Chrome": "y", | |||
"IE": "y", | |||
"Edge": "y", | |||
"Firefox": "y", | |||
"Safari": "y" | |||
}, | |||
"小程序": { | |||
"微信": "y", | |||
"阿里": "y", | |||
"百度": "y", | |||
"字节跳动": "y", | |||
"QQ": "y" | |||
}, | |||
"快应用": { | |||
"华为": "y", | |||
"联盟": "y" | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,444 @@ | |||
## [uCharts官方网站](https://www.ucharts.cn) | |||
## [DEMO演示及在线生成工具(v2.0文档)https://demo.ucharts.cn](https://demo.ucharts.cn) | |||
## [图表组件在项目中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
### [v1.0文档(将在9月30日作废,请尽快转v2.0)](http://doc.ucharts.cn) | |||
## [如何安装、更新 uni_modules 插件点这里,必看,必看,必看](https://uniapp.dcloud.io/uni_modules?id=%e4%bd%bf%e7%94%a8-uni_modules-%e6%8f%92%e4%bb%b6) | |||
## 点击右侧绿色【使用HBuilderX导入插件】即可使用,示例项目请点击右侧蓝色按钮【使用HBuilderX导入示例项目】。 | |||
## 初次使用不显示问题详见[常见问题选项卡](https://demo.ucharts.cn) | |||
## <font color=#FF0000> 新手请先完整阅读【帮助文档】及【常见问题】3遍,右侧蓝色按钮【示例项目】请看2遍! </font> | |||
## <font color=#FF0000> 关于NVUE兼容的说明: </font> 建议NVUE使用图表的页面改为vue页面,在App端,从性能来讲,由于通讯阻塞的问题,nvue的canvas性能不可能达到使用renderjs的vue页面的canvas。在App端,推荐使用vue的canvas。[详见uni-app官方说明](https://uniapp.dcloud.io/component/canvas?id=canvas) | |||
[](https://gitee.com/uCharts/uCharts) | |||
## 秋云图表组件使用帮助 | |||
全新图表组件,全端全平台支持,开箱即用,可选择uCharts引擎全端渲染,也可指定PC端或APP端`单独使用ECharts`引擎渲染图表。支持极简单的调用方式,只需指定图表类型及传入符合标准的图表数据即可,使开发者只需专注业务及数据。同时也支持datacom组件读取uniClinetDB,无需关心如何拼接数据等不必要的重复工作,大大缩短开发时间。 | |||
## 为何使用官方封装的组件? | |||
封装组件并不难,谁都会,但组件调试却是一件令人掉头发的事,尤其是canvas封装成组件会带来一系列问题:例如封装后不显示,图表多次初始化导致抖动问题,单页面多个图表点击事件错乱,组件放在scroll-view中无法点击,在图表上滑动时页面无法滚动等等一系列问题。为解决开发者使用可视化组件的困扰,uCharts官方特推出可视化通用组件,本组件具备以下特点: | |||
- 极简单的调用方式,默认配置下只需要传入`图表类型`及`图表数据`即可全端显示。 | |||
- 提供强大的`在线配置生成工具`,可视化中的可视化,鼠标点一点就可以生成图表,可视化从此不再难配。 | |||
- 兼容ECharts,可选择`PC端或APP端单独使用ECharts`引擎渲染图表。 | |||
- H5及App采用`renderjs`渲染图表,动画流畅、性能翻倍。 | |||
- 根据父容器尺寸`弹性显示图表`,再也不必为宽高匹配及多端适配问题发愁。 | |||
- 支持`加载状态loading及error展示`,避免数据读取显示空白的尴尬。 | |||
- chartData`配置与数据解耦`,即便使用ECharts引擎也不必担心拼接option的困扰。 | |||
- localdata`后端数据直接渲染`,无需自行拼接chartData的categories及series,从后端拿回的数据简单处理即可生成图表。 | |||
- 小程序端不必担心包体积过大问题,ECharts引擎将不会编译到各小程序端,u-charts.js编译后`仅为93kb`。 | |||
- 未来将支持通过HbuilderX的[schema2code自动生成全端全平台图表](https://ext.dcloud.net.cn/plugin?id=4684),敬请期待!!! | |||
- uCharts官方拥有3个2000人的QQ群支持,庞大的用户量证明我们一直在努力,本组件将持续更新,请各位放心使用,本组件问题请在`QQ3群`反馈,您的宝贵建议是我们努力的动力!! | |||
## 致开发者 | |||
感谢各位开发者`两年`来对秋云及uCharts的支持,uCharts的进步离不开各位开发者的鼓励与贡献,为更好的帮助各位开发者在uni-app生态系统更好的应用图表,uCharts始终坚持开源,并提供社群帮助开发者解决问题。 为确保您能更好的应用图表组件,建议您先`仔细阅读本页文档`以及uCharts官方文档,而不是下载下来`直接使用`。 如遇到问题请先阅读文档,如仍然不能解决,请加入QQ群咨询,如群友均不能解决或者您有特殊需求,请在群内私聊我,因工作原因,回复不一定很及时,您可直接说问题,有时间一定会回复您。 | |||
uCharts的开源图表组件的开发,付出了大量的个人时间与精力,经过两年来的考验,不会有比较明显的bug,请各位放心使用。不求您5星评价,也不求您赞赏,`只求您对开源贡献的支持态度`,所以,当您想给`1星评价`的时候,秋云真的会`含泪希望您绕路而行……`。如果您有更好的想法,可以在`码云提交Pull Requests`以帮助更多开发者完成需求,再次感谢各位对uCharts的鼓励与支持! | |||
## 快速体验 | |||
一套代码编到7个平台,依次扫描二维码,亲自体验uCharts图表跨平台效果!IOS因demo比较简单无法上架,请自行编译。 | |||
 | |||
## 快速上手 | |||
### <font color=#FF0000> 注意前提条件【版本要求:HBuilderX 3.1.0+】 </font> | |||
- 1、插件市场点击右侧绿色按钮【使用HBuilderX导入插件】,或者【使用HBuilderX导入示例项目】查看完整示例工程 | |||
- 2、依赖uniapp的vue-cli项目:请将uni-modules目录复制到src目录,即src/uni_modules。(请升级uniapp依赖为最新版本) | |||
- 3、页面中直接按下面用法直接调用即可,无需在页面中注册组件qiun-data-charts | |||
- 4、注意父元素class='charts-box'这个样式需要有宽高 | |||
## 基本用法 | |||
- template代码:([建议使用在线工具生成](https://demo.ucharts.cn)) | |||
``` | |||
<view class="charts-box"> | |||
<qiun-data-charts type="column" :chartData="chartData" /> | |||
</view> | |||
``` | |||
- 标准数据格式1:(折线图、柱状图、区域图等需要categories的直角坐标系图表类型) | |||
``` | |||
chartData:{ | |||
categories: ["2016", "2017", "2018", "2019", "2020", "2021"], | |||
series: [{ | |||
name: "目标值", | |||
data: [35, 36, 31, 33, 13, 34] | |||
}, { | |||
name: "完成量", | |||
data: [18, 27, 21, 24, 6, 28] | |||
}] | |||
} | |||
``` | |||
- 标准数据格式2:(饼图、圆环图、漏斗图等不需要categories的图表类型) | |||
``` | |||
chartData:{ | |||
series: [{ | |||
data: [ | |||
{ | |||
name: "一班", | |||
value: 50 | |||
}, { | |||
name: "二班", | |||
value: 30 | |||
}, { | |||
name: "三班", | |||
value: 20 | |||
}, { | |||
name: "四班", | |||
value: 18 | |||
}, { | |||
name: "五班", | |||
value: 8 | |||
} | |||
] | |||
}] | |||
} | |||
``` | |||
注:其他特殊图表类型,请参考mockdata文件夹下的数据格式,v2.0版本的uCharts已兼容ECharts的数据格式,v2.0版本仍然支持v1.0版本的数据格式。 | |||
## localdata数据渲染用法 | |||
- 使用localdata数据格式渲染图表的优势:数据结构简单,无需自行拼接chartData的categories及series,从后端拿回的数据简单处理即可生成图表。 | |||
- localdata数据的缺点:并不是所有的图表类型均可通过localdata渲染图表,例如混合图,组件并不能识别哪个series分组需要渲染成折线还是柱状图,涉及到复杂的图表,仍需要由chartData传入。 | |||
- template代码:([建议使用在线工具生成](https://demo.ucharts.cn)) | |||
``` | |||
<view class="charts-box"> | |||
<qiun-data-charts type="column" :localdata="localdata" /> | |||
</view> | |||
``` | |||
- 标准数据格式1:(折线图、柱状图、区域图等需要categories的直角坐标系图表类型) | |||
其中value代表数据的数值,text代表X轴的categories数据点,group代表series分组的类型名称即series[i].name。 | |||
``` | |||
localdata:[ | |||
{value:35, text:"2016", group:"目标值"}, | |||
{value:18, text:"2016", group:"完成量"}, | |||
{value:36, text:"2017", group:"目标值"}, | |||
{value:27, text:"2017", group:"完成量"}, | |||
{value:31, text:"2018", group:"目标值"}, | |||
{value:21, text:"2018", group:"完成量"}, | |||
{value:33, text:"2019", group:"目标值"}, | |||
{value:24, text:"2019", group:"完成量"}, | |||
{value:13, text:"2020", group:"目标值"}, | |||
{value:6, text:"2020", group:"完成量"}, | |||
{value:34, text:"2021", group:"目标值"}, | |||
{value:28, text:"2021", group:"完成量"} | |||
] | |||
``` | |||
- 标准数据格式2:(饼图、圆环图、漏斗图等不需要categories的图表类型) | |||
其中value代表数据的数值,text代表value数值对应的描述。 | |||
``` | |||
localdata:[ | |||
{value:50, text:"一班"}, | |||
{value:30, text:"二班"}, | |||
{value:20, text:"三班"}, | |||
{value:18, text:"四班"}, | |||
{value:8, text:"五班"}, | |||
] | |||
``` | |||
- 注意,localdata的数据格式必需要符合datacom组件规范[【详见datacom组件】](https://uniapp.dcloud.io/component/datacom?id=mixindatacom)。 | |||
## 进阶用法读取uniCloud数据库并渲染图表 | |||
- 组件基于uniCloud的[clientDB](https://uniapp.dcloud.net.cn/uniCloud/clientdb)技术,无需云函数,在前端对数据库通过where查询条件及group和count统计即可渲染图表。 | |||
- 具体可参考/pages/unicloud/unicloud.vue中的demo例子,使用前,请先关联云服务空间,然后在uniCloud/database/db_init.json文件上点右键,初始化云数据库,当控制台显示“初始化云数据库完成”即完成示例数据的导入,之后方可运行uniCloud的demo。 | |||
- template代码: | |||
``` | |||
<qiun-data-charts | |||
type="line" | |||
:chartData="demoData" | |||
collection="uni-id-users" | |||
field="register_date,status" | |||
:where="'publish_date >= ' + new Date(startDate).getTime() + ' && publish_date <= ' + new Date(endDate).getTime()" | |||
groupby="dateToString(add(new Date(0),register_date),'%Y-%m-%d','+0800') as text,status as group" | |||
group-field="count(*) as value" | |||
/> | |||
``` | |||
- 注意,从uniCloud读取出的数据,需要符合localdata的标准结果数据格式(参考上部分localdata),并需要把输出的字段as成规定的别名(value、text、group)。 | |||
## 示例文件地址: | |||
### <font color=#FF0000> 强烈建议先看本页帮助,再看下面示例文件源码!</font> | |||
``` | |||
/pages/ucharts/ucharts.vue(展示用uCharts全端运行的例子) | |||
/pages/echarts/echarts.vue(展示H5和App用ECharts,小程序端用uCharts的例子) | |||
/pages/unicloud/unicloud.vue(展示读取uniCloud数据库后直接渲染图表的例子) | |||
/pages/updata/updata.vue(展示动态更新图表数据的例子) | |||
/pages/other/other.vue(展示图表交互的例子:动态更新图表数据,渲染完成事件,获取点击索引,自定义tooltip,图表保存为图片,强制展示错误信息等) | |||
/pages/format-u/format-u.vue(展示uCharts的formatter用法的例子) | |||
/pages/format-e/format-e.vue(展示ECharts的formatter用法的例子) | |||
/pages/tab/tab.vue(展示再tab选项卡中用法的例子,即父容器采用v-show或v-if时需要注意的问题) | |||
/pages/layout/layout.vue(展示特殊布局用法的例子:swiper、scroll-view、绝对定位等布局) | |||
/pages/canvas/canvas.vue(展示uCharts v2.0版本原生js用法的例子) | |||
``` | |||
## 组件基本API参数 | |||
|属性名|类型|默认值|必填|说明| | |||
| -- | -- | -- | -- | -- | | |||
|type|String|null|`是`|图表类型,如全端用uCharts,可选值为pie、ring、rose、word、funnel、map、arcbar、line、column、area、radar、gauge、candle、mix <font color=#FF0000>(您也可以根据需求自定义新图表类型,需要在config-ucharts.js或config-echarts.js内添加,可参考config-ucharts.js内的"demotype"类型)</font>| | |||
|chartData|Object|见说明|`是`|图表数据,常用的标准数据格式为{categories: [],series: []},请按不同图表类型传入对应的标准数据。| | |||
|localdata|Array|[]|`是`|图表数据,如果您觉得拼接上面chartData比较繁琐,可以通过使用localdata渲染,组件会根据传入的type类型,自动拼接categories或series数据(使用localdata就不必再传入chartData,详见 /pages/other/other.vue 中使用localdata渲染图表的例子)。【localdata和collection(uniCloud数据库)同时存在,优先使用localdata;如果localdata和chartData同时存在,优先使用chartData。<font color=#FF0000> 即chartData>localdata>collection的优先级</font>渲染图表】。| | |||
|opts|Object|{}|否|uCharts图表配置参数(option),请参考[【在线生成工具】](https://demo.ucharts.cn)<font color=#FF0000>注:传入的opts会覆盖默认config-ucharts.js中的配置,只需传入与config-ucharts.js中属性不一致的opts即可实现【同类型的图表显示不同的样式】。</font>| | |||
|eopts|Object|{}|否|ECharts图表配置参数(option),请参考[【ECharts配置手册】](https://echarts.apache.org/zh/option.html)传入eopts。<font color=#FF0000>注:1、传入的eopts会覆盖默认config-echarts.js中的配置,以实现同类型的图表显示不同的样式。2、eopts不能传递function,如果option配置参数需要function,请将option写在config-echarts.js中即可实现。</font>| | |||
|loadingType|Number|2|否|加载动画样式,0为不显示加载动画,1-5为不同的样式,见下面示例。| | |||
|errorShow|Boolean|true|否|是否在页面上显示错误提示,true为显示错误提示图片,false时会显示空白区域| | |||
|errorReload|Boolean|true|否|是否启用点击错误提示图表重新加载,true为允许点击重新加载,false为禁用点击重新加载事件| | |||
|errorMessage|String|null|否|自定义错误信息,强制显示错误图片及错误信息,当上面errorShow为true时可用。(组件会监听该属性的变化,只要有变化,就会强制显示错误信息!)。说明:1、一般用于页面网络不好或其他情况导致图表loading动画一直显示,可以传任意(不为null或者"null"或者空"")字符串强制显示错误图片及说明。2、如果组件使用了data-come属性读取uniCloud数据,组件会自动判断错误状态并展示错误图标,不必使用此功能。3、当状态从非null改变为null或者空时,会强制调用reload重新加载并渲染图表数据。| | |||
|echartsH5|Boolean|false|否|是否在H5端使用ECharts引擎渲染图表| | |||
|directory|String|'/'|否|二级目录名称,如果开启上面echartsH5即H5端用ECharts引擎渲染图表,并且项目未发布在website根目录,需要填写此项配置。例如二级目录是h5,则需要填写`/h5/`,左右两侧需要带`/`,发布到三级或更多层目录示例`/web/v2/h5/`| | |||
|echartsApp|Boolean|false|否|是否在APP端使用ECharts引擎渲染图表| | |||
|canvasId|String|见说明|否|默认生成32位随机字符串。如果指定canvasId,可方便后面调用指定图表实例,否则需要通过渲染完成事件获取自动生成随机的canvasId| | |||
|canvas2d|Boolean|false|否|是否开启canvas2d模式,用于解决微信小程序层级过高问题,仅微信小程序端可用,其他端会强制关闭canvas2d模式。<font color=#FF0000>注:开启canvas2d模式,必须要传入上面的canvasId(随机字符串,不能是动态绑定的值,不能是数字),否则微信小程序可能会获取不到dom导致无法渲染图表!**开启后,开发者工具显示不正常,预览正常(不能“真机调试”,不能“真机调试”,不能“真机调试”)**</font>| | |||
|background|String|none|否|背景颜色,默认透明none,可选css的16进制color值,如#FFFFFF| | |||
|animation|Boolean|true|否|是否开启图表动画效果| | |||
|inScrollView|Boolean|false|否|图表组件是否在scroll-view中,如果在请传true,否则会出现点击事件坐标不准确的现象| | |||
|pageScrollTop|Number|0|否|如果图表组件是在scroll-view中,并且整个页面还存在滚动条,这个值应为绑定为页面滚动条滚动的距离,否则会出现点击事件坐标不准确的现象| | |||
|reshow|Boolean|false|否|强制重新渲染属性,如果图表组件父级用v-show包裹,初始化的时候会获取不到元素的宽高值,导致渲染失败,此时需要把父元素的v-show方法复制到reshow中,组件检测到reshow值变化并且为true的时候会强制重新渲染| | |||
|reload|Boolean|false|否|强制重新加载属性,与上面的reshow区别在于:1、reload会重新显示loading动画;2、如果组件绑定了uniCloud数据查询,通过reload会重新执行SQL语句查询,重新请求网络。而reshow则不会显示loading动画,只是应用现有的chartData数据进行重新渲染| | |||
|disableScroll|Boolean|false|否|当在canvas中移动时,且有绑定手势事件时,禁止屏幕滚动以及下拉刷新(赋值为true时,在图表区域内无法拖动页面滚动)| | |||
|tooltipShow|Boolean|true|否|点击或者鼠标经过图表时,是否显示tooltip提示窗,默认显示| | |||
|tooltipFormat|String|undefined|否|自定义格式化Tooltip显示内容,详见下面【tooltipFormat格式化】| | |||
|tooltipCustom|Object|undefined|否|(仅uCharts)如果以上系统自带的Tooltip格式化方案仍然不满足您,您可以用此属性实现更多需求,详见下面【tooltipCustom自定义】| | |||
|startDate|String|undefined|否|需为标准时间格式,例如"2021-02-14"。用于配合uniClinetDB自动生成categories使用| | |||
|endDate|String|undefined|否|需为标准时间格式,例如"2021-03-31"。用于配合uniClinetDB自动生成categories使用| | |||
|groupEnum|Array|[]|否|当使用到uniCloud数据库时,group字段属性如果遇到统计枚举属性的字段,需要通过将DB Schema中的enum的描述定义指派给该属性,具体格式为[{value: 1,text: "男"},{value: 2,text: "女"}]| | |||
|textEnum|Array|[]|否|当使用到uniCloud数据库时,text字段属性如果遇到统计枚举属性的字段,需要通过将DB Schema中的enum的描述定义指派给该属性,具体格式为[{value: 1,text: "男"},{value: 2,text: "女"}]| | |||
|ontap|Boolean|true|否|是否监听@tap@cilck事件,禁用后不会触发组件点击事件| | |||
|ontouch|Boolean|false|否|(仅uCharts)是否监听@touchstart@touchmove@touchend事件(赋值为true时,非PC端在图表区域内无法拖动页面滚动)| | |||
|onmouse|Boolean|true|否|是否监听@mousedown@mousemove@mouseup事件,禁用后鼠标经过图表上方不会显示tooltip| | |||
|`onmovetip`|Boolean|false|否|(仅uCharts)是否开启跟手显示tooltip功能(前提条件,1、需要开启touch功能,即:ontouch="true";2、并且opts.enableScroll=false即关闭图表的滚动条功能)(建议微信小程序开启canvas2d功能,否则原生canvas组件会很卡)| | |||
## 组件事件及方法 | |||
|事件名|说明| | |||
| --| --| | |||
|@complete|图表渲染完成事件,渲染完成会返回图表实例{complete: true, id:"xxxxx"(canvasId), type:"complete"}。可以引入config-ucharts.js/config-echarts.js来根据返回的id,调用uCharts或者ECharts实例的相关方法,详见other.vue其他图表高级应用。| | |||
|@getIndex|获取点击数据索引,点击后返回图表索引currentIndex,图例索引(仅uCharts)legendIndex,等信息。返回数据:{type: "getIndex", currentIndex: 3, legendIndex: -1, id:"xxxxx"(canvasId), event: {x: 100, y: 100}(点击坐标值)}| | |||
|@error|当组件发生错误时会触发该事件。返回数据:返回数据:{type:"error",errorShow:true/false(组件props中的errorShow状态值) , msg:"错误消息xxxx", id: "xxxxx"(canvasId)}| | |||
|@getTouchStart|(仅uCharts)拖动开始监听事件。返回数据:{type:"touchStart",event:{x: 100, y: 100}(点击坐标值),id:"xxxxx"(canvasId)}| | |||
|@getTouchMove|(仅uCharts)拖动中监听事件。返回数据:{type:"touchMove",event:{x: 100, y: 100}(点击坐标值),id:"xxxxx"(canvasId)}| | |||
|@getTouchEnd|(仅uCharts)拖动结束监听事件。返回数据:{type:"touchEnd",event:{x: 100, y: 100}(点击坐标值),id:"xxxxx"(canvasId)}| | |||
|@scrollLeft|(仅uCharts)开启滚动条后,滚动条到最左侧触发的事件,用于动态打点,需要自行编写防抖方法。返回数据:{type:"scrollLeft", scrollLeft: true, id: "xxxxx"(canvasId)}| | |||
|@scrollRight|(仅uCharts)开启滚动条后,滚动条到最右侧触发的事件,用于动态打点,需要自行编写防抖方法。返回数据:返回数据:{type:"scrollRight", scrollRight: true, id: "xxxxx"(canvasId)}| | |||
## tooltipFormat格式化(uCharts和ECharts) | |||
tooltipFormat类型为string字符串类型,需要指定config-ucharts.js/config-echarts.js中formatter下的属性值。因各小程序及app端通过组件均不能传递function类型参数,因此请先在config-ucharts.js/config-echarts.js内定义您想格式化的数据,然后在这里传入formatter下的key值,组件会自动匹配与其对应的function。如不定义该属性,组件会调用默认的tooltip方案,标准的tooltipFormat使用姿势如下: | |||
``` | |||
<qiun-data-charts | |||
type="column" | |||
:chartData="chartData" | |||
tooltipFormat="tooltipDemo1" | |||
⁄> | |||
================== | |||
config-ucharts.js | |||
formatter:{ | |||
tooltipDemo1:function(item, category, index, opts){return item.data+'天'} | |||
} | |||
================== | |||
config-echarts.js | |||
formatter:{ | |||
tooltipDemo1:function(){ | |||
} | |||
} | |||
``` | |||
注意,config-ucharts.js内的formatter下的function需要携带(item, category, index, opts)参数,这4个参数都是uCharts实例内传递过来的数据,具体定义如下: | |||
|属性名|说明| | |||
| -- | -- | | |||
|item|组件内计算好的当前点位的series[index]数据,其属性有data(继承series[index].format属性),color,type,style,pointShape,disableLegend,name,show| | |||
|category|当前点位的X轴categories[index]分类名称(如果图表类型没有category,其值则为undefined)| | |||
|index|当前点位的索引值| | |||
|opts|全部uCharts的opts配置,包含categories、series等一切你需要的都在里面,可以根据index索引值获取其他相关数据。您可以在渲染完成后打印一下opts,看看里面都有什么,也可以自定义一些你需要的挂载到opts上,这样就可以根据需求更方便的显示自定义内容了。| | |||
## tooltipCustom自定义(仅uCharts) | |||
上面仅仅展示了Tooltip的自定义格式化,如果仍然仍然还不能还不能满足您的需求,只能看这里的方法了。tooltipCustom可以自定义在任何位置显示任何内容的文本,当然tooltipCustom可以和tooltipFormat格式化同时使用以达到更多不同的需求,下面展示了tooltip固定位置显示的方法: | |||
``` | |||
<qiun-data-charts | |||
type="column" | |||
:chartData="chartData" | |||
:tooltipCustom="{x:10,y:10}" | |||
/> | |||
``` | |||
tooltipCustom属性如下: | |||
|属性名|类型|默认值|说明| | |||
| -- | -- | -- | -- | | |||
|x|Number|undefined|tooltip左上角相对于画布的X坐标| | |||
|y|Number|undefined|tooltip左上角相对于画布的Y坐标| | |||
|index|Number|undefined|相对于series或者categories中的索引值。当没有定义index或者index定义为undefined的时候,组件会自动获取当前点击的索引,并根据上面的xy位置绘制tooltip提示框。如果为0及以上的数字时,会根据您传的索引自动计算x轴方向的偏移量(仅直角坐标系有效)| | |||
|textList|Array.Object|undefined|多对象数组,tooltip的文字组。当没有定义textList或者textList定义为undefined的时候,会调自动获取点击索引并拼接相应的textList。如传递[{text:'默认显示的tooltip',color:null},{text:'类别1:某个值xxx',color:'#2fc25b'},{text:'类别2:某个值xxx',color:'#facc14'},{text:'类别3:某个值xxx',color:'#f04864'}]这样定义好的数组,则会只显示该数组。| | |||
|textList[i].text|String| |显示的文字| | |||
|textList[i].color|Color| |左侧图表颜色| | |||
## datacome属性及说明 | |||
- 通过配置datacome属性,可直接获取uniCloud云数据,并快速自动生成图表,使开发者只需专注业务及数据,无需关心如何拼接数据等不必要的重复工作,大大缩短开发时间。datacome属性及说明,详见[datacom组件规范](https://uniapp.dcloud.io/component/datacom?id=mixindatacom) | |||
|属性名|类型|默认值|说明| | |||
| -- | -- | -- | -- | | |||
|collection|String| |表名。支持输入多个表名,用 , 分割| | |||
|field|String| |查询字段,多个字段用 , 分割| | |||
|where|String| |查询条件,内容较多,另见jql文档:[详情](https://uniapp.dcloud.net.cn/uniCloud/uni-clientDB?id=jsquery)| | |||
|orderby|String| |排序字段及正序倒叙设置| | |||
|groupby|String| |对数据进行分组| | |||
|group-field|String| |对数据进行分组统计| | |||
|distinct|Boolean|false|是否对数据查询结果中重复的记录进行去重| | |||
|action|string| |云端执行数据库查询的前或后,触发某个action函数操作,进行预处理或后处理,详情。场景:前端无权操作的数据,比如阅读数+1| | |||
|page-data|string|add|分页策略选择。值为 add 代表下一页的数据追加到之前的数据中,常用于滚动到底加载下一页;值为 replace 时则替换当前data数据,常用于PC式交互,列表底部有页码分页按钮| | |||
|page-current|Number|0|当前页| | |||
|page-size|Number|0|每页数据数量| | |||
|getcount|Boolean|false|是否查询总数据条数,默认 false,需要分页模式时指定为 true| | |||
|getone|Boolean|false|指定查询结果是否仅返回数组第一条数据,默认 false。在false情况下返回的是数组,即便只有一条结果,也需要[0]的方式获取。在值为 true 时,直接返回结果数据,少一层数组。一般用于非列表页,比如详情页| | |||
|gettree|Boolean|false|是否查询树状数据,默认 false| | |||
|startwith|String|''|gettree的第一层级条件,此初始条件可以省略,不传startWith时默认从最顶级开始查询| | |||
|limitlevel|Number|10|gettree查询返回的树的最大层级。超过设定层级的节点不会返回。默认10级,最大15,最小1| | |||
## uni_modules目录说明 | |||
``` | |||
├── components | |||
│ └── qiun-data-chatrs──────────# 组件主入口模块 | |||
│ └── qiun-error────────────────# 加载动画组件文件目录(可以修改错误提示图标以减少包体积) | |||
│ └── qiun-loading──────────────# 加载动画组件文件目录(可以删除您不需要的动画效果以减少包体积) | |||
├── js_skd | |||
│ └── u-charts | |||
│ ── └──config-echarts.js ──────# ECharts默认配置文件(非APP端内可作为实例公用中转) | |||
│ ── └──config-ucharts.js ──────# uCharts默认配置文件(非APP端内可作为实例公用中转) | |||
│ ── └──u-charts-v2.0.0.js──────# uCharts基础库v2.0.0版本,部分API与之前版本不同 | |||
├── static | |||
│ └── app-plus──────────────────# 条件编译目录,仅编译到APP端 | |||
│ ── └──echarts.min.js──────────# Echarts基础库v4.2.1 | |||
│ └── h5────────────────────────# 条件编译目录,仅编译到H5端 | |||
│ ── └──echarts.min.js──────────# Echarts基础库v4.2.1 | |||
``` | |||
## 加载动画及错误提示 | |||
- 为保证编译后的包体积,加载动画引用作者wkiwi提供的[w-loading](https://ext.dcloud.net.cn/plugin?id=504)中选取5种,如需其他样式请看下面说明。 | |||
- loading的展示逻辑: | |||
* 1、如果是uniCloud数据,从发送网络请求到返回数据期间展示。 | |||
* 2、如果是自行传入的chartData,当chartData.series=[]空数组的时候展示loading,也就是说初始化图表的时候,如果您没有数据,可以通过先传个空数组来展示loading效果,当chartData.series有数据后会自动隐藏loading图标。 | |||
- <font color=#FF0000>如您修改了qiun-data-charts.vue组件文件,请务必在升级前备份您的文件,以免被覆盖!!!建议将加载状态显示做成组件,避免下次升级时丢失后无法找到。</font> | |||
## 配置文件说明 | |||
- <font color=#FF0000>注意,config-echarts.js和config-ucharts.js内只需要配置符合您项目整体UI的整体默认配置,根据需求,先用[【在线工具】](http://demo.ucharts.cn)调试好默认配置,并粘贴到配置文件中。</font> | |||
- <font color=#FF0000>如果需要与configjs中不同的配置,只需要在组件上绑定:opts或者:eopts传入与默认配置不同的某个属性及值即可覆盖默认配置,极大降低了代码量。</font> | |||
- ECharts默认配置文件:config-echarts.js | |||
i、<font color=#FF0000>如您修改了默认配置文件,请务必在升级前备份您的配置文件,以免被覆盖!!!</font> | |||
ii、ECharts配置手册:[https://echarts.apache.org/zh/option.html](https://echarts.apache.org/zh/option.html) | |||
iii、"type"及"categories"属性为支持的图表属性,您可参照ECharts配置手册,配置您更多的图表类型,并将对应的图表配置添加至下面 | |||
iv、"formatter"属性,因各小程序及app端通过组件均不能传递function类型参数,因此请先在此属性下定义您想格式化的数据,组件会自动匹配与其对应的function | |||
v、"seriesTemplate"属性,因ECharts的大部分配置均在series内,seriesTemplate作为series的模板,这样只需要在这里做好模板配置,组件的数组层chartData(或者localdata或者collection)的series会自动挂载模板配置。如需临时或动态改变seriesTemplate,可在:eopts中传递seriesTemplate,详见pages/echarts/echarts.vue中的曲线图。 | |||
vi、ECharts配置仅可用于H5或者APP端,并且配置`echartsH5`或`echartsApp`为`true`时可用 | |||
- uCharts默认配置文件:config-ucharts.js | |||
i、<font color=#FF0000>如您修改了默认配置文件,请务必在升级前备份您的配置文件,以免被覆盖!!!</font> | |||
ii、v2版本后的uCharts基础库不提供配置手册,您可以使用在线配置生成工具来快速生成配置:[http://demo.ucharts.cn](http://demo.ucharts.cn) | |||
iii、"type"及"categories"属性为支持的图表属性,不支持添加uCharts基础库没有的图表类型 | |||
iv、"formatter"属性因各小程序及app端通过组件均不能传递function类型参数,因此请先在此属性下定义您想格式化的数据,组件会自动匹配与其对应的function | |||
v、uCharts配置可跨全端使用 | |||
## 常见问题及注意事项 | |||
- `图表无法显示问题`: | |||
* 请先检查您的HBuilderX版本,要求高于3.1.0+。 | |||
* 1、如果是首次导入插件不显示,或者报以下未注册`qiun-data-charts`的错误: | |||
> Unknown custom element: < qiun-data-charts > - did you register the component correctly? For recursive components, make sure to provide the "name" option. | |||
* 2、<font color=#FF0000>请【重启HBuilderX】或者【重启项目】或者【重启开发者工具】或者【删除APP基座】重新运行,避免缓存问题导致不能显示。</font> | |||
* 3、如果是基于uniapp的vue-cli项目,1、请 npm update 升级uniapp依赖为最新版本;2、请尝试清理node-modules,重新install,还不行就删除项目,再重新install。如果仍然不行,请检查uniapp依赖是否为最新版本,再重试以上步骤。如果仍然不行,请使用<font color=#FF0000>【非uni_modules版本】</font>组件,最新非uni_modules版本在码云发布,[点击此处获取](https://gitee.com/uCharts/uCharts/tree/master/qiun-data-charts%EF%BC%88%E9%9D%9Euni-modules%EF%BC%89)。。 | |||
* 4、请检查控制台是否有报错或提示信息,如果没有报错,也没有提示信息,并且检查视图中class="charts-box"这个元素的宽高均为0,请修改父元素的css样式或进行下面第4步检查。 | |||
* 5、检查父级是否使用了v-show来控制显示。如果页面初始化时组件处于隐藏状态,组件则无法正确获取宽高尺寸,此时<font color=#FF0000>需要组件内绑定reshow属性(逻辑应与父级的v-show的逻辑相同)</font>,强制重新渲染图表,例如:reshow="父级v-show绑定的事件"。 | |||
* 6、如果在微信小程序端开启了canvas2d模式<font color=#FF0000>(不能使用真机调试,请直接预览)</font>不显示图表: | |||
* a、请务必在组件上定义canvasId,不能为纯数字、不能为变量、不能重复、尽量长一些。 | |||
* b、请检查微信小程序的基础库,修改至2.16.0或者最新版本的基础库。 | |||
* c、请检查父元素或父组件是否用v-if来控制显示,如有请改为v-show,并将v-show的逻辑绑定至组件。 | |||
- `formatter格式化问题`:无论是uCharts还是ECharts,因为组件不能传递function,所有的formatter均需要变成别名format来定义,并在config-ucharts.js或config-echarts.js配置对应的formatter方法,组件会根据format的值自动替换配置文件中的formatter方法。(参考示例项目pages/format/format.vue) | |||
- `图表抖动问题`:如果开启了animation动画效果,由于组件内开启了chartData和opts的监听,当数据变化时会重新渲染图表,<font color=#FF0000>建议整体改变chartData及opts的属性值</font>,而不要通过循环或遍历来改变this实例下的chartData及opts,例如先定义一个临时变量,拼接好数据后再整体赋值。(参考示例项目pages/updata/updata.vue) | |||
- `微信小程序报错Maximum call stack size exceeded问题`:由于组件内开启了chartData和opts的监听,当数据变化时会重新渲染图表,<font color=#FF0000>建议整体改变chartData及opts的属性值</font>,而不要通过循环或遍历来改变this实例下的chartData及opts,例如先定义一个临时变量,拼接好数据后再整体赋值。(参考示例项目pages/updata/updata.vue) | |||
- `Loading状态问题`:如不使用uniClinetDB获取数据源,并且需要展示Loading状态,请先清空series,使组件变更为Loading状态,即this.chartData.series=[]即可展示,然后再从服务端获取数据,拼接完成后再传入this.chartData。如果不需要展示Loading状态,则不需要以上步骤,获取到数据,拼接好标准格式后,直接赋值即可。 | |||
- `微信小程序图表层级过高问题`:因canvas在微信小程序是原生组件,如果使用自定义tabbar或者自定义导航栏,图表则会超出预期,此时需要给组件的canvas2d传值true来使用type='2d'的功能,开启此模式后,<font color=#FF0000>一定要在组件上自定义canvasId,不能为数字,不能动态绑定,要为随机字符串!不能“真机调试”,不能“真机调试”,不能“真机调试”</font>开发者工具显示不正常,图表层级会变高,而正常预览或者发布上线则是正常状态,开发者不必担心,一切以真机预览为准(因微信开发者工具显示不正确,canvas2d这种模式下给调试带来了困难,开发时,可以先用:canvas2d="false"来调试,预览无误后再改成true)。 | |||
- `MiniPorgramError U.createEvent is ot a function`:此问题一般是微信小程序开启了canvas2d,并点击了“真机调试导致”,参考上面【微信小程序图表层级过高问题】解决办法,开启2d后,不可以真机调试,只能开发者工具调试或者扫二维码“预览”。 | |||
- `在图表上滑动无法使页面滚动问题`:此问题是因为监听了touchstart、touchmove和touchend三个事件,或者开启了disableScroll属性,如果您的图表不需要开启图表内的滚动条功能,请禁用这三个方法的监听,即:ontouch="false"或者:disableScroll="false"即可(此时图表组件默认通过@tap事件来监听点击,可正常显示Tooltip提示窗)。 | |||
- `开启滚动条无法拖动图表问题`:此问题正与以上问题相反,是因为禁用了监听touchstart、touchmove和touchend三个事件,请启用这三个方法的监听,即在组件上加入 :ontouch="true" 即可。注意,不要忘记在opts里需要配置enableScroll:true,另外如果需要显示滚动条,需要在xAxis中配置scrollShow:ture。 | |||
- `开启滚动条后图表两侧有白边问题`:此问题是因为组件上的background为none或者没有指定,请在组件上加入background="#000000"(您的背景色)。如果父元素为图片,尽量不要开启滚动条,此时图表是透明色,可以显示父元素背景图片。 | |||
- `地图变形问题`:此问题是因为您引用的geojson地图数据的坐标系可能是地球坐标(WGS84)导致,需要开启【是否进行WGS84转墨卡托投影】功能。开启后因大量的数据运算tooltip可能会不跟手,建议自行转换为墨卡托坐标系,可参照源码内function lonlat2mercator()。其他地图数据下载地址:[http://datav.aliyun.com/tools/atlas/](http://datav.aliyun.com/tools/atlas/) | |||
- `支付宝(钉钉)小程序无法点击问题`:请检查支付宝小程序开发者工具中,点击【详情】,在弹出的【项目详情】中【取消】启用小程序基础库 2.0 构建,一定不要勾选此项。 | |||
- `uni-simple-router中使用问题`:如果使用uni-simple-router路由插件,H5开启完全路由模式(即h5:{vueRouterDev:true})时,会导致组件内uni.xxx部分方法失效,引发节点获取不正常报错,请使用普通模式即可。 | |||
## [更多常见问题以官方网站【常见问题】为准](http://demo.ucharts.cn) | |||
## QQ群号码 | |||
## <font color=#FF0000> 请先完整阅读【帮助文档】及【常见问题】3遍,右侧蓝色按钮【示例项目】请看2遍!不看文档不看常见问题进群就问的拒绝回答问题!咨询量太大请理解作者! </font> | |||
- 放在下面是为了让您先看文档,看好群分类,再进群!! | |||
- 交流群1:371774600(已满) | |||
- 交流群2:619841586(不回答本组件问题,只回答uCharts基础库问题) | |||
- 交流群3:955340127<font color=#FF0000>(优先解答本组件问题,其他问题群友互助)</font> | |||
- 口令`uniapp` | |||
## 相关链接 | |||
- [DCloud插件市场地址](https://ext.dcloud.net.cn/plugin?id=271) | |||
- [uCharts官网](https://www.ucharts.cn) | |||
- [uCharts在线生成工具](http://demo.ucharts.cn)<font color=#FF0000>(注:v2.0版本后将不提供配置手册,请通过在线生成工具生成图表配置)</font> | |||
- [uCharts码云开源托管地址](https://gitee.com/uCharts/uCharts) [](https://gitee.com/uCharts/uCharts/stargazers) | |||
- [uCharts基础库更新记录](https://gitee.com/uCharts/uCharts/wikis/%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95?sort_id=1535998) | |||
- [uCharts改造教程](https://gitee.com/uCharts/uCharts/wikis/%E6%94%B9%E9%80%A0uCharts%E6%89%93%E9%80%A0%E4%B8%93%E5%B1%9E%E5%9B%BE%E8%A1%A8?sort_id=1535997) | |||
- [图表组件在项目中的应用 UReport数据报表](https://ext.dcloud.net.cn/plugin?id=4651) | |||
- [ECharts官网](https://echarts.apache.org/zh/index.html) | |||
- [ECharts配置手册](https://echarts.apache.org/zh/option.html) | |||
- [`wkiwi`提供的w-loading组件地址](https://ext.dcloud.net.cn/plugin?id=504) |
@@ -0,0 +1,234 @@ | |||
// import Strophe from "../sdk/libs/strophe"; | |||
//import xmldom from "../sdk/libs/xmldom/dom-parser"; | |||
// import websdk from "../sdk/sdk/src/connection"; | |||
import websdk from "../sdk/webimSDK3.1.1.js"; | |||
import config from "./WebIMConfig"; | |||
console.group = console.group || {}; | |||
console.groupEnd = console.groupEnd || {}; | |||
var window = {}; | |||
let WebIM = window.WebIM = websdk; | |||
window.WebIM.config = config; //var DOMParser = window.DOMParser = xmldom.DOMParser; | |||
//let document = window.document = new DOMParser().parseFromString("<?xml version='1.0'?>\n", "text/xml"); | |||
WebIM.isDebug = function (option) { | |||
if (option) { | |||
WebIM.config.isDebug = option.isDebug; | |||
openDebug(WebIM.config.isDebug); | |||
} | |||
function openDebug(value) { | |||
function ts() { | |||
var d = new Date(); | |||
var Hours = d.getHours(); // 获取当前小时数(0-23) | |||
var Minutes = d.getMinutes(); // 获取当前分钟数(0-59) | |||
var Seconds = d.getSeconds(); // 获取当前秒数(0-59) | |||
return (Hours < 10 ? "0" + Hours : Hours) + ":" + (Minutes < 10 ? "0" + Minutes : Minutes) + ":" + (Seconds < 10 ? "0" + Seconds : Seconds) + " "; | |||
} // if (value) { | |||
// Strophe.Strophe.Connection.prototype.rawOutput = function(data){ | |||
// try{ | |||
// console.group("%csend # " + ts(), "color: blue; font-size: large"); | |||
// console.log("%c" + data, "color: blue"); | |||
// console.groupEnd(); | |||
// } | |||
// catch(e){ | |||
// console.log(e); | |||
// } | |||
// }; | |||
// }else{ | |||
// Strophe.Strophe.Connection.prototype.rawOutput = function(){}; | |||
// } | |||
} | |||
}; | |||
/** | |||
* Set autoSignIn as true (autoSignInName and autoSignInPwd are configured below), | |||
* You can auto signed in each time when you refresh the page in dev model. | |||
*/ | |||
WebIM.config.autoSignIn = false; | |||
if (WebIM.config.autoSignIn) { | |||
WebIM.config.autoSignInName = "lwz2"; | |||
WebIM.config.autoSignInPwd = "1"; | |||
} // var stropheConn = new window.Strophe.Connection("ws://im-api.easemob.com/ws/", { | |||
// inactivity: 30, | |||
// maxRetries: 5, | |||
// pollingTime: 4500 | |||
// }); | |||
// | |||
// stropheConn.connect( | |||
// '$t$' + 'YWMtmbQEBKKIEeaGmMtXyg5n1wAAAVlkQvGO2WOJGlMCEJKM4VV9GCMnb_XLCXU', | |||
// function() { | |||
// console.log(arguments, 'ggogogo'); | |||
// }, stropheConn.wait, stropheConn.hold); | |||
WebIM.parseEmoji = function (msg) { | |||
if (typeof WebIM.Emoji === "undefined" || typeof WebIM.Emoji.map === "undefined") { | |||
return msg; | |||
} | |||
var emoji = WebIM.Emoji, | |||
reg = null; | |||
var msgList = []; | |||
var objList = []; | |||
for (var face in emoji.map) { | |||
if (emoji.map.hasOwnProperty(face)) { | |||
while (msg.indexOf(face) > -1) { | |||
msg = msg.replace(face, "^" + emoji.map[face] + "^"); | |||
} | |||
} | |||
} | |||
var ary = msg.split("^"); | |||
var reg = /^e.*g$/; | |||
for (var i = 0; i < ary.length; i++) { | |||
if (ary[i] != "") { | |||
msgList.push(ary[i]); | |||
} | |||
} | |||
for (var i = 0; i < msgList.length; i++) { | |||
if (reg.test(msgList[i])) { | |||
var obj = {}; | |||
obj.data = msgList[i]; | |||
obj.type = "emoji"; | |||
objList.push(obj); | |||
} else { | |||
var obj = {}; | |||
obj.data = msgList[i]; | |||
obj.type = "txt"; | |||
objList.push(obj); | |||
} | |||
} | |||
return objList; | |||
}; | |||
WebIM.time = function () { | |||
var date = new Date(); | |||
var Hours = date.getHours(); | |||
var Minutes = date.getMinutes(); | |||
var Seconds = date.getSeconds(); | |||
var time = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + (Hours < 10 ? "0" + Hours : Hours) + ":" + (Minutes < 10 ? "0" + Minutes : Minutes) + ":" + (Seconds < 10 ? "0" + Seconds : Seconds); | |||
return time; | |||
}; | |||
WebIM.Emoji = { | |||
map: { | |||
"[):]": "ee_1.png", | |||
"[:D]": "ee_2.png", | |||
"[;)]": "ee_3.png", | |||
"[:-o]": "ee_4.png", | |||
"[:p]": "ee_5.png", | |||
"[(H)]": "ee_6.png", | |||
"[:@]": "ee_7.png", | |||
"[:s]": "ee_8.png", | |||
"[:$]": "ee_9.png", | |||
"[:(]": "ee_10.png", | |||
"[:'(]": "ee_11.png", | |||
"[<o)]": "ee_12.png", | |||
"[(a)]": "ee_13.png", | |||
"[8o|]": "ee_14.png", | |||
"[8-|]": "ee_15.png", | |||
"[+o(]": "ee_16.png", | |||
"[|-)]": "ee_17.png", | |||
"[:|]": "ee_18.png", | |||
"[*-)]": "ee_19.png", | |||
"[:-#]": "ee_20.png", | |||
"[^o)]": "ee_21.png", | |||
"[:-*]": "ee_22.png", | |||
"[8-)]": "ee_23.png", | |||
"[del]": "btn_del.png", | |||
"[(|)]": "ee_24.png", | |||
"[(u)]": "ee_25.png", | |||
"[(S)]": "ee_26.png", | |||
"[(*)]": "ee_27.png", | |||
"[(#)]": "ee_28.png", | |||
"[(R)]": "ee_29.png", | |||
"[({)]": "ee_30.png", | |||
"[(})]": "ee_31.png", | |||
"[(k)]": "ee_32.png", | |||
"[(F)]": "ee_33.png", | |||
"[(W)]": "ee_34.png", | |||
"[(D)]": "ee_35.png" | |||
} | |||
}; | |||
WebIM.EmojiObj = { | |||
// 相对 emoji.js 路径 | |||
map1: { | |||
"[):]": "ee_1.png", | |||
"[:D]": "ee_2.png", | |||
"[;)]": "ee_3.png", | |||
"[:-o]": "ee_4.png", | |||
"[:p]": "ee_5.png", | |||
"[(H)]": "ee_6.png", | |||
"[:@]": "ee_7.png" | |||
}, | |||
map2: { | |||
"[:s]": "ee_8.png", | |||
"[:$]": "ee_9.png", | |||
"[:(]": "ee_10.png", | |||
"[:'(]": "ee_11.png", | |||
"[<o)]": "ee_12.png", | |||
"[(a)]": "ee_13.png", | |||
"[8o|]": "ee_14.png" | |||
}, | |||
map3: { | |||
"[8-|]": "ee_15.png", | |||
"[+o(]": "ee_16.png", | |||
"[|-)]": "ee_17.png", | |||
"[:|]": "ee_18.png", | |||
"[*-)]": "ee_19.png", | |||
"[:-#]": "ee_20.png", | |||
"[del]": "del.png" | |||
}, | |||
map4: { | |||
"[^o)]": "ee_21.png", | |||
"[:-*]": "ee_22.png", | |||
"[8-)]": "ee_23.png", | |||
"[(|)]": "ee_24.png", | |||
"[(u)]": "ee_25.png", | |||
"[(S)]": "ee_26.png", | |||
"[(*)]": "ee_27.png" | |||
}, | |||
map5: { | |||
"[(#)]": "ee_28.png", | |||
"[(R)]": "ee_29.png", | |||
"[({)]": "ee_30.png", | |||
"[(})]": "ee_31.png", | |||
"[(k)]": "ee_32.png", | |||
"[(F)]": "ee_33.png", | |||
"[(W)]": "ee_34.png", | |||
"[(D)]": "ee_35.png" | |||
}, | |||
map6: { | |||
"[del]": "del.png" | |||
} | |||
}; // wx.connectSocket({url: WebIM.config.xmppURL, method: "GET"}) | |||
WebIM.conn = new WebIM.connection({ | |||
appKey: WebIM.config.appkey, | |||
isMultiLoginSessions: WebIM.config.isMultiLoginSessions, | |||
https: typeof WebIM.config.https === "boolean" ? WebIM.config.https : location.protocol === "https:", | |||
url: WebIM.config.xmppURL, | |||
apiUrl: WebIM.config.apiURL, | |||
isAutoLogin: false, | |||
heartBeatWait: WebIM.config.heartBeatWait, | |||
autoReconnectNumMax: WebIM.config.autoReconnectNumMax, | |||
autoReconnectInterval: WebIM.config.autoReconnectInterval | |||
}); // async response | |||
// WebIM.conn.listen({ | |||
// onOpened: () => dispatch({type: Types.ON_OPEND}) | |||
// }) | |||
// export default WebIM; | |||
module.exports = { | |||
"default": WebIM | |||
}; |
@@ -0,0 +1,90 @@ | |||
/** | |||
* git do not control webim.config.js | |||
* everyone should copy webim.config.js to webim.config.js | |||
* and have their own configs. | |||
* In this way , others won't be influenced by this config while git pull. | |||
* | |||
*/ | |||
// for react native | |||
let location = { | |||
protocol: "https" | |||
}; | |||
let config = { | |||
/* | |||
* XMPP server | |||
*/ | |||
// xmppURL: "wss://im-api.easemob.com/ws/", //小程序2.0sdk线上环境 | |||
// xmppURL: "wss://im-api-hsb.easemob.com/ws/", //小程序2.0sdk沙箱环境 | |||
// xmppURL: 'wss://im-api-new-hsb.easemob.com/websocket', //小程序沙箱环境 | |||
xmppURL: 'wss://im-api-wechat.easemob.com/websocket', | |||
//小程序线上环境 | |||
/* | |||
* Backend REST API URL | |||
*/ | |||
// apiURL: (location.protocol === 'https:' ? 'https:' : 'http:') + '//a1.easemob.com', | |||
apiURL: "https://a1.easemob.com", | |||
// 线上环境 | |||
// apiURL: "https://a1-hsb.easemob.com", // 沙箱环境 | |||
// apiURL: 'https://172.17.3.155:8080', | |||
/* | |||
* Application AppKey | |||
*/ | |||
appkey: "1106191017019057#qufang", | |||
/* | |||
* Whether to use HTTPS '1177161227178308#xcx' | |||
* @parameter {Boolean} true or false | |||
*/ | |||
https: false, | |||
/* | |||
* isMultiLoginSessions | |||
* true: A visitor can sign in to multiple webpages and receive messages at all the webpages. | |||
* false: A visitor can sign in to only one webpage and receive messages at the webpage. | |||
*/ | |||
isMultiLoginSessions: true, | |||
/** | |||
* Whether to use window.doQuery() | |||
* @parameter {Boolean} true or false | |||
*/ | |||
isWindowSDK: false, | |||
/** | |||
* isSandBox=true: xmppURL: 'im-api.sandbox.easemob.com', apiURL: '//a1.sdb.easemob.com', | |||
* isSandBox=false: xmppURL: 'im-api.easemob.com', apiURL: '//a1.easemob.com', | |||
* @parameter {Boolean} true or false | |||
*/ | |||
isSandBox: false, | |||
/** | |||
* Whether to console.log in strophe.log() | |||
* @parameter {Boolean} true or false | |||
*/ | |||
isDebug: false, | |||
/** | |||
* will auto connect the xmpp server autoReconnectNumMax times in background when client is offline. | |||
* won't auto connect if autoReconnectNumMax=0. | |||
*/ | |||
autoReconnectNumMax: 15, | |||
/** | |||
* the interval secons between each atuo reconnectting. | |||
* works only if autoReconnectMaxNum >= 2. | |||
*/ | |||
autoReconnectInterval: 2, | |||
/** | |||
* webrtc supports WebKit and https only | |||
*/ | |||
isWebRTC: false, | |||
/* | |||
* Set to auto sign-in | |||
*/ | |||
isAutoLogin: true | |||
}; | |||
export default config; |
@@ -0,0 +1,54 @@ | |||
// const baseUrl = 'http://121.42.63.138:9091/autoSR/api';// 测试站 | |||
// const baseUrl = 'http://192.168.31.163:8080/autoSR/api'; // 长龙 | |||
// const baseUrl = 'http://192.168.31.130:8080/autoSR/api'; // 佳豪 | |||
// const baseUrl = 'http://10.2.1.104:8081/autoSR/api'; // 刘敏 | |||
const baseUrl = 'https://zkgj.quhouse.com/api'; // 质控正式 | |||
// const baseUrl = 'https://hfju.com/api'; // 数智正式 | |||
const install = (Vue, vm) => { | |||
Vue.prototype.$u.http.setConfig({ | |||
baseUrl, | |||
loadingText: '加载中~', | |||
loadingTime: 800, | |||
}); | |||
// 请求拦截,如果有token,携带token | |||
Vue.prototype.$u.http.interceptor.request = (config) => { | |||
const token = uni.getStorageSync('weapp_session_login_data'); | |||
if(token){ | |||
config.header['Access-Token'] = token.token; | |||
} | |||
return config | |||
} | |||
// 响应拦截,公共错误处理 | |||
Vue.prototype.$u.http.interceptor.response = (res) => { | |||
if(res.code == 10000) { | |||
return res.data; | |||
}else if(res.code == 10003 || res.code == 20006){ | |||
uni.hideToast(); | |||
uni.showToast({ | |||
icon:"none", | |||
title:"您的登录已失效,请重新登录", | |||
duration: 2000 | |||
}) | |||
uni.clearStorageSync(); | |||
setTimeout(function () { | |||
uni.reLaunch({ | |||
url: '/pages/login/index' | |||
}); | |||
},2000); | |||
return false; | |||
}else{ | |||
uni.hideLoading(); | |||
uni.showToast({ | |||
icon:"none", | |||
title:res.message, | |||
duration: 3000 | |||
}) | |||
return false; | |||
} | |||
} | |||
} | |||
export default { | |||
install | |||
} |
@@ -0,0 +1,23 @@ | |||
var sub = function (val, lengths) { | |||
var length_val = val.length + ''; | |||
if (length_val == 0 || val == undefined) { | |||
return; | |||
} | |||
if (length_val > lengths) { | |||
return val.substring(0, lengths) + "..."; | |||
} else { | |||
return val; | |||
} | |||
}; | |||
var subMore = function (val, lengths) { | |||
var val = val + ''; | |||
return val.substring(0, lengths); | |||
}; | |||
module.exports = { | |||
sub: sub, | |||
subMore: subMore | |||
}; |
@@ -0,0 +1,670 @@ | |||
let WebIM = uni.WebIM = require("./WebIM.js")["default"]; | |||
function formatLongTime(mss) { | |||
var days = parseInt(mss / (1000 * 60 * 60 * 24)); | |||
var hours = parseInt(mss % (1000 * 60 * 60 * 24) / (1000 * 60 * 60)); | |||
var minutes = parseInt(mss % (1000 * 60 * 60) / (1000 * 60)); | |||
var seconds = mss % (1000 * 60) / 1000; | |||
seconds = parseInt(seconds); | |||
if (minutes < 9) { | |||
minutes = "0" + minutes; | |||
} | |||
if (seconds < 9) { | |||
seconds = "0" + seconds; | |||
} | |||
return minutes + ":" + seconds + ""; | |||
} | |||
/** | |||
* 格式化时间 | |||
* @param {String} date 原始时间格式 | |||
* 格式后的时间:yyyy/mm/dd hh:mm:ss | |||
**/ | |||
/** | |||
* 格式化时间 | |||
* @param {String} date 原始时间格式 | |||
* 格式后的时间:yyyy/mm/dd hh:mm:ss | |||
**/ | |||
const formatSecond = seconds=> { | |||
var h = Math.floor(seconds / 3600) < 10 ? '0'+Math.floor(seconds / 3600) : Math.floor(seconds / 3600); | |||
var m = Math.floor((seconds / 60 % 60)) < 10 ? '0' + Math.floor((seconds / 60 % 60)) : Math.floor((seconds / 60 % 60)); | |||
var s = Math.floor((seconds % 60)) < 10 ? '0' + Math.floor((seconds % 60)) : Math.floor((seconds % 60)); | |||
return h + ":" + m + ":" + s; | |||
} | |||
const formatTime = date => { | |||
const year = date.getFullYear(); | |||
const month = date.getMonth() + 1; | |||
const day = date.getDate(); | |||
const hour = date.getHours(); | |||
const minute = date.getMinutes(); | |||
const second = date.getSeconds(); | |||
return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':'); | |||
}; | |||
/** | |||
* 格式化时间 | |||
* @param {String} date 原始时间格式 | |||
* 格式后的时间:hh:mm | |||
**/ | |||
const formatDateTime = date => { | |||
const hour = date.getHours(); | |||
const minute = date.getMinutes(); | |||
return [hour, minute, second].map(formatNumber).join(':'); | |||
}; | |||
/** | |||
* 格式化时间 | |||
* @param {String} date 原始时间格式 | |||
* 格式后的时间:yyyy-mm-dd | |||
**/ | |||
const formatDate = date => { | |||
const year = date.getFullYear(); | |||
const month = date.getMonth() + 1; | |||
const day = date.getDate(); | |||
return [year, month, day].map(formatNumber).join('-'); | |||
}; | |||
const formatNumber = n => { | |||
n = n.toString(); | |||
return n[1] ? n : '0' + n; | |||
}; | |||
/** | |||
* 获取指定日期前后N天的日期、前N个月日期、后N个月日期 | |||
* @param {String} sdate 当前日期 | |||
* @param {Number} interval 间隔天数 | |||
* @param {String} caret 间隔符号 | |||
* @example 获取当天日期 getNowFormatDate("",0,"-"); 结果为"2018-09-18"; | |||
获取前一天日期 getNowFormatDate("2018-03-01",-1,"-"); 结果为"2018-02-18"; | |||
获取后一天日期 getNowFormatDate("2018-02-28",1,"-"); 结果为"2018-03-01"; | |||
**/ | |||
function getNowFormatDate(sdate, interval, caret) { | |||
var patt1 = /^\d{4}-([0-1]?[0-9])-([0-3]?[0-9])$/; //判断输入的日期是否符合格式正则表达式 | |||
if (!(sdate && typeof sdate == "string" && patt1.test(sdate))) { | |||
sdate = new Date(); //不满足日期的则使用当前年月日 | |||
} | |||
interval = isNaN(parseInt(interval)) ? 0 : parseInt(interval); //若没有输入间隔,则使用当前日 | |||
caret = caret && typeof caret == "string" ? caret : ""; | |||
var gdate = new Date(sdate).getTime(); //获取指定年月日 | |||
gdate = gdate + 1000 * 60 * 60 * 24 * interval; //加减相差毫秒数 | |||
var speDate = new Date(gdate); //获取指定好毫秒数时间 | |||
var preYear = speDate.getFullYear(); | |||
var preMonth = speDate.getMonth() + 1; | |||
var preDay = speDate.getDate(); | |||
preMonth = preMonth < 10 ? "0" + preMonth : preMonth; | |||
preDay = preDay < 10 ? "0" + preDay : preDay; | |||
var preDate = preYear + caret + preMonth + caret + preDay; | |||
return preDate; | |||
} // 获取某年某月的有多少周 | |||
String.prototype.weekInMonthCount = function () { | |||
var date = new Date(new Date(this.replace(/-/g, "/")) || new Date()); | |||
var firstWeekDate = 1; // 默认第一周是本月1号 为了模拟本月1号是否为本月第1周的判断 | |||
if (date.getDay() === 1) { | |||
// 判断1号是周一 | |||
firstWeekDate = 1; | |||
} else if (date.getDay() === 0) { | |||
// 判断1号是周日 | |||
firstWeekDate = 8 - 7 + 1; | |||
} else { | |||
// 判断1号是周二至周六之间 | |||
firstWeekDate = 8 - date.getDay() + 1; | |||
} | |||
date.setMonth(date.getMonth() + 1); | |||
date.setDate(0); | |||
var monthHasDays = date.getDate(); // 本月天数 | |||
monthHasDays = date.getDate() - firstWeekDate + 1; | |||
var hasWeek = Math.ceil(monthHasDays / 7); // 计算本月有几周 | |||
return hasWeek; | |||
}; // 获取今天是第几周 注:ios不支持2018-01-14 需转化为2018/01/14 | |||
String.prototype.weekIndexInMonth = function () { | |||
var date_trim = (this.trim() != "" ? this : new Date()).replace(/-/g, "/"); | |||
var date = new Date(date_trim); | |||
var dateStart_trim = new Date((this.trim() != "" ? this : new Date()).replace(/-/g, "/")).setDate(1); | |||
var dateStart = new Date(dateStart_trim); // 本月初 | |||
var firstWeek = 1; | |||
if (dateStart.getDay() === 1) { | |||
firstWeek = 1; | |||
} else if (dateStart.getDay() === 0) { | |||
firstWeek = 8 - 7 + 1; | |||
} else { | |||
firstWeek = 8 - dateStart.getDay() + 1; | |||
} | |||
var weekIndex = 1; | |||
var c = date.getDate(); | |||
if (date.getDay() === 1 && date.getDate() < 7) { | |||
weekIndex = 1; | |||
} else if (c < firstWeek) { | |||
weekIndex = -1; | |||
} else { | |||
if (c < 7) { | |||
weekIndex = Math.ceil(c / 7); | |||
} else { | |||
c = c - firstWeek + 1; | |||
if (c % 7 === 0) { | |||
if (dateStart.getDay() !== 6) { | |||
weekIndex = c / 7; | |||
} else { | |||
weekIndex = c / 7 + 1; | |||
} | |||
} else { | |||
weekIndex = Math.ceil(c / 7); | |||
} | |||
} | |||
} | |||
return weekIndex; | |||
}; | |||
/** | |||
* 验证车牌号是否正确 | |||
* @param number carNumber | |||
**/ | |||
function isLicensePlate(carNumber) { | |||
if (carNumber == '') { | |||
return false; | |||
} | |||
if (/^[A-Za-z]+$/.test(carNumber.slice(1))) { | |||
//全为字母 | |||
return false; | |||
} | |||
return /^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/.test(carNumber); | |||
} | |||
/** | |||
* 验证手机号 | |||
*/ | |||
function checkPhone(phone) { | |||
let that = this; | |||
if (phone == '') { | |||
return false; | |||
} | |||
if (!/^1[3456789]\d{9}$/.test(phone)) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
/** | |||
* 隐藏字符串 | |||
* @param string str 字符串 | |||
* @param int frontLen 前面需要保留几位 | |||
* @param int endLen 后面需要保留几位 | |||
*/ | |||
function hiddenString(str, frontLen, endLen) { | |||
var len = str.length - frontLen - endLen; | |||
var xing = ''; | |||
for (var i = 0; i < len; i++) { | |||
xing += '*'; | |||
} | |||
return str.substring(0, frontLen) + xing + str.substring(str.length - endLen); | |||
} | |||
/** | |||
* 从一个数组中随机取出若干个元素组成数组 | |||
* @param {Array} arr 原数组 | |||
* @param {Number} count 需要随机取得个数 | |||
**/ | |||
const getRandomArray = (arr, count) => { | |||
var shuffled = arr.slice(0), | |||
i = arr.length, | |||
min = i - count, | |||
temp, | |||
index; | |||
while (i-- > min) { | |||
index = Math.floor((i + 1) * Math.random()); | |||
temp = shuffled[index]; | |||
shuffled[index] = shuffled[i]; | |||
shuffled[i] = temp; | |||
} | |||
return shuffled.slice(min); | |||
}; | |||
/** | |||
* 从一个数组中随机取出一个元素 | |||
* @param {Array} arr 原数组 | |||
**/ | |||
const getRandomArrayElement = arr => { | |||
return arr[Math.floor(Math.random() * arr.length)]; | |||
}; | |||
/** | |||
* 去除数组中重复的值 | |||
* @param {Array} arr 原数组 | |||
**/ | |||
const getUnique = array => { | |||
var n = {}, | |||
r = [], | |||
len = array.length, | |||
val, | |||
type; | |||
for (var i = 0; i < array.length; i++) { | |||
val = array[i]; | |||
type = typeof val; | |||
if (!n[val]) { | |||
n[val] = [type]; | |||
r.push(val); | |||
} else if (n[val].indexOf(type) < 0) { | |||
n[val].push(type); | |||
r.push(val); | |||
} | |||
} | |||
return r; | |||
}; | |||
/** | |||
* 读取xml字符串 | |||
* @param {String} xmlString 数据 | |||
**/ | |||
function loadXMLStr(xmlString) { | |||
var danmulist = []; | |||
if (xmlString.indexOf("<d p")) { | |||
var str = xmlString.substring(xmlString.indexOf("<d p"), xmlString.length); | |||
var reg = /<d p/g; | |||
var arr = str.match(reg); | |||
if (arr) { | |||
console.log(str); | |||
console.log(arr.length); | |||
for (var i = 0; i < arr.length; ++i) { | |||
var getstr = str.substring(0, str.indexOf("</d>") + "</d>".length); | |||
str = str.substring(str.indexOf("</d>") + "</d>".length, str.length); | |||
var danmu = { | |||
text: getstr.substring(getstr.indexOf(">") + ">".length, getstr.indexOf("</d>")), | |||
color: '#ff00ff', | |||
time: Math.floor(getstr.substring(getstr.indexOf("<d p=\"") + "<d p=\"".length, getstr.indexOf(","))) | |||
}; | |||
danmulist.push(danmu); | |||
} | |||
} | |||
} | |||
return danmulist; | |||
} // 显示警告提示(7个汉字长度) | |||
var showWarn = text => uni.showToast({ | |||
title: text, | |||
image: 'https://qufang.oss-cn-beijing.aliyuncs.com/upload/icon/xcx/jjycrm/warn.png' | |||
}); // 显示错误提示(7个汉字长度) | |||
var showError = text => uni.showToast({ | |||
title: text, | |||
image: 'https://qufang.oss-cn-beijing.aliyuncs.com/upload/icon/xcx/jjycrm/error.png' | |||
}); // 显示繁忙提示(7个汉字长度) | |||
var showBusy = text => uni.showToast({ | |||
title: text, | |||
icon: 'loading', | |||
duration: 1500 | |||
}); // 显示成功提示(7个汉字长度) | |||
var showSuccess = text => uni.showToast({ | |||
title: text, | |||
icon: 'success' | |||
}); // 显示无图标提示(两行) | |||
var showNone = text => uni.showToast({ | |||
title: text, | |||
icon: 'none', | |||
duration: 1500 | |||
}); // 显示失败提示框 | |||
var showModel = (title, content) => { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title, | |||
content: JSON.stringify(content), | |||
showCancel: false | |||
}); | |||
}; //获取列表 | |||
function getList(url, userinfo, page, pageSize, callback) { | |||
this.showBusy('加载中...'); | |||
var token = uni.getStorageSync('weapp_session_login_data'); | |||
uni.request({ | |||
url: url, | |||
data: { | |||
skey: userinfo.skey, | |||
tel: userinfo.userinfo.mobile_phone, | |||
page: page, | |||
pageSize: pageSize | |||
}, | |||
method: 'POST', | |||
header: { | |||
'content-type': 'application/x-www-form-urlencoded', | |||
"Access-Token":token.token | |||
}, | |||
success: function (result) { | |||
if (result.data.code == '200') { | |||
uni.hideToast(); | |||
var result = result.data; | |||
callback(result); | |||
} | |||
}, | |||
fail(error) { | |||
this.showModel('请求失败', error); | |||
return false; | |||
} | |||
}); | |||
} //请求数据 | |||
function getRequest(url, params, callback) { | |||
this.showBusy('加载中...'); | |||
var token = uni.getStorageSync('weapp_session_login_data'); | |||
uni.request({ | |||
url: url, | |||
data: params, | |||
method: 'POST', | |||
header: { | |||
'content-type': 'application/json', | |||
'Access-Token': token.token | |||
}, | |||
success: function (result) { | |||
if (result.data.code == '10003' || result.data.code == '20006') { | |||
//未登录 | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '您的登录已失效,请重新登录', | |||
showCancel: false, | |||
success(res) { | |||
if (res.confirm) { | |||
try { | |||
uni.clearStorageSync(); | |||
uni.reLaunch({ | |||
url: '/pages/main/login/index' //绝对路径 | |||
}); | |||
} catch (e) { | |||
return false; | |||
} | |||
} | |||
} | |||
}); // WebIM.conn.close(); | |||
return false; | |||
} | |||
if (result.data.code == '10000') { | |||
uni.hideToast(); | |||
var result = result.data; | |||
callback(result.data); | |||
} else { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '加载失败,请重新刷新', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}, | |||
fail(error) { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '网络异常,请重新刷新', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}); | |||
} //请求数据(异步) | |||
function getRequestPromise(url, params, isNone,option) { | |||
var isNone = isNone ? true : false; | |||
if (!isNone) { | |||
this.showBusy('加载中...'); | |||
} | |||
var token = uni.getStorageSync('weapp_session_login_data'); | |||
return new Promise(function (resolve, reject) { | |||
uni.request({ | |||
url: url, | |||
data: params, | |||
method: option?option:'POST', | |||
header: { | |||
'content-type': 'application/json', | |||
'Access-Token': token.token | |||
}, | |||
success: function (result) { | |||
if (result.data.code == '10000') { | |||
uni.hideToast(); | |||
var results = result.data; | |||
resolve(results.data); | |||
} else if (result.data.code == '10003' || result.data.code == '20006') { | |||
//未登录 | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '您的登录已失效,请重新登录', | |||
showCancel: false, | |||
success(res) { | |||
if (res.confirm) { | |||
try { | |||
uni.clearStorageSync(); | |||
uni.reLaunch({ | |||
url: '/pages/main/login/index' //绝对路径 | |||
}); | |||
} catch (e) { | |||
return false; | |||
} | |||
} | |||
} | |||
}); // WebIM.conn.close(); | |||
return false; | |||
} else if (result.data.code == '60001') { | |||
//已录入客户 | |||
uni.hideToast(); | |||
uni.showToast({ | |||
title: '您已经录入了该客户!', | |||
icon: 'none', | |||
duration: 1500 | |||
}); | |||
return false; | |||
} else if (result.data.code == '20005') { | |||
//已录入客户 | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '原始密码错误', | |||
showCancel: false | |||
}); | |||
return false; | |||
} else { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: result.data.message ? result.data.message : '请求数据失败,请重新尝试', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}, | |||
fail(error) { | |||
console.log(error); | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '网络异常,请重新尝试', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}); | |||
}); | |||
} //请求数据自定义 | |||
function getRequestCustom(url, params, isShow) { | |||
if (isShow != 1) { | |||
this.showBusy('加载中...'); | |||
} | |||
var token = uni.getStorageSync('weapp_session_login_data'); | |||
return new Promise(function (resolve, reject) { | |||
uni.request({ | |||
url: url, | |||
data: params, | |||
method: 'POST', | |||
header: { | |||
'content-type': 'application/x-www-form-urlencoded', | |||
'Access-Token': token.token | |||
}, | |||
success: function (result) { | |||
if (result.data.data == undefined) { | |||
resolve(result.data); | |||
} else { | |||
if (result.data.success) { | |||
resolve(result.data.data); | |||
} else { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: result.data.message ? result.data.message : '请求数据失败,请重新尝试', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
} | |||
}, | |||
fail(error) { | |||
uni.showModal({ | |||
title: '提示', | |||
content: '网络异常,请重新尝试', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}); | |||
}); | |||
} //请求数据(异步)-自定义 | |||
function getRequestPromiseCustom(url, params,option) { | |||
this.showBusy('请求中...'); | |||
var token = uni.getStorageSync('weapp_session_login_data'); | |||
return new Promise(function (resolve, reject) { | |||
uni.request({ | |||
url: url, | |||
data: params, | |||
method: option?option:'POST', | |||
header: { | |||
'content-type': 'application/json', | |||
'token': token.token | |||
}, | |||
success: function (result) { | |||
uni.hideToast(); | |||
var results = result.data; | |||
resolve(results); | |||
}, | |||
fail(error) { | |||
uni.hideToast(); | |||
uni.showModal({ | |||
title: '提示', | |||
content: '网络异常,请重新尝试', | |||
showCancel: false | |||
}); | |||
return false; | |||
} | |||
}); | |||
}); | |||
} | |||
module.exports = { | |||
formatSecond, | |||
formatTime, | |||
formatDate, | |||
formatDateTime, | |||
getRandomArray: getRandomArray, | |||
getRandomArrayElement: getRandomArrayElement, | |||
showWarn, | |||
showError, | |||
showBusy, | |||
showSuccess, | |||
showNone, | |||
showModel, | |||
getList: getList, | |||
getRequest: getRequest, | |||
getRequestPromise: getRequestPromise, | |||
getRequestCustom: getRequestCustom, | |||
getRequestPromiseCustom: getRequestPromiseCustom, | |||
getNowFormatDate: getNowFormatDate, | |||
isLicensePlate: isLicensePlate, | |||
checkPhone: checkPhone, | |||
hiddenString: hiddenString, | |||
getUnique: getUnique, | |||
loadXMLStr: loadXMLStr, | |||
formatLongTime: formatLongTime | |||
}; |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2020 www.uviewui.com | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,102 @@ | |||
<p align="center"> | |||
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;"> | |||
</p> | |||
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3> | |||
<h3 align="center">多平台快速开发的UI框架</h3> | |||
## 说明 | |||
uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水 | |||
## 特性 | |||
- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序 | |||
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用 | |||
- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨 | |||
- 众多的常用页面和布局,让您专注逻辑,事半功倍 | |||
- 详尽的文档支持,现代化的演示效果 | |||
- 按需引入,精简打包体积 | |||
## 安装 | |||
```bash | |||
# npm方式安装 | |||
npm i uview-ui | |||
``` | |||
## 快速上手 | |||
1. `main.js`引入uView库 | |||
```js | |||
// main.js | |||
import uView from 'uview-ui'; | |||
Vue.use(uView); | |||
``` | |||
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持) | |||
```css | |||
/* App.vue */ | |||
<style lang="scss"> | |||
@import "uview-ui/index.scss"; | |||
</style> | |||
``` | |||
3. `uni.scss`引入全局scss变量文件 | |||
```css | |||
/* uni.scss */ | |||
@import "uview-ui/theme.scss"; | |||
``` | |||
4. `pages.json`配置easycom规则(按需引入) | |||
```js | |||
// pages.json | |||
{ | |||
"easycom": { | |||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue" | |||
}, | |||
// 此为本身已有的内容 | |||
"pages": [ | |||
// ...... | |||
] | |||
} | |||
``` | |||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 | |||
## 使用方法 | |||
配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。 | |||
```html | |||
<template> | |||
<u-button>按钮</u-button> | |||
</template> | |||
``` | |||
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 | |||
## 链接 | |||
- [官方文档](https://uviewui.com/) | |||
- [更新日志](https://uviewui.com/components/changelog.html) | |||
- [升级指南](https://uviewui.com/components/changelog.html) | |||
- [关于我们](https://uviewui.com/cooperation/about.html) | |||
## 预览 | |||
您可以通过**微信**扫码,查看最佳的演示效果。 | |||
<br> | |||
<br> | |||
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" > | |||
<!-- ## 捐赠uView的研发 | |||
uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。 | |||
<img src="https://uviewui.com/common/wechat.png" width="220" > | |||
<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" > | |||
--> | |||
## 版权信息 | |||
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。 |
@@ -0,0 +1,172 @@ | |||
<template> | |||
<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble" | |||
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex"> | |||
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]"> | |||
{{tips.text}} | |||
</view> | |||
<block v-for="(item, index) in list" :key="index"> | |||
<view @touchmove.stop.prevent @tap="itemClick(index)" :style="[itemStyle(index)]" class="u-action-sheet-item" :class="[index < list.length - 1 ? 'u-border-bottom' : '']" | |||
hover-class="u-hover-class" :hover-stay-time="150"> | |||
{{item.text}} | |||
</view> | |||
</block> | |||
<view class="u-gab" v-if="cancelBtn"> | |||
</view> | |||
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class" | |||
:hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view> | |||
</u-popup> | |||
</template> | |||
<script> | |||
/** | |||
* actionSheet 操作菜单 | |||
* @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。 | |||
* @tutorial https://www.uviewui.com/components/actionSheet.html | |||
* @property {Array<Object>} list 按钮的文字数组,见官方文档示例 | |||
* @property {Object} tips 顶部的提示文字,见官方文档示例 | |||
* @property {String} cancel-text 取消按钮的提示文字 | |||
* @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true) | |||
* @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0) | |||
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true) | |||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) | |||
* @property {Number String} z-index z-index值(默认1075) | |||
* @property {String} cancel-text 取消按钮的提示文字 | |||
* @event {Function} click 点击ActionSheet列表项时触发 | |||
* @event {Function} close 点击取消按钮时触发 | |||
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet> | |||
*/ | |||
export default { | |||
name: "u-action-sheet", | |||
props: { | |||
// 点击遮罩是否可以关闭actionsheet | |||
maskCloseAble: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx | |||
list: { | |||
type: Array, | |||
default () { | |||
// 如下 | |||
// return [{ | |||
// text: '确定', | |||
// color: '', | |||
// fontSize: '' | |||
// }] | |||
return []; | |||
} | |||
}, | |||
// 顶部的提示文字 | |||
tips: { | |||
type: Object, | |||
default () { | |||
return { | |||
text: '', | |||
color: '', | |||
fontSize: '26' | |||
} | |||
} | |||
}, | |||
// 底部的取消按钮 | |||
cancelBtn: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 | |||
safeAreaInsetBottom: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 通过双向绑定控制组件的弹出与收起 | |||
value: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 弹出的顶部圆角值 | |||
borderRadius: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 弹出的z-index值 | |||
zIndex: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 取消按钮的文字提示 | |||
cancelText: { | |||
type: String, | |||
default: '取消' | |||
} | |||
}, | |||
computed: { | |||
// 顶部提示的样式 | |||
tipsStyle() { | |||
let style = {}; | |||
if (this.tips.color) style.color = this.tips.color; | |||
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx'; | |||
return style; | |||
}, | |||
// 操作项目的样式 | |||
itemStyle() { | |||
return (index) => { | |||
let style = {}; | |||
if (this.list[index].color) style.color = this.list[index].color; | |||
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx'; | |||
return style; | |||
} | |||
}, | |||
uZIndex() { | |||
// 如果用户有传递z-index值,优先使用 | |||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | |||
} | |||
}, | |||
methods: { | |||
// 点击取消按钮 | |||
close() { | |||
// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数 | |||
// 这是一个vue发送事件的特殊用法 | |||
this.popupClose(); | |||
this.$emit('close'); | |||
}, | |||
// 弹窗关闭 | |||
popupClose() { | |||
this.$emit('input', false); | |||
}, | |||
// 点击某一个itemif (!this.show) return; | |||
itemClick(index) { | |||
this.$emit('click', index); | |||
this.$emit('input', false); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-tips { | |||
font-size: 26rpx; | |||
text-align: center; | |||
padding: 34rpx 0; | |||
line-height: 1; | |||
color: $u-tips-color; | |||
} | |||
.u-action-sheet-item { | |||
display: flex; | |||
line-height: 1; | |||
justify-content: center; | |||
align-items: center; | |||
font-size: 34rpx; | |||
padding: 34rpx 0; | |||
} | |||
.u-gab { | |||
height: 12rpx; | |||
background-color: rgb(234, 234, 236); | |||
} | |||
.u-actionsheet-cancel { | |||
color: $u-main-color; | |||
} | |||
</style> |
@@ -0,0 +1,219 @@ | |||
<template> | |||
<view class="u-alert-tips" v-if="show" :class="[ | |||
!show ? 'u-close-alert-tips': '', | |||
type ? 'u-alert-tips--bg--' + type + '-light' : '', | |||
type ? 'u-alert-tips--border--' + type + '-disabled' : '', | |||
]" :style="{ | |||
backgroundColor: bgColor, | |||
borderColor: borderColor | |||
}"> | |||
<view class="u-icon-wrap"> | |||
<u-icon v-if="showIcon" :name="$u.type2icon(type)" :size="description ? 40 : 32" class="u-icon" :color="type"></u-icon> | |||
</view> | |||
<view class="u-alert-content" @tap.stop="click"> | |||
<view class="u-alert-title" :style="{fontWeight: description ? 500 : 'normal'}"> | |||
{{title}} | |||
</view> | |||
<view v-if="description" class="u-alert-desc"> | |||
{{description}} | |||
</view> | |||
</view> | |||
<view class="u-icon-wrap"> | |||
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc" | |||
:size="22" class="u-close-icon" :style="{ | |||
top: description ? '18rpx' : '24rpx' | |||
}"></u-icon> | |||
</view> | |||
<text v-if="closeAble && closeText" class="u-close-text" :style="{ | |||
top: description ? '18rpx' : '24rpx' | |||
}">{{closeText}}</text> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* alertTips 警告提示 | |||
* @description 警告提示,展现需要关注的信息 | |||
* @tutorial https://uviewui.com/components/alertTips.html | |||
* @property {String} title 显示的标题文字 | |||
* @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选 | |||
* @property {String} type 关闭按钮(默认为叉号icon图标) | |||
* @property {String} close-able 用文字替代关闭图标,close-able为true时有效 | |||
* @property {Boolean} show-icon 是否显示左边的辅助图标 | |||
* @property {Boolean} show 显示或隐藏组件 | |||
* @event {Function} click 点击组件时触发 | |||
* @event {Function} close 点击关闭按钮时触发 | |||
*/ | |||
export default { | |||
name: 'u-alert-tips', | |||
props: { | |||
// 显示文字 | |||
title: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 主题,success/warning/info/error | |||
type: { | |||
type: String, | |||
default: 'warning' | |||
}, | |||
// 辅助性文字 | |||
description: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否可关闭 | |||
closeAble: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 关闭按钮自定义文本 | |||
closeText: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示图标 | |||
showIcon: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 文字颜色,如果定义了color值,icon会失效 | |||
color: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 边框颜色 | |||
borderColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示 | |||
show: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
} | |||
}, | |||
methods: { | |||
// 点击内容 | |||
click() { | |||
this.$emit('click'); | |||
}, | |||
// 点击关闭按钮 | |||
close() { | |||
this.$emit('close'); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-alert-tips { | |||
display: flex; | |||
align-items: center; | |||
padding: 16rpx 30rpx; | |||
border-radius: 8rpx; | |||
position: relative; | |||
transition: all 0.3s linear; | |||
border: 1px solid #fff; | |||
&--bg--primary-light { | |||
background-color: $u-type-primary-light; | |||
} | |||
&--bg--info-light { | |||
background-color: $u-type-info-light; | |||
} | |||
&--bg--success-light { | |||
background-color: $u-type-success-light; | |||
} | |||
&--bg--warning-light { | |||
background-color: $u-type-warning-light; | |||
} | |||
&--bg--error-light { | |||
background-color: $u-type-error-light; | |||
} | |||
&--border--primary-disabled { | |||
border-color: $u-type-primary-disabled; | |||
} | |||
&--border--success-disabled { | |||
border-color: $u-type-success-disabled; | |||
} | |||
&--border--error-disabled { | |||
border-color: $u-type-error-disabled; | |||
} | |||
&--border--warning-disabled { | |||
border-color: $u-type-warning-disabled; | |||
} | |||
&--border--info-disabled { | |||
border-color: $u-type-info-disabled; | |||
} | |||
} | |||
.u-close-alert-tips { | |||
opacity: 0; | |||
visibility: hidden; | |||
} | |||
@keyframes myfirst { | |||
from { | |||
height: 100%; | |||
} | |||
to { | |||
height: 0 | |||
} | |||
} | |||
.u-icon { | |||
margin-right: 16rpx; | |||
} | |||
.u-alert-title { | |||
font-size: 28rpx; | |||
color: $u-main-color; | |||
} | |||
.u-alert-desc { | |||
font-size: 26rpx; | |||
text-align: left; | |||
color: $u-content-color; | |||
} | |||
.u-close-icon { | |||
position: absolute; | |||
top: 20rpx; | |||
right: 20rpx; | |||
} | |||
.u-close-hover { | |||
color: red; | |||
} | |||
.u-close-text { | |||
font-size: 24rpx; | |||
color: $u-tips-color; | |||
position: absolute; | |||
top: 20rpx; | |||
right: 20rpx; | |||
line-height: 1; | |||
} | |||
</style> |
@@ -0,0 +1,291 @@ | |||
<template> | |||
<view class="content"> | |||
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }"> | |||
<canvas | |||
class="cropper" | |||
:disable-scroll="true" | |||
@touchstart="touchStart" | |||
@touchmove="touchMove" | |||
@touchend="touchEnd" | |||
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }" | |||
canvas-id="cropper" | |||
id="cropper" | |||
></canvas> | |||
<canvas | |||
class="cropper" | |||
:disable-scroll="true" | |||
:style="{ | |||
position: 'fixed', | |||
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`, | |||
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`, | |||
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`, | |||
height: `${cropperOpt.height * cropperOpt.pixelRatio}` | |||
}" | |||
canvas-id="targetId" | |||
id="targetId" | |||
></canvas> | |||
</view> | |||
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }"> | |||
<!-- #ifdef H5 --> | |||
<view class="upload" @tap="uploadTap">选择图片</view> | |||
<!-- #endif --> | |||
<!-- #ifndef H5 --> | |||
<view class="upload" @tap="uploadTap">重新选择</view> | |||
<!-- #endif --> | |||
<view class="getCropperImage" @tap="getCropperImage(false)">确定</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
import WeCropper from './weCropper.js'; | |||
export default { | |||
props: { | |||
// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色, | |||
// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)" | |||
boundStyle: { | |||
type: Object, | |||
default() { | |||
return { | |||
lineWidth: 4, | |||
borderColor: 'rgb(245, 245, 245)', | |||
mask: 'rgba(0, 0, 0, 0.35)' | |||
}; | |||
} | |||
} | |||
// // 裁剪框宽度,单位rpx | |||
// rectWidth: { | |||
// type: [String, Number], | |||
// default: 400 | |||
// }, | |||
// // 裁剪框高度,单位rpx | |||
// rectHeight: { | |||
// type: [String, Number], | |||
// default: 400 | |||
// }, | |||
// // 输出图片宽度,单位rpx | |||
// destWidth: { | |||
// type: [String, Number], | |||
// default: 400 | |||
// }, | |||
// // 输出图片高度,单位rpx | |||
// destHeight: { | |||
// type: [String, Number], | |||
// default: 400 | |||
// }, | |||
// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可 | |||
// fileType: { | |||
// type: String, | |||
// default: 'jpg', | |||
// }, | |||
// // 生成的图片质量 | |||
// // H5上无效,目前不考虑使用此参数 | |||
// quality: { | |||
// type: [Number, String], | |||
// default: 1 | |||
// } | |||
}, | |||
data() { | |||
return { | |||
// 底部导航的高度 | |||
bottomNavHeight: 50, | |||
originWidth: 200, | |||
width: 0, | |||
height: 0, | |||
cropperOpt: { | |||
id: 'cropper', | |||
targetId: 'targetCropper', | |||
pixelRatio: 1, | |||
width: 0, | |||
height: 0, | |||
scale: 2.5, | |||
zoom: 8, | |||
cut: { | |||
x: (this.width - this.originWidth) / 2, | |||
y: (this.height - this.originWidth) / 2, | |||
width: this.originWidth, | |||
height: this.originWidth | |||
}, | |||
boundStyle: { | |||
lineWidth: uni.upx2px(this.boundStyle.lineWidth), | |||
mask: this.boundStyle.mask, | |||
color: this.boundStyle.borderColor | |||
} | |||
}, | |||
// 裁剪框和输出图片的尺寸,高度默认等于宽度 | |||
// 输出图片宽度,单位px | |||
destWidth: 200, | |||
// 裁剪框宽度,单位px | |||
rectWidth: 200, | |||
// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可 | |||
fileType: 'jpg', | |||
src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片 | |||
}; | |||
}, | |||
onLoad(option) { | |||
let rectInfo = uni.getSystemInfoSync(); | |||
this.width = rectInfo.windowWidth; | |||
this.height = rectInfo.windowHeight - this.bottomNavHeight; | |||
this.cropperOpt.width = this.width; | |||
this.cropperOpt.height = this.height; | |||
this.cropperOpt.pixelRatio = rectInfo.pixelRatio; | |||
if (option.destWidth) this.destWidth = option.destWidth; | |||
if (option.rectWidth) { | |||
let rectWidth = Number(option.rectWidth); | |||
this.cropperOpt.cut = { | |||
x: (this.width - rectWidth) / 2, | |||
y: (this.height - rectWidth) / 2, | |||
width: rectWidth, | |||
height: rectWidth | |||
}; | |||
} | |||
this.rectWidth = option.rectWidth; | |||
if (option.fileType) this.fileType = option.fileType; | |||
// 初始化 | |||
this.cropper = new WeCropper(this.cropperOpt) | |||
.on('ready', ctx => { | |||
// wecropper is ready for work! | |||
}) | |||
.on('beforeImageLoad', ctx => { | |||
// before picture loaded, i can do something | |||
}) | |||
.on('imageLoad', ctx => { | |||
// picture loaded | |||
}) | |||
.on('beforeDraw', (ctx, instance) => { | |||
// before canvas draw,i can do something | |||
}); | |||
// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景 | |||
uni.setNavigationBarColor({ | |||
frontColor: '#ffffff', | |||
backgroundColor: '#000000' | |||
}); | |||
uni.chooseImage({ | |||
count: 1, // 默认9 | |||
sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有 | |||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 | |||
success: res => { | |||
this.src = res.tempFilePaths[0]; | |||
// 获取裁剪图片资源后,给data添加src属性及其值 | |||
this.cropper.pushOrign(this.src); | |||
} | |||
}); | |||
}, | |||
methods: { | |||
touchStart(e) { | |||
this.cropper.touchStart(e); | |||
}, | |||
touchMove(e) { | |||
this.cropper.touchMove(e); | |||
}, | |||
touchEnd(e) { | |||
this.cropper.touchEnd(e); | |||
}, | |||
getCropperImage(isPre = false) { | |||
if(!this.src) return this.$u.toast('请先选择图片再裁剪'); | |||
let cropper_opt = { | |||
destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值 | |||
destWidth: Number(this.destWidth), | |||
fileType: this.fileType | |||
}; | |||
this.cropper.getCropperImage(cropper_opt, (path, err) => { | |||
if (err) { | |||
uni.showModal({ | |||
title: '温馨提示', | |||
content: err.message | |||
cancelColor:"#999999", | |||
}); | |||
} else { | |||
if (isPre) { | |||
uni.previewImage({ | |||
current: '', // 当前显示图片的 http 链接 | |||
urls: [path] // 需要预览的图片 http 链接列表 | |||
}); | |||
} else { | |||
uni.$emit('uAvatarCropper', path); | |||
this.$u.route({ | |||
type: 'back' | |||
}); | |||
} | |||
} | |||
}); | |||
}, | |||
uploadTap() { | |||
const self = this; | |||
uni.chooseImage({ | |||
count: 1, // 默认9 | |||
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有 | |||
sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有 | |||
success: (res) => { | |||
self.src = res.tempFilePaths[0]; | |||
// 获取裁剪图片资源后,给data添加src属性及其值 | |||
self.cropper.pushOrign(this.src); | |||
} | |||
}); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped> | |||
@import '../../libs/css/style.components.scss'; | |||
.content { | |||
background: rgba(255, 255, 255, 1); | |||
} | |||
.cropper { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
z-index: 11; | |||
} | |||
.cropper-buttons { | |||
background-color: #000000; | |||
color: #eee; | |||
} | |||
.cropper-wrapper { | |||
position: relative; | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-between; | |||
align-items: center; | |||
width: 100%; | |||
background-color: #000; | |||
} | |||
.cropper-buttons { | |||
width: 100vw; | |||
display: flex; | |||
flex-direction: row; | |||
justify-content: space-between; | |||
align-items: center; | |||
position: fixed; | |||
bottom: 0; | |||
left: 0; | |||
font-size: 28rpx; | |||
} | |||
.cropper-buttons .upload, | |||
.cropper-buttons .getCropperImage { | |||
width: 50%; | |||
text-align: center; | |||
} | |||
.cropper-buttons .upload { | |||
text-align: left; | |||
padding-left: 50rpx; | |||
} | |||
.cropper-buttons .getCropperImage { | |||
text-align: right; | |||
padding-right: 50rpx; | |||
} | |||
</style> |
@@ -0,0 +1,147 @@ | |||
<template> | |||
<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{ | |||
bottom: bottom + 'rpx', | |||
right: right + 'rpx', | |||
borderRadius: mode == 'circle' ? '10000rpx' : '8rpx', | |||
zIndex: uZIndex, | |||
opacity: opacity | |||
}, customStyle]"> | |||
<view class="" v-if="!$slots.default"> | |||
<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon> | |||
<view class="u-back-top__tips"> | |||
{{tips}} | |||
</view> | |||
</view> | |||
<slot v-else /> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'u-back-top', | |||
props: { | |||
// 返回顶部的形状,circle-圆形,square-方形 | |||
mode: { | |||
type: String, | |||
default: 'circle' | |||
}, | |||
// 自定义图标 | |||
icon: { | |||
type: String, | |||
default: 'arrow-upward' | |||
}, | |||
// 提示文字 | |||
tips: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 返回顶部滚动时间 | |||
duration: { | |||
type: [Number, String], | |||
default: 100 | |||
}, | |||
// 滚动距离 | |||
scrollTop: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 距离顶部多少距离显示,单位rpx | |||
top: { | |||
type: [Number, String], | |||
default: 400 | |||
}, | |||
// 返回顶部按钮到底部的距离,单位rpx | |||
bottom: { | |||
type: [Number, String], | |||
default: 200 | |||
}, | |||
// 返回顶部按钮到右边的距离,单位rpx | |||
right: { | |||
type: [Number, String], | |||
default: 40 | |||
}, | |||
// 层级 | |||
zIndex: { | |||
type: [Number, String], | |||
default: '9' | |||
}, | |||
// 图标的样式,对象形式 | |||
iconStyle: { | |||
type: Object, | |||
default() { | |||
return { | |||
color: '#909399', | |||
fontSize: '38rpx' | |||
} | |||
} | |||
}, | |||
// 整个组件的样式 | |||
customStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
} | |||
}, | |||
watch: { | |||
showBackTop(nVal, oVal) { | |||
// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度 | |||
// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果 | |||
if(nVal) { | |||
this.uZIndex = this.zIndex; | |||
this.opacity = 1; | |||
} else { | |||
this.uZIndex = -1; | |||
this.opacity = 0; | |||
} | |||
} | |||
}, | |||
computed: { | |||
showBackTop() { | |||
// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值 | |||
// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮 | |||
return this.scrollTop > uni.upx2px(this.top); | |||
}, | |||
}, | |||
data() { | |||
return { | |||
// 不透明度,为了让组件有一个显示和隐藏的过渡动画 | |||
opacity: 0, | |||
// 组件的z-index值,隐藏时设置为-1,就会看不到 | |||
uZIndex: -1 | |||
} | |||
}, | |||
methods: { | |||
backToTop() { | |||
uni.pageScrollTo({ | |||
scrollTop: 0, | |||
duration: this.duration | |||
}); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-back-top { | |||
width: 80rpx; | |||
height: 80rpx; | |||
position: fixed; | |||
z-index: 9; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
background-color: #E1E1E1; | |||
color: $u-content-color; | |||
align-items: center; | |||
transition: opacity 0.4s; | |||
&__tips { | |||
font-size: 24rpx; | |||
transform: scale(0.8); | |||
line-height: 1; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,213 @@ | |||
<template> | |||
<view v-if="show" class="u-badge" :class="[ | |||
isDot ? 'u-badge-dot' : '', | |||
size == 'mini' ? 'u-badge-mini' : '', | |||
type ? 'u-badge--bg--' + type : '' | |||
]" :style="[{ | |||
top: offset[0] + 'rpx', | |||
right: offset[1] + 'rpx', | |||
fontSize: fontSize + 'rpx', | |||
position: absolute ? 'absolute' : 'static', | |||
color: color, | |||
backgroundColor: bgColor | |||
}, boxStyle]" | |||
> | |||
{{showText}} | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* badge 角标 | |||
* @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。 | |||
* @tutorial https://www.uviewui.com/components/badge.html | |||
* @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏 | |||
* @property {Boolean} is-dot 不展示数字,只有一个小点(默认false) | |||
* @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true) | |||
* @property {String Number} overflow-count 展示封顶的数字值(默认99) | |||
* @property {String} type 使用预设的背景颜色(默认error) | |||
* @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false) | |||
* @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default) | |||
* @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20]) | |||
* @property {String} color 字体颜色(默认#ffffff) | |||
* @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效 | |||
* @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false) | |||
* @example <u-badge type="error" count="7"></u-badge> | |||
*/ | |||
export default { | |||
name: 'u-badge', | |||
props: { | |||
// primary,warning,success,error,info | |||
type: { | |||
type: String, | |||
default: 'error' | |||
}, | |||
// default, mini | |||
size: { | |||
type: String, | |||
default: 'default' | |||
}, | |||
//是否是圆点 | |||
isDot: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 显示的数值内容 | |||
count: { | |||
type: [Number, String], | |||
}, | |||
// 展示封顶的数字值 | |||
overflowCount: { | |||
type: Number, | |||
default: 99 | |||
}, | |||
// 当数值为 0 时,是否展示 Badge | |||
showZero: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 位置偏移 | |||
offset: { | |||
type: Array, | |||
default: () => { | |||
return [20, 20] | |||
} | |||
}, | |||
// 是否开启绝对定位,开启了offset才会起作用 | |||
absolute: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 字体大小 | |||
fontSize: { | |||
type: [String, Number], | |||
default: '24' | |||
}, | |||
// 字体演示 | |||
color: { | |||
type: String, | |||
default: '#ffffff' | |||
}, | |||
// badge的背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效 | |||
isCenter: { | |||
type: Boolean, | |||
default: false | |||
} | |||
}, | |||
computed: { | |||
// 是否将badge中心与父组件右上角重合 | |||
boxStyle() { | |||
let style = {}; | |||
if(this.isCenter) { | |||
style.top = 0; | |||
style.right = 0; | |||
// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半 | |||
style.transform = "translateY(-50%) translateX(50%)"; | |||
} else { | |||
style.top = this.offset[0] + 'rpx'; | |||
style.right = this.offset[1] + 'rpx'; | |||
style.transform = "translateY(0) translateX(0)"; | |||
} | |||
// 如果尺寸为mini,后接上scal() | |||
if(this.size == 'mini') { | |||
style.transform = style.transform + " scale(0.8)"; | |||
} | |||
return style; | |||
}, | |||
// isDot类型时,不显示文字 | |||
showText() { | |||
if(this.isDot) return ''; | |||
else { | |||
if(this.count > this.overflowCount) return `${this.overflowCount}+`; | |||
else return this.count; | |||
} | |||
}, | |||
// 是否显示组件 | |||
show() { | |||
// 如果count的值为0,并且showZero设置为false,不显示组件 | |||
if(this.count == 0 && this.showZero == false) return false; | |||
else return true; | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-badge { | |||
display: inline-flex; | |||
justify-content: center; | |||
align-items: center; | |||
line-height: 24rpx; | |||
padding: 4rpx 8rpx; | |||
border-radius: 100rpx; | |||
&--bg--primary { | |||
background-color: $u-type-primary; | |||
} | |||
&--bg--error { | |||
background-color: $u-type-error; | |||
} | |||
&--bg--success { | |||
background-color: $u-type-success; | |||
} | |||
&--bg--info { | |||
background-color: $u-type-info; | |||
} | |||
&--bg--warning { | |||
background-color: $u-type-warning; | |||
} | |||
} | |||
.u-badge-dot { | |||
height: 16rpx; | |||
width: 16rpx; | |||
border-radius: 100rpx; | |||
line-height: 1; | |||
} | |||
.u-badge-mini { | |||
transform: scale(0.8); | |||
transform-origin: center center; | |||
} | |||
// .u-primary { | |||
// background: $u-type-primary; | |||
// color: #fff; | |||
// } | |||
// .u-error { | |||
// background: $u-type-error; | |||
// color: #fff; | |||
// } | |||
// .u-warning { | |||
// background: $u-type-warning; | |||
// color: #fff; | |||
// } | |||
// .u-success { | |||
// background: $u-type-success; | |||
// color: #fff; | |||
// } | |||
// .u-black { | |||
// background: #585858; | |||
// color: #fff; | |||
// } | |||
.u-info { | |||
background: $u-type-info; | |||
color: #fff; | |||
} | |||
</style> |
@@ -0,0 +1,567 @@ | |||
<template> | |||
<button | |||
id="u-wave-btn" | |||
class="u-btn u-line-1 u-fix-ios-appearance" | |||
:class="[ | |||
'u-size-' + size, | |||
plain ? 'u-btn--' + type + '--plain' : '', | |||
loading ? 'u-loading' : '', | |||
shape == 'circle' ? 'u-round-circle' : '', | |||
hairLine ? showHairLineBorder : 'u-btn--bold-border', | |||
'u-btn--' + type, | |||
disabled ? `u-btn--${type}--disabled` : '', | |||
]" | |||
:disabled="disabled" | |||
: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" | |||
:style="[customStyle]" | |||
@tap.stop="click($event)" | |||
:hover-class="getHoverClass" | |||
:loading="loading" | |||
> | |||
<slot></slot> | |||
<view | |||
v-if="ripple" | |||
class="u-wave-ripple" | |||
:class="[waveActive ? 'u-wave-active' : '']" | |||
:style="{ | |||
top: rippleTop + 'px', | |||
left: rippleLeft + 'px', | |||
width: fields.targetWidth + 'px', | |||
height: fields.targetWidth + 'px', | |||
'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)' | |||
}" | |||
></view> | |||
</button> | |||
</template> | |||
<script> | |||
/** | |||
* button 按钮 | |||
* @description Button 按钮 | |||
* @tutorial https://www.uviewui.com/components/button.html | |||
* @property {String} size 按钮的大小 | |||
* @property {Boolean} ripple 是否开启点击水波纹效果 | |||
* @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效 | |||
* @property {String} type 按钮的样式类型 | |||
* @property {Boolean} plain 按钮是否镂空,背景色透明 | |||
* @property {Boolean} disabled 是否禁用 | |||
* @property {Boolean} hair-line 是否显示按钮的细边框(默认true) | |||
* @property {Boolean} shape 按钮外观形状,见文档说明 | |||
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) | |||
* @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | |||
* @property {String} open-type 开放能力 | |||
* @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持) | |||
* @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒 | |||
* @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒 | |||
* @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明 | |||
* @event {Function} click 按钮点击 | |||
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效 | |||
* @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo | |||
* @event {Function} error 当使用开放能力时,发生错误的回调 | |||
* @event {Function} opensetting 在打开授权设置页并关闭后回调 | |||
* @event {Function} launchapp 打开 APP 成功的回调 | |||
* @example <u-button>月落</u-button> | |||
*/ | |||
export default { | |||
name: 'u-button', | |||
props: { | |||
// 是否细边框 | |||
hairLine: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 按钮的预置样式,default,primary,error,warning,success | |||
type: { | |||
type: String, | |||
default: 'default' | |||
}, | |||
// 按钮尺寸,default,medium,mini | |||
size: { | |||
type: String, | |||
default: 'default' | |||
}, | |||
// 按钮形状,circle(两边为半圆),square(带圆角) | |||
shape: { | |||
type: String, | |||
default: 'square' | |||
}, | |||
// 按钮是否镂空 | |||
plain: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否禁止状态 | |||
disabled: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否加载中 | |||
loading: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 开放能力,具体请看uniapp稳定关于button组件部分说明 | |||
// https://uniapp.dcloud.io/component/button | |||
openType: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件 | |||
// 取值为submit(提交表单),reset(重置表单) | |||
formType: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 | |||
// 只微信小程序、QQ小程序有效 | |||
appParameter: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效 | |||
hoverStopPropagation: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效 | |||
lang: { | |||
type: String, | |||
default: 'en' | |||
}, | |||
// 会话来源,open-type="contact"时有效。只微信小程序有效 | |||
sessionFrom: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 会话内消息卡片标题,open-type="contact"时有效 | |||
// 默认当前标题,只微信小程序有效 | |||
sendMessageTitle: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效 | |||
// 默认当前分享路径,只微信小程序有效 | |||
sendMessagePath: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 会话内消息卡片图片,open-type="contact"时有效 | |||
// 默认当前页面截图,只微信小程序有效 | |||
sendMessageImg: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示, | |||
// 用户点击后可以快速发送小程序消息,open-type="contact"时有效 | |||
showMessageCard: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 手指按(触摸)按钮时按钮时的背景颜色 | |||
hoverBgColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 水波纹的背景颜色 | |||
rippleBgColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否开启水波纹效果 | |||
ripple: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 按下的类名 | |||
hoverClass: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 自定义样式,对象形式 | |||
customStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取 | |||
dataName: { | |||
type: String, | |||
default: '' | |||
} | |||
}, | |||
computed: { | |||
// 当没有传bgColor变量时,按钮按下去的颜色类名 | |||
getHoverClass() { | |||
// 如果开启水波纹效果,则不启用hover-class效果 | |||
if (this.loading || this.disabled || this.ripple || this.hoverClass) return ''; | |||
let hoverClass = ''; | |||
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover'; | |||
return hoverClass; | |||
}, | |||
// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象 | |||
showHairLineBorder() { | |||
if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) { | |||
return ''; | |||
} else { | |||
return 'u-hairline-border'; | |||
} | |||
} | |||
}, | |||
data() { | |||
return { | |||
rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离 | |||
rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离 | |||
fields: {}, // 波纹按钮节点信息 | |||
waveActive: false // 激活水波纹 | |||
}; | |||
}, | |||
methods: { | |||
// 按钮点击 | |||
click(e) { | |||
// 如果按钮时disabled和loading状态,不触发水波纹效果 | |||
if (this.loading === true || this.disabled === true) return; | |||
// 是否开启水波纹效果 | |||
if (this.ripple) { | |||
// 每次点击时,移除上一次的类,再次添加,才能触发动画效果 | |||
this.waveActive = false; | |||
this.$nextTick(function() { | |||
this.getWaveQuery(e); | |||
}); | |||
} | |||
this.$emit('click'); | |||
}, | |||
// 查询按钮的节点信息 | |||
getWaveQuery(e) { | |||
this.getElQuery().then(res => { | |||
// 查询返回的是一个数组节点 | |||
let data = res[0]; | |||
// 查询不到节点信息,不操作 | |||
if (!data.width || !data.width) return; | |||
// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边 | |||
// 最终的方形(变换后的圆形)才能覆盖整个按钮 | |||
data.targetWidth = data.height > data.width ? data.height : data.width; | |||
if (!data.targetWidth) return; | |||
this.fields = data; | |||
let touchesX = '', | |||
touchesY = ''; | |||
// #ifdef MP-BAIDU | |||
touchesX = e.changedTouches[0].clientX; | |||
touchesY = e.changedTouches[0].clientY; | |||
// #endif | |||
// #ifdef MP-ALIPAY | |||
touchesX = e.detail.clientX; | |||
touchesY = e.detail.clientY; | |||
// #endif | |||
// #ifndef MP-BAIDU || MP-ALIPAY | |||
touchesX = e.touches[0].clientX; | |||
touchesY = e.touches[0].clientY; | |||
// #endif | |||
// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top | |||
// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置 | |||
// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置 | |||
this.rippleTop = touchesY - data.top - data.targetWidth / 2; | |||
this.rippleLeft = touchesX - data.left - data.targetWidth / 2; | |||
this.$nextTick(() => { | |||
this.waveActive = true; | |||
}); | |||
}); | |||
}, | |||
// 获取节点信息 | |||
getElQuery() { | |||
return new Promise(resolve => { | |||
let queryInfo = ''; | |||
// 获取元素节点信息,请查看uniapp相关文档 | |||
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect | |||
queryInfo = uni.createSelectorQuery().in(this); | |||
//#ifdef MP-ALIPAY | |||
queryInfo = uni.createSelectorQuery(); | |||
//#endif | |||
queryInfo.select('.u-btn').boundingClientRect(); | |||
queryInfo.exec(data => { | |||
resolve(data); | |||
}); | |||
}); | |||
}, | |||
// 下面为对接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); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import '../../libs/css/style.components.scss'; | |||
.u-btn::after { | |||
border: none; | |||
} | |||
.u-btn { | |||
position: relative; | |||
border: 0; | |||
//border-radius: 10rpx; | |||
display: inline-block; | |||
overflow: hidden; | |||
line-height: 1; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
cursor: pointer; | |||
padding: 0 40rpx; | |||
z-index: 1; | |||
box-sizing: border-box; | |||
transition: all 0.15s; | |||
&--bold-border { | |||
border: 1px solid #ffffff; | |||
} | |||
&--default { | |||
color: $u-content-color; | |||
border-color: #c0c4cc; | |||
background-color: #ffffff; | |||
} | |||
&--primary { | |||
color: #ffffff; | |||
border-color: $u-type-primary; | |||
background-color: $u-type-primary; | |||
} | |||
&--success { | |||
color: #ffffff; | |||
border-color: $u-type-success; | |||
background-color: $u-type-success; | |||
} | |||
&--error { | |||
color: #ffffff; | |||
border-color: $u-type-error; | |||
background-color: $u-type-error; | |||
} | |||
&--warning { | |||
color: #ffffff; | |||
border-color: $u-type-warning; | |||
background-color: $u-type-warning; | |||
} | |||
&--default--disabled { | |||
color: #ffffff; | |||
border-color: #e4e7ed; | |||
background-color: #ffffff; | |||
} | |||
&--primary--disabled { | |||
color: #ffffff!important; | |||
border-color: $u-type-primary-disabled!important; | |||
background-color: $u-type-primary-disabled!important; | |||
} | |||
&--success--disabled { | |||
color: #ffffff!important; | |||
border-color: $u-type-success-disabled!important; | |||
background-color: $u-type-success-disabled!important; | |||
} | |||
&--error--disabled { | |||
color: #ffffff!important; | |||
border-color: $u-type-error-disabled!important; | |||
background-color: $u-type-error-disabled!important; | |||
} | |||
&--warning--disabled { | |||
color: #ffffff!important; | |||
border-color: $u-type-warning-disabled!important; | |||
background-color: $u-type-warning-disabled!important; | |||
} | |||
&--primary--plain { | |||
color: $u-type-primary!important; | |||
border-color: $u-type-primary-disabled!important; | |||
background-color: $u-type-primary-light!important; | |||
} | |||
&--success--plain { | |||
color: $u-type-success!important; | |||
border-color: $u-type-success-disabled!important; | |||
background-color: $u-type-success-light!important; | |||
} | |||
&--error--plain { | |||
color: $u-type-error!important; | |||
border-color: $u-type-error-disabled!important; | |||
background-color: $u-type-error-light!important; | |||
} | |||
&--warning--plain { | |||
color: $u-type-warning!important; | |||
border-color: $u-type-warning-disabled!important; | |||
background-color: $u-type-warning-light!important; | |||
} | |||
} | |||
.u-hairline-border:after { | |||
content: ' '; | |||
position: absolute; | |||
pointer-events: none; | |||
// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border) | |||
box-sizing: border-box; | |||
// 中心点作为变形(scale())的原点 | |||
-webkit-transform-origin: 0 0; | |||
transform-origin: 0 0; | |||
left: 0; | |||
top: 0; | |||
width: 199.8%; | |||
height: 199.7%; | |||
-webkit-transform: scale(0.5, 0.5); | |||
transform: scale(0.5, 0.5); | |||
border: 1px solid currentColor; | |||
z-index: 1; | |||
} | |||
.u-wave-ripple { | |||
z-index: 0; | |||
position: absolute; | |||
border-radius: 100%; | |||
background-clip: padding-box; | |||
pointer-events: none; | |||
user-select: none; | |||
transform: scale(0); | |||
opacity: 1; | |||
transform-origin: center; | |||
} | |||
.u-wave-ripple.u-wave-active { | |||
opacity: 0; | |||
transform: scale(2); | |||
transition: opacity 1s linear, transform 0.4s linear; | |||
} | |||
.u-round-circle { | |||
border-radius: 100rpx; | |||
} | |||
.u-round-circle::after { | |||
border-radius: 100rpx; | |||
} | |||
.u-loading::after { | |||
background-color: hsla(0, 0%, 100%, 0.35); | |||
} | |||
.u-size-default { | |||
font-size: 30rpx; | |||
height: 80rpx; | |||
line-height: 80rpx; | |||
} | |||
.u-size-medium { | |||
display: inline-flex; | |||
width: auto; | |||
font-size: 26rpx; | |||
height: 70rpx; | |||
line-height: 70rpx; | |||
padding: 0 80rpx; | |||
} | |||
.u-size-mini { | |||
display: inline-flex; | |||
width: auto; | |||
font-size: 22rpx; | |||
padding-top: 1px; | |||
height: 50rpx; | |||
line-height: 50rpx; | |||
padding: 0 20rpx; | |||
} | |||
.u-primary-plain-hover { | |||
color: #ffffff !important; | |||
background: $u-type-primary-dark !important; | |||
} | |||
.u-default-plain-hover { | |||
color: $u-type-primary-dark !important; | |||
background: $u-type-primary-light !important; | |||
} | |||
.u-success-plain-hover { | |||
color: #ffffff !important; | |||
background: $u-type-success-dark !important; | |||
} | |||
.u-warning-plain-hover { | |||
color: #ffffff !important; | |||
background: $u-type-warning-dark !important; | |||
} | |||
.u-error-plain-hover { | |||
color: #ffffff !important; | |||
background: $u-type-error-dark !important; | |||
} | |||
.u-info-plain-hover { | |||
color: #ffffff !important; | |||
background: $u-type-info-dark !important; | |||
} | |||
.u-default-hover { | |||
color: $u-type-primary-dark !important; | |||
border-color: $u-type-primary-dark !important; | |||
background-color: $u-type-primary-light !important; | |||
} | |||
.u-primary-hover { | |||
background: $u-type-primary-dark !important; | |||
color: #fff; | |||
} | |||
.u-success-hover { | |||
background: $u-type-success-dark !important; | |||
color: #fff; | |||
} | |||
.u-info-hover { | |||
background: $u-type-info-dark !important; | |||
color: #fff; | |||
} | |||
.u-warning-hover { | |||
background: $u-type-warning-dark !important; | |||
color: #fff; | |||
} | |||
.u-error-hover { | |||
background: $u-type-error-dark !important; | |||
color: #fff; | |||
} | |||
</style> |
@@ -0,0 +1,641 @@ | |||
<template> | |||
<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" | |||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable"> | |||
<view class="u-calendar"> | |||
<view class="u-calendar__header"> | |||
<view class="u-calendar__header__text" v-if="!$slots['tooltip']"> | |||
{{toolTip}} | |||
</view> | |||
<slot v-else name="tooltip" /> | |||
</view> | |||
<view class="u-calendar__action u-flex u-row-center"> | |||
<view class="u-calendar__action__icon"> | |||
<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon> | |||
</view> | |||
<view class="u-calendar__action__icon"> | |||
<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon> | |||
</view> | |||
<view class="u-calendar__action__text">{{ showTitle }}</view> | |||
<view class="u-calendar__action__icon"> | |||
<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon> | |||
</view> | |||
<view class="u-calendar__action__icon"> | |||
<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon> | |||
</view> | |||
</view> | |||
<view class="u-calendar__week-day"> | |||
<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view> | |||
</view> | |||
<view class="u-calendar__content"> | |||
<!-- 前置空白部分 --> | |||
<block v-for="(item, index) in weekdayArr" :key="index"> | |||
<view class="u-calendar__content__item"></view> | |||
</block> | |||
<view class="u-calendar__content__item" :class="{ | |||
'u-hover-class':openDisAbled(year,month,index+1), | |||
'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date', | |||
'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date' | |||
}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index" | |||
@tap="dateClick(index)"> | |||
<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}"> | |||
<view>{{ index + 1 }}</view> | |||
</view> | |||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view> | |||
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view> | |||
</view> | |||
<view class="u-calendar__content__bg-month">{{month}}</view> | |||
</view> | |||
<view class="u-calendar__bottom"> | |||
<view class="u-calendar__bottom__choose"> | |||
<text>{{mode == 'date' ? activeDate : startDate}}</text> | |||
<text v-if="endDate">至{{endDate}}</text> | |||
</view> | |||
<view class="u-calendar__bottom__btn"> | |||
<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button> | |||
</view> | |||
</view> | |||
</view> | |||
</u-popup> | |||
</template> | |||
<script> | |||
/** | |||
* calendar 日历 | |||
* @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。 | |||
* @tutorial http://uviewui.com/components/calendar.html | |||
* @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围 | |||
* @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起 | |||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) | |||
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true) | |||
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true) | |||
* @property {String Number} max-year 可切换的最大年份(默认2050) | |||
* @property {String Number} min-year 最小可选日期(默认1950) | |||
* @property {String Number} min-date 可切换的最小年份(默认1950-01-01) | |||
* @property {String Number} max-date 最大可选日期(默认当前日期) | |||
* @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20) | |||
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true) | |||
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266) | |||
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399) | |||
* @property {String} color 日期字体的默认颜色(默认#303133) | |||
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff) | |||
* @property {String Number} z-index 弹出时的z-index值(默认10075) | |||
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff) | |||
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13)) | |||
* @property {String} range-color 选择范围内字体颜色(默认#2979ff) | |||
* @property {String} start-text 起始日期底部的提示文字(默认 '开始') | |||
* @property {String} end-text 结束日期底部的提示文字(默认 '结束') | |||
* @property {String} btn-type 底部确定按钮的主题(默认 'primary') | |||
* @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期') | |||
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true) | |||
* @example <u-calendar v-model="show" :mode="mode"></u-calendar> | |||
*/ | |||
export default { | |||
name: 'u-calendar', | |||
props: { | |||
safeAreaInsetBottom: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否允许通过点击遮罩关闭Picker | |||
maskCloseAble: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 通过双向绑定控制组件的弹出与收起 | |||
value: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 弹出的z-index值 | |||
zIndex: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 是否允许切换年份 | |||
changeYear: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否允许切换月份 | |||
changeMonth: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// date-单个日期选择,range-开始日期+结束日期选择 | |||
mode: { | |||
type: String, | |||
default: 'date' | |||
}, | |||
// 可切换的最大年份 | |||
maxYear: { | |||
type: [Number, String], | |||
default: 2050 | |||
}, | |||
// 可切换的最小年份 | |||
minYear: { | |||
type: [Number, String], | |||
default: 1950 | |||
}, | |||
// 最小可选日期(不在范围内日期禁用不可选) | |||
minDate: { | |||
type: [Number, String], | |||
default: '1950-01-01' | |||
}, | |||
/** | |||
* 最大可选日期 | |||
* 默认最大值为今天,之后的日期不可选 | |||
* 2030-12-31 | |||
* */ | |||
maxDate: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
// 弹窗顶部左右两边的圆角值 | |||
borderRadius: { | |||
type: [String, Number], | |||
default: 20 | |||
}, | |||
// 月份切换按钮箭头颜色 | |||
monthArrowColor: { | |||
type: String, | |||
default: '#606266' | |||
}, | |||
// 年份切换按钮箭头颜色 | |||
yearArrowColor: { | |||
type: String, | |||
default: '#909399' | |||
}, | |||
// 默认日期字体颜色 | |||
color: { | |||
type: String, | |||
default: '#303133' | |||
}, | |||
// 选中|起始结束日期背景色 | |||
activeBgColor: { | |||
type: String, | |||
default: '#2979ff' | |||
}, | |||
// 选中|起始结束日期字体颜色 | |||
activeColor: { | |||
type: String, | |||
default: '#ffffff' | |||
}, | |||
// 范围内日期背景色 | |||
rangeBgColor: { | |||
type: String, | |||
default: 'rgba(41,121,255,0.13)' | |||
}, | |||
// 范围内日期字体颜色 | |||
rangeColor: { | |||
type: String, | |||
default: '#2979ff' | |||
}, | |||
// mode=range时生效,起始日期自定义文案 | |||
startText: { | |||
type: String, | |||
default: '开始' | |||
}, | |||
// mode=range时生效,结束日期自定义文案 | |||
endText: { | |||
type: String, | |||
default: '结束' | |||
}, | |||
//按钮样式类型 | |||
btnType: { | |||
type: String, | |||
default: 'primary' | |||
}, | |||
// 当前选中日期带选中效果 | |||
isActiveCurrent: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 切换年月是否触发事件 mode=date时生效 | |||
isChange: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否显示右上角的关闭图标 | |||
closeable: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 顶部的提示文字 | |||
toolTip: { | |||
type: String, | |||
default: '选择日期' | |||
} | |||
}, | |||
data() { | |||
return { | |||
// 星期几,值为1-7 | |||
weekday: 1, | |||
weekdayArr:[], | |||
// 当前月有多少天 | |||
days: 0, | |||
daysArr:[], | |||
showTitle: '', | |||
year: 2020, | |||
month: 0, | |||
day: 0, | |||
startYear: 0, | |||
startMonth: 0, | |||
startDay: 0, | |||
endYear: 0, | |||
endMonth: 0, | |||
endDay: 0, | |||
today: '', | |||
activeDate: '', | |||
startDate: '', | |||
endDate: '', | |||
isStart: true, | |||
min: null, | |||
max: null, | |||
weekDayZh: ['日', '一', '二', '三', '四', '五', '六'] | |||
}; | |||
}, | |||
computed: { | |||
dataChange() { | |||
return `${this.mode}-${this.minDate}-${this.maxDate}`; | |||
}, | |||
uZIndex() { | |||
// 如果用户有传递z-index值,优先使用 | |||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | |||
} | |||
}, | |||
watch: { | |||
dataChange(val) { | |||
this.init() | |||
} | |||
}, | |||
created() { | |||
this.init() | |||
}, | |||
methods: { | |||
getColor(index, type) { | |||
let color = type == 1 ? '' : this.color; | |||
let day = index + 1 | |||
let date = `${this.year}-${this.month}-${day}` | |||
let timestamp = new Date(date.replace(/\-/g, '/')).getTime(); | |||
let start = this.startDate.replace(/\-/g, '/') | |||
let end = this.endDate.replace(/\-/g, '/') | |||
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) { | |||
color = type == 1 ? this.activeBgColor : this.activeColor; | |||
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) { | |||
color = type == 1 ? this.rangeBgColor : this.rangeColor; | |||
} | |||
return color; | |||
}, | |||
init() { | |||
let now = new Date(); | |||
this.year = now.getFullYear(); | |||
this.month = now.getMonth() + 1; | |||
this.day = now.getDate(); | |||
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`; | |||
this.activeDate = this.today; | |||
this.min = this.initDate(this.minDate); | |||
this.max = this.initDate(this.maxDate || this.today); | |||
this.startDate = ""; | |||
this.startYear = 0; | |||
this.startMonth = 0; | |||
this.startDay = 0; | |||
this.endYear = 0; | |||
this.endMonth = 0; | |||
this.endDay = 0; | |||
this.endDate = ""; | |||
this.isStart = true; | |||
this.changeData(); | |||
}, | |||
//日期处理 | |||
initDate(date) { | |||
let fdate = date.split('-'); | |||
return { | |||
year: Number(fdate[0] || 1920), | |||
month: Number(fdate[1] || 1), | |||
day: Number(fdate[2] || 1) | |||
} | |||
}, | |||
openDisAbled: function(year, month, day) { | |||
let bool = true; | |||
let date = `${year}/${month}/${day}`; | |||
// let today = this.today.replace(/\-/g, '/'); | |||
let min = `${this.min.year}/${this.min.month}/${this.min.day}`; | |||
let max = `${this.max.year}/${this.max.month}/${this.max.day}`; | |||
let timestamp = new Date(date).getTime(); | |||
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) { | |||
bool = false; | |||
} | |||
return bool; | |||
}, | |||
generateArray: function(start, end) { | |||
return Array.from(new Array(end + 1).keys()).slice(start); | |||
}, | |||
formatNum: function(num) { | |||
return num < 10 ? '0' + num : num + ''; | |||
}, | |||
//一个月有多少天 | |||
getMonthDay(year, month) { | |||
let days = new Date(year, month, 0).getDate(); | |||
return days; | |||
}, | |||
getWeekday(year, month) { | |||
let date = new Date(`${year}/${month}/01 00:00:00`); | |||
return date.getDay(); | |||
}, | |||
checkRange(year) { | |||
let overstep = false; | |||
if (year < this.minYear || year > this.maxYear) { | |||
uni.showToast({ | |||
title: "日期超出范围啦~", | |||
icon: 'none' | |||
}) | |||
overstep = true; | |||
} | |||
return overstep; | |||
}, | |||
changeMonthHandler(isAdd) { | |||
if (isAdd) { | |||
let month = this.month + 1; | |||
let year = month > 12 ? this.year + 1 : this.year; | |||
if (!this.checkRange(year)) { | |||
this.month = month > 12 ? 1 : month; | |||
this.year = year; | |||
this.changeData(); | |||
} | |||
} else { | |||
let month = this.month - 1; | |||
let year = month < 1 ? this.year - 1 : this.year; | |||
if (!this.checkRange(year)) { | |||
this.month = month < 1 ? 12 : month; | |||
this.year = year; | |||
this.changeData(); | |||
} | |||
} | |||
}, | |||
changeYearHandler(isAdd) { | |||
let year = isAdd ? this.year + 1 : this.year - 1; | |||
if (!this.checkRange(year)) { | |||
this.year = year; | |||
this.changeData(); | |||
} | |||
}, | |||
changeData() { | |||
this.days = this.getMonthDay(this.year, this.month); | |||
this.daysArr=this.generateArray(1,this.days) | |||
this.weekday = this.getWeekday(this.year, this.month); | |||
this.weekdayArr=this.generateArray(1,this.weekday) | |||
this.showTitle = `${this.year}年${this.month}月`; | |||
if (this.isChange && this.mode == 'date') { | |||
this.btnFix(true); | |||
} | |||
}, | |||
dateClick: function(day) { | |||
day += 1; | |||
if (!this.openDisAbled(this.year, this.month, day)) { | |||
this.day = day; | |||
let date = `${this.year}-${this.month}-${day}`; | |||
if (this.mode == 'date') { | |||
this.activeDate = date; | |||
} else { | |||
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime() | |||
if (this.isStart || compare) { | |||
this.startDate = date; | |||
this.startYear = this.year; | |||
this.startMonth = this.month; | |||
this.startDay = this.day; | |||
this.endYear = 0; | |||
this.endMonth = 0; | |||
this.endDay = 0; | |||
this.endDate = ""; | |||
this.activeDate = ""; | |||
this.isStart = false; | |||
} else { | |||
this.endDate = date; | |||
this.endYear = this.year; | |||
this.endMonth = this.month; | |||
this.endDay = this.day; | |||
this.isStart = true; | |||
} | |||
} | |||
} | |||
}, | |||
close() { | |||
// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗 | |||
this.$emit('input', false); | |||
}, | |||
getWeekText(date) { | |||
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`); | |||
let week = date.getDay(); | |||
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week]; | |||
}, | |||
btnFix(show) { | |||
if (!show) { | |||
this.close(); | |||
} | |||
if (this.mode == 'date') { | |||
let arr = this.activeDate.split('-') | |||
let year = this.isChange ? this.year : Number(arr[0]); | |||
let month = this.isChange ? this.month : Number(arr[1]); | |||
let day = this.isChange ? this.day : Number(arr[2]); | |||
//当前月有多少天 | |||
let days = this.getMonthDay(year, month); | |||
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`; | |||
let weekText = this.getWeekText(result); | |||
let isToday = false; | |||
if (`${year}-${month}-${day}` == this.today) { | |||
//今天 | |||
isToday = true; | |||
} | |||
this.$emit('change', { | |||
year: year, | |||
month: month, | |||
day: day, | |||
days: days, | |||
result: result, | |||
week: weekText, | |||
isToday: isToday, | |||
// switch: show //是否是切换年月操作 | |||
}); | |||
} else { | |||
if (!this.startDate || !this.endDate) return; | |||
let startMonth = this.formatNum(this.startMonth); | |||
let startDay = this.formatNum(this.startDay); | |||
let startDate = `${this.startYear}-${startMonth}-${startDay}`; | |||
let startWeek = this.getWeekText(startDate) | |||
let endMonth = this.formatNum(this.endMonth); | |||
let endDay = this.formatNum(this.endDay); | |||
let endDate = `${this.endYear}-${endMonth}-${endDay}`; | |||
let endWeek = this.getWeekText(endDate); | |||
this.$emit('change', { | |||
startYear: this.startYear, | |||
startMonth: this.startMonth, | |||
startDay: this.startDay, | |||
startDate: startDate, | |||
startWeek: startWeek, | |||
endYear: this.endYear, | |||
endMonth: this.endMonth, | |||
endDay: this.endDay, | |||
endDate: endDate, | |||
endWeek: endWeek | |||
}); | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-calendar { | |||
color: $u-content-color; | |||
&__header { | |||
width: 100%; | |||
box-sizing: border-box; | |||
font-size: 30rpx; | |||
background-color: #fff; | |||
color: $u-main-color; | |||
&__text { | |||
margin-top: 30rpx; | |||
padding: 0 60rpx; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
} | |||
} | |||
&__action { | |||
padding: 40rpx 0 40rpx 0; | |||
&__icon { | |||
margin: 0 16rpx; | |||
} | |||
&__text { | |||
padding: 0 16rpx; | |||
color: $u-main-color; | |||
font-size: 32rpx; | |||
line-height: 32rpx; | |||
font-weight: bold; | |||
} | |||
} | |||
&__week-day { | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
padding: 6px 0; | |||
overflow: hidden; | |||
&__text { | |||
flex: 1; | |||
text-align: center; | |||
} | |||
} | |||
&__content { | |||
width: 100%; | |||
display: flex; | |||
flex-wrap: wrap; | |||
padding: 6px 0; | |||
box-sizing: border-box; | |||
background-color: #fff; | |||
position: relative; | |||
&--end-date { | |||
border-top-right-radius: 8rpx; | |||
border-bottom-right-radius: 8rpx; | |||
} | |||
&--start-date { | |||
border-top-left-radius: 8rpx; | |||
border-bottom-left-radius: 8rpx; | |||
} | |||
&__item { | |||
width: 14.2857%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
padding: 6px 0; | |||
overflow: hidden; | |||
position: relative; | |||
z-index: 2; | |||
&__inner { | |||
height: 84rpx; | |||
display: -webkit-box; | |||
display: -webkit-flex; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
flex-direction: column; | |||
font-size: 32rpx; | |||
position: relative; | |||
border-radius: 50%; | |||
&__desc { | |||
width: 100%; | |||
font-size: 24rpx; | |||
line-height: 24rpx; | |||
transform: scale(0.75); | |||
transform-origin: center center; | |||
position: absolute; | |||
left: 0; | |||
text-align: center; | |||
bottom: 2rpx; | |||
} | |||
} | |||
&__tips { | |||
width: 100%; | |||
font-size: 24rpx; | |||
line-height: 24rpx; | |||
position: absolute; | |||
left: 0; | |||
transform: scale(0.8); | |||
transform-origin: center center; | |||
text-align: center; | |||
bottom: 8rpx; | |||
z-index: 2; | |||
} | |||
} | |||
&__bg-month { | |||
position: absolute; | |||
font-size: 130px; | |||
line-height: 130px; | |||
left: 50%; | |||
top: 50%; | |||
transform: translate(-50%, -50%); | |||
color: #e4e7ed; | |||
z-index: 1; | |||
} | |||
} | |||
&__bottom { | |||
width: 100%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
flex-direction: column; | |||
background-color: #fff; | |||
padding: 0 40rpx 30rpx; | |||
box-sizing: border-box; | |||
font-size: 24rpx; | |||
color: $u-tips-color; | |||
&__choose { | |||
height: 50rpx; | |||
} | |||
&__btn { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,254 @@ | |||
<template> | |||
<view class="u-keyboard" @touchmove.stop.prevent> | |||
<view class="u-keyboard-grids"> | |||
<block> | |||
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i"> | |||
<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn" | |||
v-for="(item, j) in group" :key="j"> | |||
{{ item }} | |||
</view> | |||
</view> | |||
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back" | |||
hover-class="u-hover-class"> | |||
<u-icon :size="38" name="backspace" :bold="true"></u-icon> | |||
</view> | |||
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode"> | |||
<text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text> | |||
/ | |||
<text class="en" :class="[abc ? 'active' : 'inactive']">英</text> | |||
</view> | |||
</block> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
name: "u-keyboard", | |||
props: { | |||
// 是否打乱键盘按键的顺序 | |||
random: { | |||
type: Boolean, | |||
default: false | |||
} | |||
}, | |||
data() { | |||
return { | |||
// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称 | |||
abc: false | |||
}; | |||
}, | |||
computed: { | |||
areaList() { | |||
let data = [ | |||
'京', | |||
'沪', | |||
'粤', | |||
'津', | |||
'冀', | |||
'豫', | |||
'云', | |||
'辽', | |||
'黑', | |||
'湘', | |||
'皖', | |||
'鲁', | |||
'苏', | |||
'浙', | |||
'赣', | |||
'鄂', | |||
'桂', | |||
'甘', | |||
'晋', | |||
'陕', | |||
'蒙', | |||
'吉', | |||
'闽', | |||
'贵', | |||
'渝', | |||
'川', | |||
'青', | |||
'琼', | |||
'宁', | |||
'挂', | |||
'藏', | |||
'港', | |||
'澳', | |||
'新', | |||
'使', | |||
'学' | |||
]; | |||
let tmp = []; | |||
// 打乱顺序 | |||
if (this.random) data = this.$u.randomArray(data); | |||
// 切割成二维数组 | |||
tmp[0] = data.slice(0, 10); | |||
tmp[1] = data.slice(10, 20); | |||
tmp[2] = data.slice(20, 30); | |||
tmp[3] = data.slice(30, 36); | |||
return tmp; | |||
}, | |||
EngKeyBoardList() { | |||
let data = [ | |||
1, | |||
2, | |||
3, | |||
4, | |||
5, | |||
6, | |||
7, | |||
8, | |||
9, | |||
0, | |||
'Q', | |||
'W', | |||
'E', | |||
'R', | |||
'T', | |||
'Y', | |||
'U', | |||
'I', | |||
'O', | |||
'P', | |||
'A', | |||
'S', | |||
'D', | |||
'F', | |||
'G', | |||
'H', | |||
'J', | |||
'K', | |||
'L', | |||
'Z', | |||
'X', | |||
'C', | |||
'V', | |||
'B', | |||
'N', | |||
'M' | |||
]; | |||
let tmp = []; | |||
if (this.random) data = this.$u.randomArray(data); | |||
tmp[0] = data.slice(0, 10); | |||
tmp[1] = data.slice(10, 20); | |||
tmp[2] = data.slice(20, 30); | |||
tmp[3] = data.slice(30, 36); | |||
return tmp; | |||
} | |||
}, | |||
methods: { | |||
// 点击键盘按钮 | |||
carInputClick(i, j) { | |||
let value = ''; | |||
// 不同模式,获取不同数组的值 | |||
if (this.abc) value = this.EngKeyBoardList[i][j]; | |||
else value = this.areaList[i][j]; | |||
this.$emit('change', value); | |||
}, | |||
// 修改汽车牌键盘的输入模式,中文|英文 | |||
changeCarInputMode() { | |||
this.abc = !this.abc; | |||
}, | |||
// 点击退格键 | |||
backspaceClick() { | |||
this.$emit('backspace'); | |||
clearInterval(this.timer); //再次清空定时器,防止重复注册定时器 | |||
this.timer = null; | |||
this.timer = setInterval(() => { | |||
this.$emit('backspace'); | |||
}, 250); | |||
}, | |||
clearTimer() { | |||
clearInterval(this.timer); | |||
this.timer = null; | |||
}, | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-keyboard-grids { | |||
background: rgb(215, 215, 217); | |||
padding: 24rpx 0; | |||
position: relative; | |||
} | |||
.u-keyboard-grids-item { | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
.u-keyboard-grids-btn { | |||
text-decoration: none; | |||
width: 62rpx; | |||
flex: 0 0 64rpx; | |||
height: 80rpx; | |||
display: inline-block; | |||
font-size: 36rpx; | |||
text-align: center; | |||
line-height: 80rpx; | |||
background-color: #fff; | |||
margin: 8rpx 5rpx; | |||
border-radius: 8rpx; | |||
box-shadow: 0 2rpx 0rpx #888992; | |||
font-weight: 500; | |||
} | |||
.u-carinput-hover { | |||
background-color: rgb(185, 188, 195) !important; | |||
} | |||
.u-keyboard-back { | |||
position: absolute; | |||
width: 96rpx; | |||
right: 22rpx; | |||
bottom: 32rpx; | |||
height: 80rpx; | |||
background-color: rgb(185, 188, 195); | |||
display: flex; | |||
align-items: center; | |||
border-radius: 8rpx; | |||
justify-content: center; | |||
box-shadow: 0 2rpx 0rpx #888992; | |||
} | |||
.u-keyboard-change { | |||
font-size: 24rpx; | |||
box-shadow: 0 2rpx 0rpx #888992; | |||
position: absolute; | |||
width: 96rpx; | |||
left: 22rpx; | |||
line-height: 1; | |||
bottom: 32rpx; | |||
height: 80rpx; | |||
background-color: #ffffff; | |||
display: flex; | |||
align-items: center; | |||
border-radius: 8rpx; | |||
justify-content: center; | |||
} | |||
.u-keyboard-change .inactive.zh { | |||
transform: scale(0.85) translateY(-10rpx); | |||
} | |||
.u-keyboard-change .inactive.en { | |||
transform: scale(0.85) translateY(10rpx); | |||
} | |||
.u-keyboard-change .active { | |||
color: rgb(237, 112, 64); | |||
font-size: 30rpx; | |||
} | |||
.u-keyboard-change .zh { | |||
transform: translateY(-10rpx); | |||
} | |||
.u-keyboard-change .en { | |||
transform: translateY(10rpx); | |||
} | |||
</style> |
@@ -0,0 +1,291 @@ | |||
<template> | |||
<view | |||
class="u-card" | |||
@tap.stop="click" | |||
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }" | |||
:style="{ | |||
borderRadius: borderRadius + 'rpx', | |||
margin: margin | |||
}" | |||
> | |||
<view | |||
v-if="showHead" | |||
class="u-card__head" | |||
:style="[{padding: padding + 'rpx'}, headStyle]" | |||
:class="{ | |||
'u-border-bottom': headBorderBottom | |||
}" | |||
@tap="headClick" | |||
> | |||
<view v-if="!$slots.head" class="u-flex u-row-between"> | |||
<view class="u-card__head--left u-flex u-line-1" v-if="title"> | |||
<image | |||
:src="thumb" | |||
class="u-card__head--left__thumb" | |||
mode="aspectfull" | |||
v-if="thumb" | |||
:style="{ | |||
height: thumbWidth + 'rpx', | |||
width: thumbWidth + 'rpx', | |||
borderRadius: thumbCircle ? '100rpx' : '6rpx' | |||
}" | |||
></image> | |||
<text | |||
class="u-card__head--left__title u-line-1" | |||
:style="{ | |||
fontSize: titleSize + 'rpx', | |||
color: titleColor | |||
}" | |||
> | |||
{{ title }} | |||
</text> | |||
</view> | |||
<view class="u-card__head--right u-line-1" v-if="subTitle"> | |||
<text | |||
class="u-card__head__title__text" | |||
:style="{ | |||
fontSize: subTitleSize + 'rpx', | |||
color: subTitleColor | |||
}" | |||
> | |||
{{ subTitle }} | |||
</text> | |||
</view> | |||
</view> | |||
<slot name="head" v-else /> | |||
</view> | |||
<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view> | |||
<view | |||
v-if="showFoot" | |||
class="u-card__foot" | |||
@tap="footClick" | |||
:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]" | |||
:class="{ | |||
'u-border-top': footBorderTop | |||
}" | |||
> | |||
<slot name="foot" /> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* card 卡片 | |||
* @description 卡片组件一般用于多个列表条目,且风格统一的场景 | |||
* @tutorial https://www.uviewui.com/components/line.html | |||
* @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false) | |||
* @property {String} title 头部左边的标题 | |||
* @property {String} title-color 标题颜色(默认#303133) | |||
* @property {String | Number} title-size 标题字体大小,单位rpx(默认30) | |||
* @property {String} sub-title 头部右边的副标题 | |||
* @property {String} sub-title-color 副标题颜色(默认#909399) | |||
* @property {String | Number} sub-title-size 副标题字体大小(默认26) | |||
* @property {Boolean} border 是否显示边框(默认true) | |||
* @property {String | Number} index 用于标识点击了第几个卡片 | |||
* @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx) | |||
* @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16) | |||
* @property {Object} head-style 头部自定义样式,对象形式 | |||
* @property {Object} body-style 中部自定义样式,对象形式 | |||
* @property {Object} foot-style 底部自定义样式,对象形式 | |||
* @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true) | |||
* @property {Boolean} foot-border-top 是否显示底部的上边框(默认true) | |||
* @property {Boolean} show-head 是否显示头部(默认true) | |||
* @property {Boolean} show-head 是否显示尾部(默认true) | |||
* @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径 | |||
* @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60) | |||
* @property {Boolean} thumb-circle 缩略图是否为圆形(默认false) | |||
* @event {Function} click 整个卡片任意位置被点击时触发 | |||
* @event {Function} head-click 卡片头部被点击时触发 | |||
* @event {Function} body-click 卡片主体部分被点击时触发 | |||
* @event {Function} foot-click 卡片底部部分被点击时触发 | |||
* @example <u-card padding="30" title="card"></u-card> | |||
*/ | |||
export default { | |||
name: 'u-card', | |||
props: { | |||
// 与屏幕两侧是否留空隙 | |||
full: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 标题 | |||
title: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 标题颜色 | |||
titleColor: { | |||
type: String, | |||
default: '#303133' | |||
}, | |||
// 标题字体大小,单位rpx | |||
titleSize: { | |||
type: [Number, String], | |||
default: '30' | |||
}, | |||
// 副标题 | |||
subTitle: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 副标题颜色 | |||
subTitleColor: { | |||
type: String, | |||
default: '#909399' | |||
}, | |||
// 副标题字体大小,单位rpx | |||
subTitleSize: { | |||
type: [Number, String], | |||
default: '26' | |||
}, | |||
// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时) | |||
border: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 用于标识点击了第几个 | |||
index: { | |||
type: [Number, String, Object], | |||
default: '' | |||
}, | |||
// 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx" | |||
margin: { | |||
type: String, | |||
default: '30rpx' | |||
}, | |||
// card卡片的圆角 | |||
borderRadius: { | |||
type: [Number, String], | |||
default: '16' | |||
}, | |||
// 头部自定义样式,对象形式 | |||
headStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 主体自定义样式,对象形式 | |||
bodyStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 底部自定义样式,对象形式 | |||
footStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 头部是否下边框 | |||
headBorderBottom: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 底部是否有上边框 | |||
footBorderTop: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 标题左边的缩略图 | |||
thumb: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 缩略图宽高,单位rpx | |||
thumbWidth: { | |||
type: [String, Number], | |||
default: '60' | |||
}, | |||
// 缩略图是否为圆形 | |||
thumbCircle: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 给head,body,foot的内边距 | |||
padding: { | |||
type: [String, Number], | |||
default: '30' | |||
}, | |||
// 是否显示头部 | |||
showHead: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示尾部 | |||
showFoot: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
data() { | |||
return {}; | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click', this.index); | |||
}, | |||
headClick() { | |||
this.$emit('head-click', this.index); | |||
}, | |||
bodyClick() { | |||
this.$emit('body-click', this.index); | |||
}, | |||
footClick() { | |||
this.$emit('foot-click', this.index); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-card { | |||
position: relative; | |||
overflow: hidden; | |||
font-size: 28rpx; | |||
background-color: #ffffff; | |||
box-sizing: border-box; | |||
&-full { | |||
// 如果是与屏幕之间不留空隙,应该设置左右边距为0 | |||
margin-left: 0 !important; | |||
margin-right: 0 !important; | |||
} | |||
&--border:after { | |||
border-radius: 16rpx; | |||
} | |||
&__head { | |||
&--left { | |||
color: $u-main-color; | |||
&__thumb { | |||
margin-right: 16rpx; | |||
} | |||
&__title { | |||
max-width: 400rpx; | |||
} | |||
} | |||
&--right { | |||
color: $u-tips-color; | |||
margin-left: 6rpx; | |||
} | |||
} | |||
&__body { | |||
color: $u-content-color; | |||
} | |||
&__foot { | |||
color: $u-tips-color; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,69 @@ | |||
<template> | |||
<view class="u-cell-box"> | |||
<view class="u-cell-title" v-if="title" :style="[titleStyle]"> | |||
{{title}} | |||
</view> | |||
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}"> | |||
<slot /> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* cellGroup 单元格父组件Group | |||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item | |||
* @tutorial https://www.uviewui.com/components/cell.html | |||
* @property {String} title 分组标题 | |||
* @property {Boolean} border 是否显示外边框(默认true) | |||
* @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'} | |||
* @example <u-cell-group title="设置喜好"> | |||
*/ | |||
export default { | |||
name: "u-cell-group", | |||
props: { | |||
// 分组标题 | |||
title: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示分组list上下边框 | |||
border: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 分组标题的样式,对象形式,注意驼峰属性写法 | |||
// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'} | |||
titleStyle: { | |||
type: Object, | |||
default () { | |||
return {}; | |||
} | |||
} | |||
}, | |||
data() { | |||
return { | |||
index: 0, | |||
} | |||
}, | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-cell-box { | |||
width: 100%; | |||
} | |||
.u-cell-title { | |||
padding: 30rpx 32rpx 10rpx 32rpx; | |||
font-size: 30rpx; | |||
text-align: left; | |||
color: $u-tips-color; | |||
} | |||
.u-cell-item-box { | |||
background-color: #FFFFFF; | |||
} | |||
</style> |
@@ -0,0 +1,300 @@ | |||
<template> | |||
<view | |||
@tap="click" | |||
class="u-cell" | |||
:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }" | |||
hover-stay-time="150" | |||
:hover-class="hoverClass" | |||
:style="{ | |||
backgroundColor: bgColor | |||
}" | |||
> | |||
<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon> | |||
<view class="u-flex" v-else> | |||
<slot name="icon"></slot> | |||
</view> | |||
<view | |||
class="u-cell_title" | |||
:style="[ | |||
{ | |||
width: titleWidth ? titleWidth + 'rpx' : 'auto' | |||
}, | |||
titleStyle | |||
]" | |||
> | |||
<block v-if="title">{{ title }}</block> | |||
<slot name="title" v-else></slot> | |||
<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]"> | |||
<block v-if="label">{{ label }}</block> | |||
<slot name="label" v-else></slot> | |||
</view> | |||
</view> | |||
<view class="u-cell__value" :style="[valueStyle]"> | |||
<block class="u-cell__value" v-if="value">{{ value }}</block> | |||
<slot v-else></slot> | |||
</view> | |||
<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon> | |||
<view class="u-flex" v-if="$slots['right-icon']"> | |||
<slot name="right-icon"></slot> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* cellItem 单元格Item | |||
* @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用 | |||
* @tutorial https://www.uviewui.com/components/cell.html | |||
* @property {String} title 左侧标题 | |||
* @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标 | |||
* @property {Object} icon-style 左边图标的样式,对象形式 | |||
* @property {String} value 右侧内容 | |||
* @property {String} label 标题下方的描述信息 | |||
* @property {Boolean} border-bottom 是否显示cell的下边框(默认true) | |||
* @property {Boolean} border-top 是否显示cell的上边框(默认false) | |||
* @property {Boolean} center 是否使内容垂直居中(默认false) | |||
* @property {String} hover-class 是否开启点击反馈,none为无效果(默认true) | |||
* // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true) | |||
* @property {Boolean} arrow 是否显示右侧箭头(默认true) | |||
* @property {Boolean} required 箭头方向,可选值(默认right) | |||
* @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false) | |||
* @property {Object} title-style 标题样式,对象形式 | |||
* @property {Object} value-style 右侧内容样式,对象形式 | |||
* @property {Object} label-style 标题下方描述信息的样式,对象形式 | |||
* @property {String} bg-color 背景颜色(默认transparent) | |||
* @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item | |||
* @property {String Number} title-width 标题的宽度,单位rpx | |||
* @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item> | |||
*/ | |||
export default { | |||
name: 'u-cell-item', | |||
props: { | |||
// 左侧图标名称(只能uView内置图标),或者图标src | |||
icon: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 左侧标题 | |||
title: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 右侧内容 | |||
value: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 标题下方的描述信息 | |||
label: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 是否显示下边框 | |||
borderBottom: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示上边框 | |||
borderTop: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离 | |||
// 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰 | |||
// borderGap: { | |||
// type: Boolean, | |||
// default: true | |||
// }, | |||
// 是否开启点击反馈,即点击时cell背景为灰色,none为无效果 | |||
hoverClass: { | |||
type: String, | |||
default: 'u-cell-hover' | |||
}, | |||
// 是否显示右侧箭头 | |||
arrow: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 内容是否垂直居中 | |||
center: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否显示左边表示必填的星号 | |||
required: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 标题的宽度,单位rpx | |||
titleWidth: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
// 右侧箭头方向,可选值:right|up|down,默认为right | |||
arrowDirection: { | |||
type: String, | |||
default: 'right' | |||
}, | |||
// 控制标题的样式 | |||
titleStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 右侧显示内容的样式 | |||
valueStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 描述信息的样式 | |||
labelStyle: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: 'transparent' | |||
}, | |||
// 用于识别被点击的是第几个cell | |||
index: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 是否使用lable插槽 | |||
useLabelSlot: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 左边图标的大小,单位rpx,只对传入icon字段时有效 | |||
iconSize: { | |||
type: [Number, String], | |||
default: 34 | |||
}, | |||
// 左边图标的样式,对象形式 | |||
iconStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
}, | |||
data() { | |||
return { | |||
}; | |||
}, | |||
computed: { | |||
arrowStyle() { | |||
let style = {}; | |||
if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)'; | |||
else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)'; | |||
else style.transform = 'rotate(0deg)'; | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click', this.index); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-cell { | |||
position: relative; | |||
display: flex; | |||
box-sizing: border-box; | |||
width: 100%; | |||
padding: 26rpx 32rpx; | |||
font-size: 28rpx; | |||
line-height: 54rpx; | |||
color: $u-content-color; | |||
background-color: #fff; | |||
text-align: left; | |||
} | |||
.u-cell_title { | |||
font-size: 28rpx; | |||
} | |||
.u-cell__left-icon-wrap { | |||
margin-right: 10rpx; | |||
font-size: 32rpx; | |||
} | |||
.u-cell__right-icon-wrap { | |||
margin-left: 10rpx; | |||
color: #969799; | |||
font-size: 28rpx; | |||
} | |||
.u-cell__left-icon-wrap, | |||
.u-cell__right-icon-wrap { | |||
display: flex; | |||
align-items: center; | |||
height: 48rpx; | |||
} | |||
.u-cell-border:after { | |||
position: absolute; | |||
box-sizing: border-box; | |||
content: ' '; | |||
pointer-events: none; | |||
right: 0; | |||
left: 0; | |||
top: 0; | |||
border-bottom: 1px solid $u-border-color; | |||
-webkit-transform: scaleY(0.5); | |||
transform: scaleY(0.5); | |||
} | |||
.u-cell-border { | |||
position: relative; | |||
} | |||
.u-cell__label { | |||
margin-top: 6rpx; | |||
font-size: 26rpx; | |||
line-height: 36rpx; | |||
color: $u-tips-color; | |||
word-wrap: break-word; | |||
} | |||
.u-cell__value { | |||
overflow: hidden; | |||
text-align: right; | |||
vertical-align: middle; | |||
color: $u-tips-color; | |||
font-size: 26rpx; | |||
} | |||
.u-cell__title, | |||
.u-cell__value { | |||
flex: 1; | |||
} | |||
.u-cell--required { | |||
overflow: visible; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.u-cell--required:before { | |||
position: absolute; | |||
content: '*'; | |||
left: 8px; | |||
margin-top: 4rpx; | |||
font-size: 14px; | |||
color: $u-type-error; | |||
} | |||
</style> |
@@ -0,0 +1,122 @@ | |||
<template> | |||
<view class="u-checkbox-group u-clearfix"> | |||
<slot></slot> | |||
</view> | |||
</template> | |||
<script> | |||
import Emitter from '../../libs/util/emitter.js'; | |||
/** | |||
* checkboxGroup 开关选择器父组件Group | |||
* @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便 | |||
* @tutorial https://www.uviewui.com/components/checkbox.html | |||
* @property {String Number} max 最多能选中多少个checkbox(默认999) | |||
* @property {String Number} size 组件整体的大小,单位rpx(默认40) | |||
* @property {Boolean} disabled 是否禁用所有checkbox(默认false) | |||
* @property {String Number} icon-size 图标大小,单位rpx(默认20) | |||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false) | |||
* @property {String} width 宽度,需带单位 | |||
* @property {String} width 宽度,需带单位 | |||
* @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle) | |||
* @property {Boolean} wrap 是否每个checkbox都换行(默认false) | |||
* @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff) | |||
* @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象 | |||
* @example <u-checkbox-group></u-checkbox-group> | |||
*/ | |||
export default { | |||
name: 'u-checkbox-group', | |||
mixins: [Emitter], | |||
props: { | |||
// 最多能选中多少个checkbox | |||
max: { | |||
type: [Number, String], | |||
default: 999 | |||
}, | |||
// 所有选中项的 name | |||
// value: { | |||
// default: Array, | |||
// default() { | |||
// return [] | |||
// } | |||
// }, | |||
// 是否禁用所有复选框 | |||
disabled: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 在表单内提交时的标识符 | |||
name: { | |||
type: [Boolean, String], | |||
default: '' | |||
}, | |||
// 是否禁止点击提示语选中复选框 | |||
labelDisabled: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 形状,square为方形,circle为原型 | |||
shape: { | |||
type: String, | |||
default: 'square' | |||
}, | |||
// 选中状态下的颜色 | |||
activeColor: { | |||
type: String, | |||
default: '#2979ff' | |||
}, | |||
// 组件的整体大小 | |||
size: { | |||
type: [String, Number], | |||
default: 34 | |||
}, | |||
// 每个checkbox占u-checkbox-group的宽度 | |||
width: { | |||
type: String, | |||
default: 'auto' | |||
}, | |||
// 是否每个checkbox都换行 | |||
wrap: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 图标的大小,单位rpx | |||
iconSize: { | |||
type: [String, Number], | |||
default: 20 | |||
}, | |||
}, | |||
data() { | |||
return { | |||
} | |||
}, | |||
created() { | |||
// 如果将children定义在data中,在微信小程序会造成循环引用而报错 | |||
this.children = []; | |||
}, | |||
methods: { | |||
emitEvent() { | |||
let values = []; | |||
this.children.map(val => { | |||
if(val.value) values.push(val.name); | |||
}) | |||
this.$emit('change', values); | |||
// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证 | |||
this.$nextTick(() => { | |||
// 将当前的值发送到 u-form-item 进行校验 | |||
this.dispatch('u-form-item', 'on-form-change', values); | |||
}); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-checkbox-group { | |||
/* #ifndef MP */ | |||
display: inline-flex; | |||
flex-wrap: wrap; | |||
/* #endif */ | |||
} | |||
</style> |
@@ -0,0 +1,275 @@ | |||
<template> | |||
<view class="u-checkbox" :style="[checkboxStyle]"> | |||
<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]"> | |||
<u-icon name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/> | |||
</view> | |||
<view class="u-checkbox__label" @tap="onClickLabel" :style="{ | |||
fontSize: $u.addUnit(labelSize) | |||
}"> | |||
<slot /> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* checkbox 复选框 | |||
* @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。 | |||
* @tutorial https://www.uviewui.com/components/checkbox.html | |||
* @property {String Number} icon-size 图标大小,单位rpx(默认20) | |||
* @property {String Number} label-size label字体大小,单位rpx(默认28) | |||
* @property {String Number} name checkbox组件的标示符 | |||
* @property {String} shape 形状,见官网说明(默认circle) | |||
* @property {Boolean} disabled 是否禁用 | |||
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox | |||
* @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效 | |||
* @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象 | |||
* @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox> | |||
*/ | |||
export default { | |||
name: "u-checkbox", | |||
props: { | |||
// checkbox的名称 | |||
name: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 形状,square为方形,circle为原型 | |||
shape: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否为选中状态 | |||
value: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否禁用 | |||
disabled: { | |||
type: [String, Boolean], | |||
default: '' | |||
}, | |||
// 是否禁止点击提示语选中复选框 | |||
labelDisabled: { | |||
type: [String, Boolean], | |||
default: '' | |||
}, | |||
// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值 | |||
activeColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 图标的大小,单位rpx | |||
iconSize: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// label的字体大小,rpx单位 | |||
labelSize: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// 组件的整体大小 | |||
size: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
}, | |||
data() { | |||
return { | |||
parentDisabled: false, | |||
newParams: {}, | |||
}; | |||
}, | |||
created() { | |||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用 | |||
this.parent = this.$u.$parent.call(this, 'u-checkbox-group'); | |||
// 如果存在u-checkbox-group,将本组件的this塞进父组件的children中 | |||
this.parent && this.parent.children.push(this); | |||
}, | |||
computed: { | |||
// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置 | |||
isDisabled() { | |||
return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false; | |||
}, | |||
// 是否禁用label点击 | |||
isLabelDisabled() { | |||
return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false; | |||
}, | |||
// 组件尺寸,对应size的值,默认值为34rpx | |||
checkboxSize() { | |||
return this.size ? this.size : (this.parent ? this.parent.size : 34); | |||
}, | |||
// 组件的勾选图标的尺寸,默认20 | |||
checkboxIconSize() { | |||
return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20); | |||
}, | |||
// 组件选中激活时的颜色 | |||
elActiveColor() { | |||
return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary'); | |||
}, | |||
// 组件的形状 | |||
elShape() { | |||
return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square'); | |||
}, | |||
iconStyle() { | |||
let style = {}; | |||
// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中 | |||
if (this.elActiveColor && this.value && !this.isDisabled) { | |||
style.borderColor = this.elActiveColor; | |||
style.backgroundColor = this.elActiveColor; | |||
} | |||
style.width = this.$u.addUnit(this.checkboxSize); | |||
style.height = this.$u.addUnit(this.checkboxSize); | |||
return style; | |||
}, | |||
// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可 | |||
iconColor() { | |||
return this.value ? '#ffffff' : 'transparent'; | |||
}, | |||
iconClass() { | |||
let classes = []; | |||
classes.push('u-checkbox__icon-wrap--' + this.elShape); | |||
if (this.value == true) classes.push('u-checkbox__icon-wrap--checked'); | |||
if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled'); | |||
if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked'); | |||
// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效 | |||
return classes.join(' '); | |||
}, | |||
checkboxStyle() { | |||
let style = {}; | |||
if(this.parent && this.parent.width) { | |||
style.width = this.parent.width; | |||
// #ifdef MP | |||
// 各家小程序因为它们特殊的编译结构,使用float布局 | |||
style.float = 'left'; | |||
// #endif | |||
// #ifndef MP | |||
// H5和APP使用flex布局 | |||
style.flex = `0 0 ${this.parent.width}`; | |||
// #endif | |||
} | |||
if(this.parent && this.parent.wrap) { | |||
style.width = '100%'; | |||
// #ifndef MP | |||
// H5和APP使用flex布局,将宽度设置100%,即可自动换行 | |||
style.flex = '0 0 100%'; | |||
// #endif | |||
} | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
onClickLabel() { | |||
if (!this.isLabelDisabled) { | |||
this.setValue(); | |||
} | |||
}, | |||
toggle() { | |||
if (!this.isDisabled) { | |||
this.setValue(); | |||
} | |||
}, | |||
emitEvent() { | |||
this.$emit('change', { | |||
value: this.value, | |||
name: this.name | |||
}) | |||
// 执行父组件u-checkbox-group的事件方法 | |||
if(this.parent && this.parent.emitEvent) this.parent.emitEvent(); | |||
}, | |||
// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值 | |||
setValue() { | |||
// 判断是否超过了可选的最大数量 | |||
let checkedNum = 0; | |||
if(this.parent && this.parent.children) { | |||
// 只要父组件的某一个子元素的value为true,就加1(已有的选中数量) | |||
this.parent.children.map(val => { | |||
if (val.value) checkedNum++; | |||
}) | |||
} | |||
// 如果原来为选中状态,那么可以取消 | |||
if (this.value == true) { | |||
this.$emit('input', !this.value); | |||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 | |||
this.$nextTick(function() { | |||
this.emitEvent(); | |||
}) | |||
} else if ((this.parent && checkedNum < this.parent.max) || !this.parent) { | |||
// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中 | |||
this.$emit('input', !this.value); | |||
// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间 | |||
this.$nextTick(function() { | |||
this.emitEvent(); | |||
}) | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-checkbox { | |||
display: inline-flex; | |||
align-items: center; | |||
overflow: hidden; | |||
user-select: none; | |||
line-height: 1.8; | |||
&__icon-wrap { | |||
color: $u-content-color; | |||
flex: none; | |||
display: -webkit-flex; | |||
display: flex; | |||
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; | |||
&--circle { | |||
border-radius: 100%; | |||
} | |||
&--square { | |||
border-radius: 6rpx; | |||
} | |||
&--checked { | |||
color: #fff; | |||
background-color: $u-type-primary; | |||
border-color: $u-type-primary; | |||
} | |||
&--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> |
@@ -0,0 +1,218 @@ | |||
<template> | |||
<view | |||
class="u-circle-progress" | |||
:style="{ | |||
width: widthPx + 'px', | |||
height: widthPx + 'px', | |||
backgroundColor: bgColor | |||
}" | |||
> | |||
<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 --> | |||
<canvas | |||
class="u-canvas-bg" | |||
:canvas-id="elBgId" | |||
:id="elBgId" | |||
:style="{ | |||
width: widthPx + 'px', | |||
height: widthPx + 'px' | |||
}" | |||
></canvas> | |||
<canvas | |||
class="u-canvas" | |||
:canvas-id="elId" | |||
:id="elId" | |||
:style="{ | |||
width: widthPx + 'px', | |||
height: widthPx + 'px' | |||
}" | |||
></canvas> | |||
<slot></slot> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* circleProgress 环形进度条 | |||
* @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。 | |||
* @tutorial https://www.uviewui.com/components/circleProgress.html | |||
* @property {String Number} percent 圆环进度百分比值,为数值类型,0-100 | |||
* @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec) | |||
* @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b) | |||
* @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200) | |||
* @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14) | |||
* @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500) | |||
* @property {String} type 如设置,active-color值将会失效 | |||
* @property {String} bg-color 整个组件背景颜色,默认为白色 | |||
* @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress> | |||
*/ | |||
export default { | |||
name: 'u-circle-progress', | |||
props: { | |||
// 圆环进度百分比值 | |||
percent: { | |||
type: Number, | |||
default: 0, | |||
// 限制值在0到100之间 | |||
validator: val => { | |||
return val >= 0 && val <= 100; | |||
} | |||
}, | |||
// 底部圆环的颜色(灰色的圆环) | |||
inactiveColor: { | |||
type: String, | |||
default: '#ececec' | |||
}, | |||
// 圆环激活部分的颜色 | |||
activeColor: { | |||
type: String, | |||
default: '#19be6b' | |||
}, | |||
// 圆环线条的宽度,单位rpx | |||
borderWidth: { | |||
type: [Number, String], | |||
default: 14 | |||
}, | |||
// 整个圆形的宽度,单位rpx | |||
width: { | |||
type: [Number, String], | |||
default: 200 | |||
}, | |||
// 整个圆环执行一圈的时间,单位ms | |||
duration: { | |||
type: [Number, String], | |||
default: 1500 | |||
}, | |||
// 主题类型 | |||
type: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 整个圆环进度区域的背景色 | |||
bgColor: { | |||
type: String, | |||
default: '#ffffff' | |||
} | |||
}, | |||
data() { | |||
return { | |||
// #ifdef MP-WEIXIN | |||
elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错 | |||
elId: 'uCircleProgressElId', | |||
// #endif | |||
// #ifndef MP-WEIXIN | |||
elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱 | |||
elId: this.$u.guid(), | |||
// #endif | |||
widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度 | |||
borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度 | |||
startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向 | |||
progressContext: null, // 活动圆的canvas上下文 | |||
newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用 | |||
oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用 | |||
}; | |||
}, | |||
watch: { | |||
percent(nVal, oVal = 0) { | |||
if (nVal > 100) nVal = 100; | |||
if (nVal < 0) oVal = 0; | |||
// 此值其实等于this.percent,命名一个新 | |||
this.newPercent = nVal; | |||
this.oldPercent = oVal; | |||
setTimeout(() => { | |||
// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值 | |||
// 将此值减少或者新增到新的百分比值 | |||
this.drawCircleByProgress(oVal); | |||
}, 50); | |||
} | |||
}, | |||
created() { | |||
// 赋值,用于加载后第一个画圆使用 | |||
this.newPercent = this.percent; | |||
this.oldPercent = 0; | |||
}, | |||
computed: { | |||
// 有type主题时,优先起作用 | |||
circleColor() { | |||
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type]; | |||
else return this.activeColor; | |||
} | |||
}, | |||
mounted() { | |||
// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7) | |||
setTimeout(() => { | |||
this.drawProgressBg(); | |||
this.drawCircleByProgress(this.oldPercent); | |||
}, 50); | |||
}, | |||
methods: { | |||
drawProgressBg() { | |||
let ctx = uni.createCanvasContext(this.elBgId, this); | |||
ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度 | |||
ctx.setStrokeStyle(this.inactiveColor); // 线条颜色 | |||
ctx.beginPath(); // 开始描绘路径 | |||
// 设置一个原点(110,110),半径为100的圆的路径到当前路径 | |||
let radius = this.widthPx / 2; | |||
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false); | |||
ctx.stroke(); // 对路径进行描绘 | |||
ctx.draw(); | |||
}, | |||
drawCircleByProgress(progress) { | |||
// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可 | |||
let ctx = this.progressContext; | |||
if (!ctx) { | |||
ctx = uni.createCanvasContext(this.elId, this); | |||
this.progressContext = ctx; | |||
} | |||
// 表示进度的两端为圆形 | |||
ctx.setLineCap('round'); | |||
// 设置线条的宽度和颜色 | |||
ctx.setLineWidth(this.borderWidthPx); | |||
ctx.setStrokeStyle(this.circleColor); | |||
// 将总过渡时间除以100,得出每修改百分之一进度所需的时间 | |||
let time = Math.floor(this.duration / 100); | |||
// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的 | |||
// 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值 | |||
let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle; | |||
ctx.beginPath(); | |||
// 半径为整个canvas宽度的一半 | |||
let radius = this.widthPx / 2; | |||
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false); | |||
ctx.stroke(); | |||
ctx.draw(); | |||
// 如果变更后新值大于旧值,意味着增大了百分比 | |||
if (this.newPercent > this.oldPercent) { | |||
// 每次递增百分之一 | |||
progress++; | |||
// 如果新增后的值,大于需要设置的值百分比值,停止继续增加 | |||
if (progress > this.newPercent) return; | |||
} else { | |||
// 同理于上面 | |||
progress--; | |||
if (progress < this.newPercent) return; | |||
} | |||
setTimeout(() => { | |||
// 定时器,每次操作间隔为time值,为了让进度条有动画效果 | |||
this.drawCircleByProgress(progress); | |||
}, time); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-circle-progress { | |||
position: relative; | |||
display: inline-flex; | |||
align-items: center; | |||
justify-content: center; | |||
} | |||
.u-canvas-bg { | |||
position: absolute; | |||
} | |||
.u-canvas { | |||
position: absolute; | |||
} | |||
</style> |
@@ -0,0 +1,128 @@ | |||
<template> | |||
<view class="u-col" :class="[ | |||
'u-col-' + span | |||
]" :style="{ | |||
padding: `0 ${Number(gutter)/2 + 'rpx'}`, | |||
marginLeft: 100 / 12 * offset + '%', | |||
flex: `0 0 ${100 / 12 * span}%`, | |||
alignItems: uAlignItem, | |||
justifyContent: uJustify | |||
}" @tap.stop.prevent="click"> | |||
<slot></slot> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* col 布局单元格 | |||
* @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用) | |||
* @tutorial https://www.uviewui.com/components/layout.html | |||
* @property {String Number} span 栅格占据的列数,总12等分(默认0) | |||
* @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0) | |||
* @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col> | |||
*/ | |||
export default { | |||
name: "u-col", | |||
props: { | |||
// 占父容器宽度的多少等分,总分为12份 | |||
span: { | |||
type: [Number, String], | |||
default: 12 | |||
}, | |||
// 指定栅格左侧的间隔数(总12栏) | |||
offset: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`) | |||
justify: { | |||
type: String, | |||
default: 'start' | |||
}, | |||
// 垂直对齐方式,可选值为top、center、bottom | |||
align: { | |||
type: String, | |||
default: 'center' | |||
} | |||
}, | |||
inject: ['gutter'], | |||
computed: { | |||
uJustify() { | |||
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify; | |||
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify; | |||
else return this.justify; | |||
}, | |||
uAlignItem() { | |||
if (this.align == 'top') return 'flex-start'; | |||
if (this.align == 'bottom') return 'flex-end'; | |||
else return this.align; | |||
} | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click'); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-col { | |||
/* #ifdef MP-WEIXIN */ | |||
float: left; | |||
/* #endif */ | |||
} | |||
.u-col-0 { | |||
width: 0; | |||
} | |||
.u-col-1 { | |||
width: calc(100%/12); | |||
} | |||
.u-col-2 { | |||
width: calc(100%/12 * 2); | |||
} | |||
.u-col-3 { | |||
width: calc(100%/12 * 3); | |||
} | |||
.u-col-4 { | |||
width: calc(100%/12 * 4); | |||
} | |||
.u-col-5 { | |||
width: calc(100%/12 * 5); | |||
} | |||
.u-col-6 { | |||
width: calc(100%/12 * 6); | |||
} | |||
.u-col-7 { | |||
width: calc(100%/12 * 7); | |||
} | |||
.u-col-8 { | |||
width: calc(100%/12 * 8); | |||
} | |||
.u-col-9 { | |||
width: calc(100%/12 * 9); | |||
} | |||
.u-col-10 { | |||
width: calc(100%/12 * 10); | |||
} | |||
.u-col-11 { | |||
width: calc(100%/12 * 11); | |||
} | |||
.u-col-12 { | |||
width: calc(100%/12 * 12); | |||
} | |||
</style> |
@@ -0,0 +1,206 @@ | |||
<template> | |||
<view class="u-collapse-item" :style="[itemStyle]"> | |||
<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]"> | |||
<block v-if="!$slots['title-all']"> | |||
<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' }, | |||
isShow && activeStyle && !arrow ? activeStyle : '']"> | |||
{{ title }} | |||
</view> | |||
<slot v-else name="title" /> | |||
<view class="u-icon-wrap"> | |||
<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }" | |||
class="u-arrow-down-icon" name="arrow-down"></u-icon> | |||
</view> | |||
</block> | |||
<slot v-else name="title-all" /> | |||
</view> | |||
<view class="u-collapse-body" :style="[{ | |||
height: isShow ? height + 'px' : '0' | |||
}]"> | |||
<view class="u-collapse-content" :id="elId" :style="[bodyStyle]"> | |||
<slot></slot> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* collapseItem 手风琴Item | |||
* @description 通过折叠面板收纳内容区域(搭配u-collapse使用) | |||
* @tutorial https://www.uviewui.com/components/collapse.html | |||
* @property {String} title 面板标题 | |||
* @property {String Number} index 主要用于事件的回调,标识那个Item被点击 | |||
* @property {Boolean} disabled 面板是否可以打开或收起(默认false) | |||
* @property {Boolean} open 设置某个面板的初始状态是否打开(默认false) | |||
* @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值 | |||
* @property {String} align 标题的对齐方式(默认left) | |||
* @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式 | |||
* @event {Function} change 某个item被打开或者收起时触发 | |||
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item> | |||
*/ | |||
export default { | |||
name: "u-collapse-item", | |||
props: { | |||
// 标题 | |||
title: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 标题的对齐方式 | |||
align: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
// 是否可以点击收起 | |||
disabled: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// collapse显示与否 | |||
open: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 唯一标识符 | |||
name: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
//活动样式 | |||
activeStyle: { | |||
type: Object, | |||
default () { | |||
return {} | |||
} | |||
}, | |||
// 标识当前为第几个 | |||
index: { | |||
type: [String, Number], | |||
default: '' | |||
} | |||
}, | |||
inject: ['uCollapse'], | |||
data() { | |||
return { | |||
isShow: false, | |||
elId: this.$u.guid(), | |||
height: 0, // body内容的高度 | |||
headStyle: {}, // 头部样式,对象形式 | |||
bodyStyle: {}, // 主体部分样式 | |||
//itemStyle: {}, // 每个item的整体样式 | |||
arrowColor: '', // 箭头的颜色 | |||
hoverClass: '', // 头部按下时的效果样式类 | |||
}; | |||
}, | |||
mounted() { | |||
this.init(); | |||
}, | |||
watch: { | |||
open(val) { | |||
this.isShow = val; | |||
} | |||
}, | |||
computed: { | |||
arrow() { | |||
return this.uCollapse.arrow; | |||
}, | |||
itemStyle() { | |||
return this.uCollapse.itemStyle; | |||
} | |||
}, | |||
created() { | |||
// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍 | |||
this.isShow = this.open; | |||
this.nameSync = this.name ? this.name : this.uCollapse.childrens.length; | |||
this.uCollapse.childrens.push(this); | |||
//this.itemStyle = this.uCollapse.itemStyle; | |||
this.headStyle = this.uCollapse.headStyle; | |||
this.bodyStyle = this.uCollapse.bodyStyle; | |||
this.arrowColor = this.uCollapse.arrowColor; | |||
this.hoverClass = this.uCollapse.hoverClass; | |||
}, | |||
methods: { | |||
// 异步获取内容,或者动态修改了内容时,需要重新初始化 | |||
init() { | |||
this.$nextTick(() => { | |||
this.queryRect(); | |||
}); | |||
}, | |||
// 点击collapsehead头部 | |||
headClick() { | |||
if (this.disabled) return; | |||
if (this.uCollapse.accordion == true) { | |||
this.uCollapse.childrens.map(val => { | |||
// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了 | |||
if (this != val) { | |||
val.isShow = false; | |||
} | |||
}); | |||
} | |||
this.isShow = !this.isShow; | |||
// 触发本组件的事件 | |||
this.$emit('change', { | |||
index: this.index, | |||
show: this.isShow | |||
}) | |||
// 只有在打开时才发出事件 | |||
if (this.isShow) this.uCollapse.onChange(); | |||
this.$forceUpdate(); | |||
}, | |||
// 查询内容高度 | |||
queryRect() { | |||
// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html | |||
// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同 | |||
this.$uGetRect('#' + this.elId).then(res => { | |||
this.height = res.height; | |||
}) | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-collapse-head { | |||
position: relative; | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
color: $u-main-color; | |||
font-size: 30rpx; | |||
line-height: 1; | |||
padding: 24rpx 0; | |||
text-align: left; | |||
} | |||
.u-collapse-title { | |||
flex: 1; | |||
overflow: hidden; | |||
} | |||
.u-arrow-down-icon { | |||
transition: all 0.3s; | |||
margin-right: 20rpx; | |||
margin-left: 14rpx; | |||
} | |||
.u-arrow-down-icon-active { | |||
transform: rotate(180deg); | |||
transform-origin: center center; | |||
} | |||
.u-collapse-body { | |||
overflow: hidden; | |||
transition: all 0.3s; | |||
} | |||
.u-collapse-content { | |||
overflow: hidden; | |||
font-size: 28rpx; | |||
color: $u-tips-color; | |||
text-align: left; | |||
} | |||
</style> |
@@ -0,0 +1,104 @@ | |||
<template> | |||
<view class="u-collapse"> | |||
<slot /> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* collapse 手风琴 | |||
* @description 通过折叠面板收纳内容区域 | |||
* @tutorial https://www.uviewui.com/components/collapse.html | |||
* @property {Boolean} accordion 是否手风琴模式(默认true) | |||
* @property {Boolean} arrow 是否显示标题右侧的箭头(默认true) | |||
* @property {String} arrow-color 标题右侧箭头的颜色(默认#909399) | |||
* @property {Object} head-style 标题自定义样式,对象形式 | |||
* @property {Object} body-style 主体自定义样式,对象形式 | |||
* @property {String} hover-class 样式类名,按下时有效(默认u-hover-class) | |||
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array) | |||
* @example <u-collapse></u-collapse> | |||
*/ | |||
export default { | |||
name:"u-collapse", | |||
props: { | |||
// 是否手风琴模式 | |||
accordion: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 头部的样式 | |||
headStyle: { | |||
type: Object, | |||
default () { | |||
return {} | |||
} | |||
}, | |||
// 主体的样式 | |||
bodyStyle: { | |||
type: Object, | |||
default () { | |||
return {} | |||
} | |||
}, | |||
// 每一个item的样式 | |||
itemStyle: { | |||
type: Object, | |||
default () { | |||
return {} | |||
} | |||
}, | |||
// 是否显示右侧的箭头 | |||
arrow: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 箭头的颜色 | |||
arrowColor: { | |||
type: String, | |||
default: '#909399' | |||
}, | |||
// 标题部分按压时的样式类,"none"为无效果 | |||
hoverClass: { | |||
type: String, | |||
default: 'u-hover-class' | |||
} | |||
}, | |||
provide() { | |||
return { | |||
uCollapse: this | |||
} | |||
}, | |||
created() { | |||
this.childrens = [] | |||
}, | |||
data() { | |||
return { | |||
} | |||
}, | |||
methods: { | |||
// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况 | |||
init() { | |||
this.childrens.forEach((vm, index) => { | |||
vm.init(); | |||
}) | |||
}, | |||
// collapse item被点击,由collapse item调用父组件方法 | |||
onChange() { | |||
let activeItem = []; | |||
this.childrens.forEach((vm, index) => { | |||
if (vm.isShow) { | |||
activeItem.push(vm.nameSync); | |||
} | |||
}) | |||
// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串 | |||
if (this.accordion) activeItem = activeItem.join(''); | |||
this.$emit('change', activeItem); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
</style> |
@@ -0,0 +1,233 @@ | |||
<template> | |||
<view | |||
class="u-notice-bar" | |||
:style="{ | |||
background: computeBgColor, | |||
padding: padding | |||
}" | |||
:class="[ | |||
type ? `u-type-${type}-light-bg` : '' | |||
]" | |||
> | |||
<view class="u-icon-wrap"> | |||
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon> | |||
</view> | |||
<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper"> | |||
<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item"> | |||
<view | |||
class="u-news-item u-line-1" | |||
:style="[textStyle]" | |||
@tap="click(index)" | |||
:class="['u-type-' + type]" | |||
> | |||
{{ item }} | |||
</view> | |||
</swiper-item> | |||
</swiper> | |||
<view class="u-icon-wrap"> | |||
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon> | |||
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
// 显示的内容,数组 | |||
list: { | |||
type: Array, | |||
default() { | |||
return []; | |||
} | |||
}, | |||
// 显示的主题,success|error|primary|info|warning | |||
type: { | |||
type: String, | |||
default: 'warning' | |||
}, | |||
// 是否显示左侧的音量图标 | |||
volumeIcon: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示右侧的右箭头图标 | |||
moreIcon: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否显示右侧的关闭图标 | |||
closeIcon: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否自动播放 | |||
autoplay: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 文字颜色,各图标也会使用文字颜色 | |||
color: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 滚动方向,row-水平滚动,column-垂直滚动 | |||
direction: { | |||
type: String, | |||
default: 'row' | |||
}, | |||
// 是否显示 | |||
show: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 字体大小,单位rpx | |||
fontSize: { | |||
type: [Number, String], | |||
default: 26 | |||
}, | |||
// 滚动一个周期的时间长,单位ms | |||
duration: { | |||
type: [Number, String], | |||
default: 2000 | |||
}, | |||
// 音量喇叭的大小 | |||
volumeSize: { | |||
type: [Number, String], | |||
default: 34 | |||
}, | |||
// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度 | |||
speed: { | |||
type: Number, | |||
default: 160 | |||
}, | |||
// 水平滚动时,是否采用衔接形式滚动 | |||
isCircular: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 滚动方向,horizontal-水平滚动,vertical-垂直滚动 | |||
mode: { | |||
type: String, | |||
default: 'horizontal' | |||
}, | |||
// 播放状态,play-播放,paused-暂停 | |||
playState: { | |||
type: String, | |||
default: 'play' | |||
}, | |||
// 是否禁止用手滑动切换 | |||
// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 | |||
disableTouch: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 通知的边距 | |||
padding: { | |||
type: [Number, String], | |||
default: '18rpx 24rpx' | |||
} | |||
}, | |||
computed: { | |||
// 计算字体颜色,如果没有自定义的,就用uview主题颜色 | |||
computeColor() { | |||
if (this.color) return this.color; | |||
// 如果是无主题,就默认使用content-color | |||
else if(this.type == 'none') return '#606266'; | |||
else return this.type; | |||
}, | |||
// 文字内容的样式 | |||
textStyle() { | |||
let style = {}; | |||
if (this.color) style.color = this.color; | |||
else if(this.type == 'none') style.color = '#606266'; | |||
style.fontSize = this.fontSize + 'rpx'; | |||
return style; | |||
}, | |||
// 垂直或者水平滚动 | |||
vertical() { | |||
if(this.mode == 'horizontal') return false; | |||
else return true; | |||
}, | |||
// 计算背景颜色 | |||
computeBgColor() { | |||
if (this.bgColor) return this.bgColor; | |||
else if(this.type == 'none') return 'transparent'; | |||
} | |||
}, | |||
data() { | |||
return { | |||
// animation: false | |||
}; | |||
}, | |||
methods: { | |||
// 点击通告栏 | |||
click(index) { | |||
this.$emit('click', index); | |||
}, | |||
// 点击关闭按钮 | |||
close() { | |||
this.$emit('close'); | |||
}, | |||
// 点击更多箭头按钮 | |||
getMore() { | |||
this.$emit('getMore'); | |||
}, | |||
change(e) { | |||
let index = e.detail.current; | |||
if(index == this.list.length - 1) { | |||
this.$emit('end'); | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-notice-bar { | |||
width: 100%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
flex-wrap: nowrap; | |||
padding: 18rpx 24rpx; | |||
overflow: hidden; | |||
} | |||
.u-swiper { | |||
font-size: 26rpx; | |||
height: 32rpx; | |||
display: flex; | |||
align-items: center; | |||
flex: 1; | |||
margin-left: 12rpx; | |||
} | |||
.u-swiper-item { | |||
display: flex; | |||
align-items: center; | |||
overflow: hidden; | |||
} | |||
.u-news-item { | |||
overflow: hidden; | |||
} | |||
.u-right-icon { | |||
margin-left: 12rpx; | |||
display: inline-flex; | |||
align-items: center; | |||
} | |||
.u-left-icon { | |||
display: inline-flex; | |||
align-items: center; | |||
} | |||
</style> |
@@ -0,0 +1,307 @@ | |||
<template> | |||
<view class="u-countdown"> | |||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '0'))"> | |||
<view class="u-countdown-time" :style="[letterStyle]"> | |||
{{ d }} | |||
</view> | |||
</view> | |||
<view | |||
class="u-countdown-colon" | |||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" | |||
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '0'))" | |||
> | |||
{{ separator == 'colon' ? ':' : '天' }} | |||
</view> | |||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showHours"> | |||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> | |||
{{ h }} | |||
</view> | |||
</view> | |||
<view | |||
class="u-countdown-colon" | |||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" | |||
v-if="showHours" | |||
> | |||
{{ separator == 'colon' ? ':' : '时' }} | |||
</view> | |||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes"> | |||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> | |||
{{ i }} | |||
</view> | |||
</view> | |||
<view | |||
class="u-countdown-colon" | |||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" | |||
v-if="showMinutes" | |||
> | |||
{{ separator == 'colon' ? ':' : '分' }} | |||
</view> | |||
<view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds"> | |||
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}"> | |||
{{ s }} | |||
</view> | |||
</view> | |||
<view | |||
class="u-countdown-colon" | |||
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}" | |||
v-if="showSeconds && separator == 'zh'" | |||
> | |||
秒 | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* countDown 倒计时 | |||
* @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。 | |||
* @tutorial https://www.uviewui.com/components/countDown.html | |||
* @property {String Number} timestamp 倒计时,单位为秒 | |||
* @property {Boolean} autoplay 是否自动开始倒计时,如果为false,需手动调用开始方法。见官网说明(默认true) | |||
* @property {String} separator 分隔符,colon为英文冒号,zh为中文(默认colon) | |||
* @property {String Number} separator-size 分隔符的字体大小,单位rpx(默认30) | |||
* @property {String} separator-color 分隔符的颜色(默认#303133) | |||
* @property {String Number} font-size 倒计时字体大小,单位rpx(默认30) | |||
* @property {Boolean} show-border 是否显示倒计时数字的边框(默认false) | |||
* @property {Boolean} hide-zero-day 当"天"的部分为0时,隐藏该字段 (默认true) | |||
* @property {String} border-color 数字边框的颜色(默认#303133) | |||
* @property {String} bg-color 倒计时数字的背景颜色(默认#ffffff) | |||
* @property {String} color 倒计时数字的颜色(默认#303133) | |||
* @property {String} height 数字高度值(宽度等同此值),设置边框时看情况是否需要设置此值,单位rpx(默认auto) | |||
* @property {Boolean} show-days 是否显示倒计时的"天"部分(默认true) | |||
* @property {Boolean} show-hours 是否显示倒计时的"时"部分(默认true) | |||
* @property {Boolean} show-minutes 是否显示倒计时的"分"部分(默认true) | |||
* @property {Boolean} show-seconds 是否显示倒计时的"秒"部分(默认true) | |||
* @event {Function} end 倒计时结束 | |||
* @event {Function} change 每秒触发一次,回调为当前剩余的倒计秒数 | |||
* @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down> | |||
*/ | |||
export default { | |||
name: 'u-count-down', | |||
props: { | |||
// 倒计时的时间,秒为单位 | |||
timestamp: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 是否自动开始倒计时 | |||
autoplay: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒" | |||
separator: { | |||
type: String, | |||
default: 'colon' | |||
}, | |||
// 分隔符的大小,单位rpx | |||
separatorSize: { | |||
type: [Number, String], | |||
default: 30 | |||
}, | |||
// 分隔符颜色 | |||
separatorColor: { | |||
type: String, | |||
default: "#303133" | |||
}, | |||
// 字体颜色 | |||
color: { | |||
type: String, | |||
default: '#303133' | |||
}, | |||
// 字体大小,单位rpx | |||
fontSize: { | |||
type: [Number, String], | |||
default: 30 | |||
}, | |||
// 背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '#fff' | |||
}, | |||
// 数字框高度,单位rpx | |||
height: { | |||
type: [Number, String], | |||
default: 'auto' | |||
}, | |||
// 是否显示数字框 | |||
showBorder: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 边框颜色 | |||
borderColor: { | |||
type: String, | |||
default: '#303133' | |||
}, | |||
// 是否显示秒 | |||
showSeconds: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示分钟 | |||
showMinutes: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示小时 | |||
showHours: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示“天” | |||
showDays: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 当"天"的部分为0时,不显示 | |||
hideZeroDay: { | |||
type: Boolean, | |||
default: false | |||
} | |||
}, | |||
watch: { | |||
// 监听时间戳的变化 | |||
timestamp(newVal, oldVal) { | |||
// 如果倒计时间发生变化,清除定时器,重新开始倒计时 | |||
clearInterval(this.timer); | |||
this.start(); | |||
} | |||
}, | |||
data() { | |||
return { | |||
d: '00', // 天的默认值 | |||
h: '00', // 小时的默认值 | |||
i: '00', // 分钟的默认值 | |||
s: '00', // 秒的默认值 | |||
timer: null ,// 定时器 | |||
seconds: 0, // 记录不停倒计过程中变化的秒数 | |||
}; | |||
}, | |||
computed: { | |||
// 倒计时item的样式,item为分别的时分秒部分的数字 | |||
itemStyle() { | |||
let style = {}; | |||
if(this.height) { | |||
style.height = this.height + 'rpx'; | |||
style.width = this.height + 'rpx'; | |||
} | |||
if(this.showBorder) { | |||
style.borderStyle = 'solid'; | |||
style.borderColor = this.borderColor; | |||
style.borderWidth = '1px'; | |||
} | |||
if(this.bgColor) { | |||
style.backgroundColor = this.bgColor; | |||
} | |||
return style; | |||
}, | |||
// 倒计时数字的样式 | |||
letterStyle() { | |||
let style = {}; | |||
if(this.fontSize) style.fontSize = this.fontSize + 'rpx'; | |||
if(this.color) style.color = this.color; | |||
return style; | |||
} | |||
}, | |||
mounted() { | |||
// 如果自动倒计时 | |||
this.autoplay && this.timestamp && this.start(); | |||
}, | |||
methods: { | |||
// 倒计时 | |||
start() { | |||
if (this.timestamp <= 0) return; | |||
this.seconds = Number(this.timestamp); | |||
this.formatTime(this.seconds); | |||
this.timer = setInterval(() => { | |||
this.seconds--; | |||
// 发出change事件 | |||
this.$emit('change', this.seconds); | |||
if (this.seconds < 0) { | |||
return this.end(); | |||
} | |||
this.formatTime(this.seconds); | |||
}, 1000); | |||
}, | |||
// 格式化时间 | |||
formatTime(seconds) { | |||
// 小于等于0的话,结束倒计时 | |||
seconds <= 0 && this.end(); | |||
let [day, hour, minute, second] = [0, 0, 0, 0]; | |||
day = Math.floor(seconds / (60 * 60 * 24)); | |||
// 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中 | |||
// hour为给后面计算秒和分等用的(基于显示天的前提下计算) | |||
hour = Math.floor(seconds / (60 * 60)) - day * 24; | |||
// showHour为需要显示的小时 | |||
let showHour = null; | |||
if(this.showDays) { | |||
showHour = hour; | |||
} else { | |||
// 如果不显示天数,将“天”部分的时间折算到小时中去 | |||
showHour = Math.floor(seconds / (60 * 60)); | |||
} | |||
minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60; | |||
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60; | |||
// 如果小于10,在前面补上一个"0" | |||
showHour = showHour < 10 ? '0' + showHour : showHour; | |||
minute = minute < 10 ? '0' + minute : minute; | |||
second = second < 10 ? '0' + second : second; | |||
this.d = day; | |||
this.h = showHour; | |||
this.i = minute; | |||
this.s = second; | |||
}, | |||
// 停止倒计时 | |||
end() { | |||
// 清除定时器 | |||
clearInterval(this.timer); | |||
this.timer = null; | |||
this.$emit('end', {}); | |||
} | |||
}, | |||
beforeDestroy() { | |||
clearInterval(this.timer); | |||
this.timer = null; | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-countdown { | |||
display: inline-flex; | |||
align-items: center; | |||
} | |||
.u-countdown-item { | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
padding: 2rpx; | |||
border-radius: 6rpx; | |||
white-space: nowrap; | |||
transform: translateZ(0); | |||
} | |||
.u-countdown-time { | |||
margin: 0; | |||
padding: 0; | |||
line-height: 1; | |||
} | |||
.u-countdown-colon { | |||
display: flex; | |||
justify-content: center; | |||
padding: 0 5rpx; | |||
line-height: 1; | |||
align-items: center; | |||
padding-bottom: 4rpx; | |||
} | |||
.u-countdown-scale { | |||
transform: scale(0.9); | |||
transform-origin: center center; | |||
} | |||
</style> |
@@ -0,0 +1,239 @@ | |||
<template> | |||
<view | |||
class="u-count-num" | |||
:style="{ | |||
fontSize: fontSize + 'rpx', | |||
fontWeight: bold ? 'bold' : 'normal', | |||
color: color | |||
}" | |||
> | |||
{{ displayValue }} | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* countTo 数字滚动 | |||
* @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。 | |||
* @tutorial https://www.uviewui.com/components/countTo.html | |||
* @property {String Number} start-val 开始值 | |||
* @property {String Number} end-val 结束值 | |||
* @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000) | |||
* @property {Boolean} autoplay 是否自动开始滚动(默认true) | |||
* @property {String Number} decimals 要显示的小数位数,见官网说明(默认0) | |||
* @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true) | |||
* @property {String} separator 千位分隔符,见官网说明 | |||
* @property {String} color 字体颜色(默认#303133) | |||
* @property {String Number} font-size 字体大小,单位rpx(默认50) | |||
* @property {Boolean} bold 字体是否加粗(默认false) | |||
* @event {Function} end 数值滚动到目标值时触发 | |||
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to> | |||
*/ | |||
export default { | |||
name: 'u-count-to', | |||
props: { | |||
// 开始的数值,默认从0增长到某一个数 | |||
startVal: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 要滚动的目标数值,必须 | |||
endVal: { | |||
type: [Number, String], | |||
default: 0, | |||
required: true | |||
}, | |||
// 滚动到目标数值的动画持续时间,单位为毫秒(ms) | |||
duration: { | |||
type: [Number, String], | |||
default: 2000 | |||
}, | |||
// 设置数值后是否自动开始滚动 | |||
autoplay: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 要显示的小数位数 | |||
decimals: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 是否在即将到达目标数值的时候,使用缓慢滚动的效果 | |||
useEasing: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 十进制分割 | |||
decimal: { | |||
type: [Number, String], | |||
default: '.' | |||
}, | |||
// 字体颜色 | |||
color: { | |||
type: String, | |||
default: '#303133' | |||
}, | |||
// 字体大小 | |||
fontSize: { | |||
type: [Number, String], | |||
default: 50 | |||
}, | |||
// 是否加粗字体 | |||
bold: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 千位分隔符,类似金额的分割(¥23,321.05中的",") | |||
separator: { | |||
type: String, | |||
default: '' | |||
} | |||
}, | |||
data() { | |||
return { | |||
localStartVal: this.startVal, | |||
displayValue: this.formatNumber(this.startVal), | |||
printVal: null, | |||
paused: false, // 是否暂停 | |||
localDuration: Number(this.duration), | |||
startTime: null, // 开始的时间 | |||
timestamp: null, // 时间戳 | |||
remaining: null, // 停留的时间 | |||
rAF: null, | |||
lastTime: 0 // 上一次的时间 | |||
}; | |||
}, | |||
computed: { | |||
countDown() { | |||
return this.startVal > this.endVal; | |||
} | |||
}, | |||
watch: { | |||
startVal() { | |||
this.autoplay && this.start(); | |||
}, | |||
endVal() { | |||
this.autoplay && this.start(); | |||
} | |||
}, | |||
mounted() { | |||
this.autoplay && this.start(); | |||
}, | |||
methods: { | |||
easingFn(t, b, c, d) { | |||
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b; | |||
}, | |||
requestAnimationFrame(callback) { | |||
const currTime = new Date().getTime(); | |||
// 为了使setTimteout的尽可能的接近每秒60帧的效果 | |||
const timeToCall = Math.max(0, 16 - (currTime - this.lastTime)); | |||
const id = setTimeout(() => { | |||
callback(currTime + timeToCall); | |||
}, timeToCall); | |||
this.lastTime = currTime + timeToCall; | |||
return id; | |||
}, | |||
cancelAnimationFrame(id) { | |||
clearTimeout(id); | |||
}, | |||
// 开始滚动数字 | |||
start() { | |||
this.localStartVal = this.startVal; | |||
this.startTime = null; | |||
this.localDuration = this.duration; | |||
this.paused = false; | |||
this.rAF = this.requestAnimationFrame(this.count); | |||
}, | |||
// 暂定状态,重新再开始滚动;或者滚动状态下,暂停 | |||
reStart() { | |||
if (this.paused) { | |||
this.resume(); | |||
this.paused = false; | |||
} else { | |||
this.stop(); | |||
this.paused = true; | |||
} | |||
}, | |||
// 暂停 | |||
stop() { | |||
this.cancelAnimationFrame(this.rAF); | |||
}, | |||
// 重新开始(暂停的情况下) | |||
resume() { | |||
this.startTime = null; | |||
this.localDuration = this.remaining; | |||
this.localStartVal = this.printVal; | |||
this.requestAnimationFrame(this.count); | |||
}, | |||
// 重置 | |||
reset() { | |||
this.startTime = null; | |||
this.cancelAnimationFrame(this.rAF); | |||
this.displayValue = this.formatNumber(this.startVal); | |||
}, | |||
count(timestamp) { | |||
if (!this.startTime) this.startTime = timestamp; | |||
this.timestamp = timestamp; | |||
const progress = timestamp - this.startTime; | |||
this.remaining = this.localDuration - progress; | |||
if (this.useEasing) { | |||
if (this.countDown) { | |||
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration); | |||
} else { | |||
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration); | |||
} | |||
} else { | |||
if (this.countDown) { | |||
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration); | |||
} else { | |||
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration); | |||
} | |||
} | |||
if (this.countDown) { | |||
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal; | |||
} else { | |||
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal; | |||
} | |||
this.displayValue = this.formatNumber(this.printVal); | |||
if (progress < this.localDuration) { | |||
this.rAF = this.requestAnimationFrame(this.count); | |||
} else { | |||
this.$emit('end'); | |||
} | |||
}, | |||
// 判断是否数字 | |||
isNumber(val) { | |||
return !isNaN(parseFloat(val)); | |||
}, | |||
formatNumber(num) { | |||
// 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错 | |||
num = Number(num); | |||
num = num.toFixed(Number(this.decimals)); | |||
num += ''; | |||
const x = num.split('.'); | |||
let x1 = x[0]; | |||
const x2 = x.length > 1 ? this.decimal + x[1] : ''; | |||
const rgx = /(\d+)(\d{3})/; | |||
if (this.separator && !this.isNumber(this.separator)) { | |||
while (rgx.test(x1)) { | |||
x1 = x1.replace(rgx, '$1' + this.separator + '$2'); | |||
} | |||
} | |||
return x1 + x2; | |||
}, | |||
destroyed() { | |||
this.cancelAnimationFrame(this.rAF); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-count-num { | |||
display: inline-block; | |||
text-align: center; | |||
} | |||
</style> |
@@ -0,0 +1,151 @@ | |||
<template> | |||
<view class="u-divider" :style="{ | |||
height: height == 'auto' ? 'auto' : height + 'rpx', | |||
backgroundColor: bgColor, | |||
marginBottom: marginBottom + 'rpx', | |||
marginTop: marginTop + 'rpx' | |||
}" @tap="click"> | |||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view> | |||
<view v-if="useSlot" class="u-divider-text" :style="{ | |||
color: color, | |||
fontSize: fontSize + 'rpx' | |||
}"><slot /></view> | |||
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* divider 分割线 | |||
* @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。 | |||
* @tutorial https://www.uviewui.com/components/divider.html | |||
* @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx | |||
* @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6) | |||
* @property {String} color 文字颜色(默认#909399) | |||
* @property {String Number} fontSize 字体大小,单位rpx(默认26) | |||
* @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff) | |||
* @property {String Number} height 整个divider的高度,单位rpx(默认40) | |||
* @property {String} type 将线条设置主题色(默认primary) | |||
* @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true) | |||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0) | |||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0) | |||
* @event {Function} click divider组件被点击时触发 | |||
* @example <u-divider color="#fa3534">长河落日圆</u-divider> | |||
*/ | |||
export default { | |||
name: 'u-divider', | |||
props: { | |||
// 单一边divider横线的宽度(数值),单位rpx。或者百分比 | |||
halfWidth: { | |||
type: [Number, String], | |||
default: 150 | |||
}, | |||
// divider横线的颜色,如设置, | |||
borderColor: { | |||
type: String, | |||
default: '#dcdfe6' | |||
}, | |||
// 主题色,可以是primary|info|success|warning|error之一值 | |||
type: { | |||
type: String, | |||
default: 'primary' | |||
}, | |||
// 文字颜色 | |||
color: { | |||
type: String, | |||
default: '#909399' | |||
}, | |||
// 文字大小,单位rpx | |||
fontSize: { | |||
type: [Number, String], | |||
default: 26 | |||
}, | |||
// 整个divider的背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '#ffffff' | |||
}, | |||
// 整个divider的高度单位rpx | |||
height: { | |||
type: [Number, String], | |||
default: 'auto' | |||
}, | |||
// 上边距 | |||
marginTop: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 下边距 | |||
marginBottom: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙 | |||
useSlot: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
computed: { | |||
lineStyle() { | |||
let style = {}; | |||
if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth; | |||
else style.width = this.halfWidth + 'rpx'; | |||
// borderColor优先级高于type值 | |||
if(this.borderColor) style.borderColor = this.borderColor; | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click'); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-divider { | |||
width: 100%; | |||
position: relative; | |||
text-align: center; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
overflow: hidden; | |||
flex-direction: row; | |||
} | |||
.u-divider-line { | |||
border-bottom: 1px solid $u-border-color; | |||
transform: scale(1, 0.5); | |||
transform-origin: center; | |||
&--bordercolor--primary { | |||
border-color: $u-type-primary; | |||
} | |||
&--bordercolor--success { | |||
border-color: $u-type-success; | |||
} | |||
&--bordercolor--error { | |||
border-color: $u-type-primary; | |||
} | |||
&--bordercolor--info { | |||
border-color: $u-type-info; | |||
} | |||
&--bordercolor--warning { | |||
border-color: $u-type-warning; | |||
} | |||
} | |||
.u-divider-text { | |||
white-space: nowrap; | |||
padding: 0 16rpx; | |||
display: inline-flex; | |||
} | |||
</style> |
@@ -0,0 +1,242 @@ | |||
<template> | |||
<view class="dropdown-list-wapper u-flex u-flex-1"> | |||
<view | |||
v-for="(drop, index) in dropdownListFromFather" | |||
:key="drop.name" | |||
:show="drop.show" | |||
class="u-selected-class u-dropdown-list" | |||
:style="{ zIndex: zIndex + 1 }" | |||
> | |||
<slot name="selectionbox"> | |||
<view | |||
:style="{ height: top + 'rpx' }" | |||
class="drop-item u-flex u-justify-center" | |||
@click="handleDropClick(drop)" | |||
> | |||
<text :style="{ color: drop.show ? activeColor : '#999' }"> | |||
{{ getTitle(drop.options) }} | |||
</text> | |||
<view | |||
class="u-animation" | |||
:class="[drop.show ? 'u-animation-show' : '']" | |||
> | |||
<u-icon | |||
v-if="drop.show" | |||
name="arrow-up-fill" | |||
:size="18" | |||
:color="activeColor" | |||
></u-icon> | |||
<u-icon v-else name="arrow-down-fill" :size="18"></u-icon> | |||
</view> | |||
</view> | |||
</slot> | |||
<view | |||
class="u-dropdown-view" | |||
:class="[drop.show ? 'u-dropdownlist-show' : '']" | |||
:style="{ | |||
background: bgcolor, | |||
height: drop.show ? 'auto' : 0, | |||
top: top + 'rpx' | |||
}" | |||
> | |||
<slot name="dropdownbox"> | |||
<view class="u-selected-list"> | |||
<view | |||
class="select-item u-flex u-align-center u-border-bottom u-align-between" | |||
:style="{ color: select.select ? activeColor : '#666666' }" | |||
@tap="handleSelected(select, drop.options)" | |||
v-for="(select, n) in drop.options" | |||
:key="n" | |||
> | |||
<text>{{ select.text }}</text> | |||
<u-icon | |||
v-if="select.select" | |||
class="select-icon" | |||
:color="activeColor" | |||
size="35" | |||
name="checkmark" | |||
></u-icon> | |||
</view> | |||
</view> | |||
</slot> | |||
</view> | |||
</view> | |||
<u-mask | |||
duration="100" | |||
:show="dropdownShow" | |||
@click="closeMask" | |||
:z-index="zIndex" | |||
></u-mask> | |||
</view> | |||
</template> | |||
<script> | |||
const dropdownOption1 = [ | |||
{ id: 0, text: '类型', value: '', select: false }, | |||
{ id: 1, text: '全场券', value: 1, select: false }, | |||
{ id: 2, text: '品类券', value: 2, select: false }, | |||
{ id: 3, text: '单品券', value: 3, select: false }, | |||
{ id: 4, text: '业务券', value: 4, select: false } | |||
] | |||
const dropdownOption2 = [ | |||
{ id: 5, text: '状态', value: '', select: false }, | |||
{ id: 6, text: '可使用', value: 1, select: false }, | |||
{ id: 7, text: '已过期', value: 2, select: false } | |||
] | |||
const dropdownOption3 = [ | |||
{ id: 8, text: '优惠力度', value: '', select: false }, | |||
{ id: 9, text: '满100-20', value: 1, select: false }, | |||
{ id: 10, text: '满100-50', value: 2, select: false } | |||
] | |||
export default { | |||
props: { | |||
// 下拉框数据 | |||
dropdownList: { | |||
type: Array, | |||
default: () => [ | |||
{ show: false, options: dropdownOption1 }, | |||
{ show: false, options: dropdownOption2 }, | |||
{ show: false, options: dropdownOption3 } | |||
], | |||
required: true, | |||
validator: value => | |||
value.every(item => Array.isArray(item.options) && item.options.length) | |||
}, | |||
//背景颜色 | |||
bgcolor: { | |||
type: String, | |||
default: 'none' | |||
}, | |||
//top rpx 选择框高度也用这个值 | |||
top: { | |||
type: Number, | |||
default: 90 | |||
}, | |||
// 菜单标题和选项的选中态颜色 | |||
activeColor: { | |||
type: String, | |||
default: '#e7141a' | |||
}, | |||
// mask和下拉列表的z-index | |||
zIndex: { | |||
type: [String, Number], | |||
default: 21 | |||
} | |||
}, | |||
data() { | |||
return { | |||
dropdownShow: false, | |||
dropdownListFromFather: this.dropdownList | |||
} | |||
}, | |||
computed: {}, | |||
methods: { | |||
getTitle(item = []) { | |||
const obj = item.find(v => v.select) || {} | |||
if (obj.select) { | |||
return obj.text | |||
} else { | |||
if (item[0]) { | |||
item[0].select = true | |||
return item[0].text | |||
} | |||
} | |||
return '' | |||
}, | |||
handleDropClick(item) { | |||
if (item.show) { | |||
item.show = false | |||
this.dropdownShow = false | |||
return | |||
} | |||
this.dropdownListFromFather.map(item => { | |||
item.show = false | |||
}) | |||
const t = setTimeout(() => { | |||
item.show = true | |||
this.dropdownShow = true | |||
clearTimeout(t) | |||
}, 100) | |||
}, | |||
closeMask() { | |||
this.dropdownShow = false | |||
this.dropdownListFromFather.map(item => { | |||
item.show = false | |||
}) | |||
}, | |||
handleSelected(select, options) { | |||
options.map(item => { | |||
item.select = false | |||
}) | |||
select.select = true | |||
this.closeMask() | |||
// 返回选中对象和下拉列表数组 | |||
this.$emit('change', select, options) | |||
} | |||
}, | |||
watch: { | |||
dropdownList: { | |||
handler(v) { | |||
this.dropdownListFromFather = v | |||
}, | |||
deep: true | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.dropdown-list-wapper { | |||
position: relative; | |||
} | |||
.u-dropdown-view { | |||
width: 100%; | |||
overflow: hidden; | |||
position: absolute; | |||
z-index: 9999; | |||
left: 0; | |||
right: 0; | |||
/* opacity: 0; */ | |||
visibility: hidden; | |||
transition: height 0.5s ease-in-out; | |||
.u-selected-list { | |||
background-color: #fff; | |||
.select-item { | |||
color: #666666; | |||
font-size: 28rpx; | |||
padding: 30rpx 54rpx 30rpx 30rpx; | |||
margin-left: 30rpx; | |||
} | |||
.select-item.selectActive { | |||
color: #e7141a; | |||
} | |||
} | |||
} | |||
.u-dropdownlist-show { | |||
/* opacity: 1; */ | |||
visibility: visible; | |||
} | |||
.u-dropdown-list { | |||
flex: 1; | |||
// z-index: 22; | |||
background: #fff; | |||
position: static; | |||
} | |||
.drop-item { | |||
justify-content: center; | |||
color: #999999; | |||
font-size: 30rpx; | |||
> text { | |||
margin-right: 10rpx; | |||
} | |||
/deep/ { | |||
.uicon { | |||
position: relative; | |||
top: -2rpx; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,193 @@ | |||
<template> | |||
<view class="u-empty" v-if="show" :style="{ | |||
marginTop: marginTop + 'rpx' | |||
}"> | |||
<u-icon | |||
:name="src ? src : 'empty-' + mode" | |||
:custom-style="iconStyle" | |||
:label="text ? text : icons[mode]" | |||
label-pos="bottom" | |||
:label-color="color" | |||
:label-size="fontSize" | |||
:size="iconSize" | |||
:color="iconColor" | |||
margin-top="14" | |||
></u-icon> | |||
<view class="u-slot-wrap"> | |||
<slot name="bottom"></slot> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* empty 内容为空 | |||
* @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。 | |||
* @tutorial https://www.uviewui.com/components/empty.html | |||
* @property {String} color 文字颜色(默认#c0c4cc) | |||
* @property {String} text 文字提示(默认“无内容”) | |||
* @property {String} src 自定义图标路径,如定义,mode参数会失效 | |||
* @property {String Number} font-size 提示文字的大小,单位rpx(默认28) | |||
* @property {String} mode 内置的图标,见官网说明(默认data) | |||
* @property {String Number} img-width 图标的宽度,单位rpx(默认240) | |||
* @property {String} img-height 图标的高度,单位rpx(默认auto) | |||
* @property {String Number} margin-top 组件距离上一个元素之间的距离(默认0) | |||
* @property {Boolean} show 是否显示组件(默认true) | |||
* @event {Function} click 点击组件时触发 | |||
* @event {Function} close 点击关闭按钮时触发 | |||
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty> | |||
*/ | |||
export default { | |||
name: "u-empty", | |||
props: { | |||
// 图标路径 | |||
src: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 提示文字 | |||
text: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 文字颜色 | |||
color: { | |||
type: String, | |||
default: '#c0c4cc' | |||
}, | |||
// 图标的颜色 | |||
iconColor: { | |||
type: String, | |||
default: '#c0c4cc' | |||
}, | |||
// 图标的大小 | |||
iconSize: { | |||
type: [String, Number], | |||
default: 120 | |||
}, | |||
// 文字大小,单位rpx | |||
fontSize: { | |||
type: [String, Number], | |||
default: 26 | |||
}, | |||
// 选择预置的图标类型 | |||
mode: { | |||
type: String, | |||
default: 'data' | |||
}, | |||
// 图标宽度,单位rpx | |||
imgWidth: { | |||
type: [String, Number], | |||
default: 120 | |||
}, | |||
// 图标高度,单位rpx | |||
imgHeight: { | |||
type: [String, Number], | |||
default: 'auto' | |||
}, | |||
// 是否显示组件 | |||
show: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 组件距离上一个元素之间的距离 | |||
marginTop: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
iconStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
} | |||
}, | |||
data() { | |||
return { | |||
icons: { | |||
car: '购物车为空', | |||
page: '页面不存在', | |||
search: '没有搜索结果', | |||
address: '没有收货地址', | |||
wifi: '没有WiFi', | |||
order: '订单为空', | |||
coupon: '没有优惠券', | |||
favor: '暂无收藏', | |||
permission: '无权限', | |||
history: '无历史记录', | |||
news: '无新闻列表', | |||
message: '消息列表为空', | |||
list: '列表为空', | |||
data: '数据为空' | |||
}, | |||
// icons: [{ | |||
// icon: 'car', | |||
// text: '购物车为空' | |||
// },{ | |||
// icon: 'page', | |||
// text: '页面不存在' | |||
// },{ | |||
// icon: 'search', | |||
// text: '没有搜索结果' | |||
// },{ | |||
// icon: 'address', | |||
// text: '没有收货地址' | |||
// },{ | |||
// icon: 'wifi', | |||
// text: '没有WiFi' | |||
// },{ | |||
// icon: 'order', | |||
// text: '订单为空' | |||
// },{ | |||
// icon: 'coupon', | |||
// text: '没有优惠券' | |||
// },{ | |||
// icon: 'favor', | |||
// text: '暂无收藏' | |||
// },{ | |||
// icon: 'permission', | |||
// text: '无权限' | |||
// },{ | |||
// icon: 'history', | |||
// text: '无历史记录' | |||
// },{ | |||
// icon: 'news', | |||
// text: '无新闻列表' | |||
// },{ | |||
// icon: 'message', | |||
// text: '消息列表为空' | |||
// },{ | |||
// icon: 'list', | |||
// text: '列表为空' | |||
// },{ | |||
// icon: 'data', | |||
// text: '数据为空' | |||
// }], | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-empty { | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
align-items: center; | |||
height: 100%; | |||
} | |||
.u-image { | |||
margin-bottom: 20rpx; | |||
} | |||
.u-slot-wrap { | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
margin-top: 20rpx; | |||
} | |||
</style> |
@@ -0,0 +1,370 @@ | |||
<template> | |||
<view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }"> | |||
<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]"> | |||
<view class="u-label" :class="[required ? 'u-required' : '']" :style="{ | |||
justifyContent: justifyContent, | |||
flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1' | |||
}"> | |||
<view class="u-icon-wrap" v-if="icon"> | |||
<u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon> | |||
</view> | |||
<slot name="icon"></slot> | |||
<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text> | |||
</view> | |||
<view class="fild-body"> | |||
<view class="u-flex-1 u-flex" :style="[inputWrapStyle]"> | |||
<textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value" | |||
:placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" | |||
:focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm" | |||
@tap="fieldClick" /> | |||
<input | |||
v-else | |||
:style="[fieldStyle]" | |||
:type="type" | |||
class="u-flex-1 u-field__input-wrap" | |||
:value="value" | |||
:password="password || this.type === 'password'" | |||
:placeholder="placeholder" | |||
:placeholderStyle="placeholderStyle" | |||
:disabled="disabled" | |||
:maxlength="inputMaxlength" | |||
:focus="focus" | |||
:confirmType="confirmType" | |||
@focus="onFocus" | |||
@blur="onBlur" | |||
@input="onInput" | |||
@confirm="onConfirm" | |||
@tap="fieldClick" | |||
/> | |||
</view> | |||
<u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @touchstart="onClear"/> | |||
<view class="u-button-wrap"><slot name="right" /></view> | |||
<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" /> | |||
</view> | |||
</view> | |||
<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{ | |||
paddingLeft: labelWidth + 'rpx' | |||
}">{{ errorMessage }}</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* field 输入框 | |||
* @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。 | |||
* @tutorial https://www.uviewui.com/components/field.html | |||
* @property {String} type 输入框的类型(默认text) | |||
* @property {String} icon label左边的图标,限uView的图标名称 | |||
* @property {Object} icon-style 左边图标的样式,对象形式 | |||
* @property {Boolean} right-icon 输入框右边的图标名称,限uView的图标名称(默认false) | |||
* @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false) | |||
* @property {String} label 输入框左边的文字提示 | |||
* @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false) | |||
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true) | |||
* @property {Number String} label-width label的宽度,单位rpx(默认130) | |||
* @property {String} label-align label的文字对齐方式(默认left) | |||
* @property {Object} field-style 自定义输入框的样式,对象形式 | |||
* @property {Number | String} clear-size 清除图标的大小,单位rpx(默认30) | |||
* @property {String} input-align 输入框内容对齐方式(默认left) | |||
* @property {Boolean} border-bottom 是否显示field的下边框(默认true) | |||
* @property {Boolean} border-top 是否显示field的上边框(默认false) | |||
* @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266) | |||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true) | |||
* @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息 | |||
* @property {String} placeholder 输入框的提示文字 | |||
* @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd" | |||
* @property {Boolean} focus 是否自动获得焦点(默认false) | |||
* @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false) | |||
* @property {Boolean} disabled 是否不可输入(默认false) | |||
* @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140) | |||
* @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done) | |||
* @event {Function} input 输入框内容发生变化时触发 | |||
* @event {Function} focus 输入框获得焦点时触发 | |||
* @event {Function} blur 输入框失去焦点时触发 | |||
* @event {Function} confirm 点击完成按钮时触发 | |||
* @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发 | |||
* @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明 | |||
* @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field> | |||
*/ | |||
export default { | |||
name:"u-field", | |||
props: { | |||
icon: String, | |||
rightIcon: String, | |||
// arrowDirection: { | |||
// type: String, | |||
// default: 'right' | |||
// }, | |||
required: Boolean, | |||
label: String, | |||
password: Boolean, | |||
clearable: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 左边标题的宽度单位rpx | |||
labelWidth: { | |||
type: [Number, String], | |||
default: 130 | |||
}, | |||
// 对齐方式,left|center|right | |||
labelAlign: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
inputAlign: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
iconColor: { | |||
type: String, | |||
default: '#606266' | |||
}, | |||
autoHeight: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
errorMessage: { | |||
type: [String, Boolean], | |||
default: '' | |||
}, | |||
placeholder: String, | |||
placeholderStyle: String, | |||
focus: Boolean, | |||
fixed: Boolean, | |||
value: [Number, String], | |||
type: { | |||
type: String, | |||
default: 'text' | |||
}, | |||
disabled: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
maxlength: { | |||
type: [Number, String], | |||
default: 140 | |||
}, | |||
confirmType: { | |||
type: String, | |||
default: 'done' | |||
}, | |||
// lable的位置,可选为 left-左边,top-上边 | |||
labelPosition: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
// 输入框的自定义样式 | |||
fieldStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
// 清除按钮的大小 | |||
clearSize: { | |||
type: [Number, String], | |||
default: 30 | |||
}, | |||
// lable左边的图标样式,对象形式 | |||
iconStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
// 是否显示上边框 | |||
borderTop: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否显示下边框 | |||
borderBottom: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
}, | |||
data() { | |||
return { | |||
focused: false, | |||
itemIndex: 0, | |||
}; | |||
}, | |||
computed: { | |||
inputWrapStyle() { | |||
let style = {}; | |||
style.textAlign = this.inputAlign; | |||
// 判断lable的位置,如果是left的话,让input左边两边有间隙 | |||
if(this.labelPosition == 'left') { | |||
style.margin = `0 8rpx`; | |||
} else { | |||
// 如果lable是top的,input的左边就没必要有间隙了 | |||
style.marginRight = `8rpx`; | |||
} | |||
return style; | |||
}, | |||
rightIconStyle() { | |||
let style = {}; | |||
if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)'; | |||
if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)'; | |||
else style.transform = 'roate(0deg)'; | |||
return style; | |||
}, | |||
labelStyle() { | |||
let style = {}; | |||
if(this.labelAlign == 'left') style.justifyContent = 'flext-start'; | |||
if(this.labelAlign == 'center') style.justifyContent = 'center'; | |||
if(this.labelAlign == 'right') style.justifyContent = 'flext-end'; | |||
return style; | |||
}, | |||
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法 | |||
justifyContent() { | |||
if(this.labelAlign == 'left') return 'flex-start'; | |||
if(this.labelAlign == 'center') return 'center'; | |||
if(this.labelAlign == 'right') return 'flex-end'; | |||
}, | |||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值 | |||
inputMaxlength() { | |||
return Number(this.maxlength) | |||
}, | |||
// label的位置 | |||
fieldInnerStyle() { | |||
let style = {}; | |||
if(this.labelPosition == 'left') { | |||
style.flexDirection = 'row'; | |||
} else { | |||
style.flexDirection = 'column'; | |||
} | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
onInput(event) { | |||
this.$emit('input', event.target.value); | |||
}, | |||
onFocus(event) { | |||
this.focused = true; | |||
this.$emit('focus', event); | |||
}, | |||
onBlur(event) { | |||
this.focused = false; | |||
this.$emit('blur', event); | |||
}, | |||
onConfirm(e) { | |||
this.$emit('confirm', e.detail.value); | |||
}, | |||
onClear(event) { | |||
this.$emit('input', ''); | |||
}, | |||
rightIconClick() { | |||
this.$emit('right-icon-click'); | |||
this.$emit('click'); | |||
}, | |||
fieldClick() { | |||
this.$emit('click'); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-field { | |||
font-size: 28rpx; | |||
padding: 20rpx 28rpx; | |||
text-align: left; | |||
position: relative; | |||
color: $u-main-color; | |||
} | |||
.u-field-inner { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.u-textarea-inner { | |||
align-items: flex-start; | |||
} | |||
.u-textarea-class { | |||
min-height: 96rpx; | |||
width: auto; | |||
font-size: 28rpx; | |||
} | |||
.fild-body { | |||
display: flex; | |||
flex: 1; | |||
align-items: center; | |||
} | |||
.u-arror-right { | |||
margin-left: 8rpx; | |||
} | |||
.u-label-text { | |||
display: inline-block; | |||
} | |||
.u-label-left-gap { | |||
margin-left: 6rpx; | |||
} | |||
.u-label-postion-top { | |||
flex-direction: column; | |||
align-items: flex-start; | |||
} | |||
.u-label { | |||
width: 130rpx; | |||
flex: 1 1 130rpx; | |||
text-align: left; | |||
position: relative; | |||
display: flex; | |||
align-items: center; | |||
} | |||
.u-required::before { | |||
content: '*'; | |||
position: absolute; | |||
left: -16rpx; | |||
font-size: 14px; | |||
color: $u-type-error; | |||
height: 9px; | |||
line-height: 1; | |||
} | |||
.u-field__input-wrap { | |||
position: relative; | |||
overflow: hidden; | |||
font-size: 28rpx; | |||
height: 48rpx; | |||
flex: 1; | |||
width: auto; | |||
} | |||
.u-clear-icon { | |||
display: flex; | |||
align-items: center; | |||
} | |||
.u-error-message { | |||
color: $u-type-error; | |||
font-size: 26rpx; | |||
text-align: left; | |||
} | |||
.placeholder-style { | |||
color: rgb(150, 151, 153); | |||
} | |||
.u-input-class { | |||
font-size: 28rpx; | |||
} | |||
.u-button-wrap { | |||
margin-left: 8rpx; | |||
} | |||
</style> |
@@ -0,0 +1,396 @@ | |||
<template> | |||
<view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}"> | |||
<view class="u-form-item__body" :style="{ | |||
flexDirection: elLabelPosition == 'left' ? 'row' : 'column' | |||
}"> | |||
<view class="u-form-item--left" :style="{ | |||
width: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '100%', | |||
flex: `0 0 ${elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '100%'}`, | |||
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx', | |||
}"> | |||
<!-- 为了块对齐 --> | |||
<view class="u-form-item--left__content"> | |||
<!-- nvue不支持伪元素before --> | |||
<text v-if="required" class="u-form-item--left__content--required">*</text> | |||
<view class="u-form-item--left__content__icon" v-if="leftIcon"> | |||
<u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon> | |||
</view> | |||
<view class="u-form-item--left__content__label" :style="[elLabelStyle, { | |||
'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end' | |||
}]"> | |||
{{label}} | |||
</view> | |||
</view> | |||
</view> | |||
<view class="u-form-item--right u-flex"> | |||
<view class="u-form-item--right__content"> | |||
<view class="u-form-item--right__content__slot "> | |||
<slot /> | |||
</view> | |||
<view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon"> | |||
<u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon> | |||
<slot name="right" /> | |||
</view> | |||
</view> | |||
</view> | |||
</view> | |||
<view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{ | |||
paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0', | |||
}">{{validateMessage}}</view> | |||
</view> | |||
</template> | |||
<script> | |||
import Emitter from '../../libs/util/emitter.js'; | |||
import schema from '../../libs/util/async-validator'; | |||
// 去除警告信息 | |||
schema.warning = function(){}; | |||
/** | |||
* form-item 表单item | |||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 | |||
* @tutorial http://uviewui.com/components/form.html | |||
* @property {String} label 左侧提示文字 | |||
* @property {Object} prop 表单域model对象的属性名,在使用 validate、resetFields 方法的情况下,该属性是必填的 | |||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框 | |||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方 | |||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90) | |||
* @property {Object} label-style lable的样式,对象形式 | |||
* @property {String} label-align lable的对齐方式 | |||
* @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址 | |||
* @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址 | |||
* @property {Object} left-icon-style 左侧图标的样式,对象形式 | |||
* @property {Object} right-icon-style 右侧图标的样式,对象形式 | |||
* @property {Boolean} required 是否显示左边的"*"号,这里仅起展示作用,如需校验必填,请通过rules配置必填规则(默认false) | |||
* @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item> | |||
*/ | |||
export default { | |||
name: 'u-form-item', | |||
mixins: [Emitter], | |||
inject: { | |||
uForm: { | |||
default() { | |||
return null | |||
} | |||
} | |||
}, | |||
props: { | |||
// input的label提示语 | |||
label: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 绑定的值 | |||
prop: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示表单域的下划线边框 | |||
borderBottom: { | |||
type: [String, Boolean], | |||
default: '' | |||
}, | |||
// label的位置,left-左边,top-上边 | |||
labelPosition: { | |||
type: String, | |||
default: '' | |||
}, | |||
// label的宽度,单位rpx | |||
labelWidth: { | |||
type: [String, Number], | |||
default: '' | |||
}, | |||
// lable的样式,对象形式 | |||
labelStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
// lable字体的对齐方式 | |||
labelAlign: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 右侧图标 | |||
rightIcon: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 左侧图标 | |||
leftIcon: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 左侧图标的样式 | |||
leftIconStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
// 左侧图标的样式 | |||
rightIconStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
// 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 | |||
required: { | |||
type: Boolean, | |||
default: false | |||
} | |||
}, | |||
data() { | |||
return { | |||
initialValue: '', // 存储的默认值 | |||
// isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成 | |||
validateState: '', // 是否校验成功 | |||
validateMessage: '' ,// 校验失败的提示语 | |||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色, | |||
errorType: ['message'], | |||
}; | |||
}, | |||
created() { | |||
// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用 | |||
this.parent = this.$u.$parent.call(this, 'u-form'); | |||
}, | |||
watch: { | |||
validateState(val) { | |||
this.broadcastInputError(); | |||
}, | |||
// 监听u-form组件的errorType的变化 | |||
"uForm.errorType"(val) { | |||
this.errorType = val; | |||
this.broadcastInputError(); | |||
}, | |||
}, | |||
computed: { | |||
fieldValue() { | |||
return this.uForm.model[this.prop]; | |||
}, | |||
showError() { | |||
return type => { | |||
// 如果errorType数组中含有none,或者toast提示类型 | |||
if(this.errorType.indexOf('none') >= 0) return false; | |||
else if(this.errorType.indexOf(type) >= 0) return true; | |||
else return false; | |||
} | |||
}, | |||
// label的宽度 | |||
elLabelWidth() { | |||
// label默认宽度为90,优先使用本组件的值,如果没有,则用u-form的值 | |||
return this.labelWidth ? this.labelWidth : (this.parent ? this.parent.labelWidth : 90); | |||
}, | |||
// label的样式 | |||
elLabelStyle() { | |||
return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parent ? this.parent.labelStyle : {}); | |||
}, | |||
// label的位置,左侧或者上方 | |||
elLabelPosition() { | |||
return this.labelPosition ? this.labelPosition : (this.parent ? this.parent.labelPosition : 'left'); | |||
}, | |||
// label的对齐方式 | |||
elLabelAlign() { | |||
return this.labelAlign ? this.labelAlign : (this.parent ? this.parent.labelAlign : 'left'); | |||
}, | |||
// label的下划线 | |||
elBorderBottom() { | |||
// 子组件的borderBottom默认为空字符串,如果不等于空字符串,意味着子组件设置了值,优先使用子组件的值 | |||
return this.borderBottom !== '' ? this.borderBottom : this.parent ? this.parent.borderBottom : true; | |||
} | |||
}, | |||
methods: { | |||
broadcastInputError() { | |||
// 子组件发出事件,第三个参数为true或者false,true代表有错误 | |||
this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border')); | |||
}, | |||
// 判断是否需要required校验 | |||
setRules() { | |||
let that = this; | |||
// 由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成 | |||
// 从父组件u-form拿到当前u-form-item需要验证 的规则 | |||
// let rules = this.getRules(); | |||
// if (rules.length) { | |||
// this.isRequired = rules.some(rule => { | |||
// // 如果有必填项,就返回,没有的话,就是undefined | |||
// return rule.required; | |||
// }); | |||
// } | |||
// blur事件 | |||
this.$on('on-form-blur', that.onFieldBlur); | |||
// change事件 | |||
this.$on('on-form-change', that.onFieldChange); | |||
}, | |||
// 从u-form的rules属性中,取出当前u-form-item的校验规则 | |||
getRules() { | |||
// 父组件的所有规则 | |||
let rules = this.uForm.rules; | |||
rules = rules ? rules[this.prop] : []; | |||
// 保证返回的是一个数组形式 | |||
return [].concat(rules || []); | |||
}, | |||
// blur事件时进行表单校验 | |||
onFieldBlur() { | |||
this.validation('blur'); | |||
}, | |||
// change事件进行表单校验 | |||
onFieldChange() { | |||
this.validation('change'); | |||
}, | |||
// 过滤出符合要求的rule规则 | |||
getFilteredRule(triggerType = '') { | |||
let rules = this.getRules(); | |||
// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证 | |||
if(!triggerType) return rules; | |||
// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件 | |||
// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change'] | |||
// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性 | |||
return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1); | |||
}, | |||
// 校验数据 | |||
validation(trigger, callback = () => {}) { | |||
// blur和change是否有当前方式的校验规则 | |||
let rules = this.getFilteredRule(trigger); | |||
// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件u-form会因为 | |||
// 对count变量的统计错误而无法进入上一层的回调 | |||
if (!rules || rules.length === 0) { | |||
return callback(''); | |||
} | |||
// 设置当前的装填,标识为校验中 | |||
this.validateState = 'validating'; | |||
// 调用async-validator的方法 | |||
let validator = new schema({ [this.prop]: rules }); | |||
validator.validate({ [this.prop]: this.fieldValue }, { firstFields: true }, (errors, fields) => { | |||
// 记录状态和报错信息 | |||
this.validateState = !errors ? 'success' : 'error'; | |||
this.validateMessage = errors ? errors[0].message : ''; | |||
// 调用回调方法 | |||
callback(this.validateMessage); | |||
}); | |||
}, | |||
// 清空当前的u-form-item | |||
resetField() { | |||
this.uForm.model[this.prop] = this.initialValue; | |||
// 设置为`success`状态,只是为了清空错误标记 | |||
this.validateState = 'success'; | |||
} | |||
}, | |||
// 组件创建完成时,将当前实例保存到u-form中 | |||
mounted() { | |||
// 如果没有传入prop,或者uForm为空(如果u-form-input单独使用,就不会有uForm注入),就不进行校验 | |||
if (!this.prop || this.uForm === null) return; | |||
// 发出事件,让父组件将本实例加入到管理数组中 | |||
this.dispatch('u-form', 'on-form-item-add', this); | |||
this.errorType = this.uForm.errorType; | |||
// 设置初始值 | |||
this.initialValue = this.fieldValue; | |||
// 添加表单校验,这里必须要写在$nextTick中,因为u-form的rules是通过ref手动传入的 | |||
// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给u-form,导致规则为空 | |||
this.$nextTick(() =>{ | |||
this.setRules(); | |||
}) | |||
}, | |||
// 组件销毁前,将实例从 Form 的缓存中移除 | |||
beforeDestroy() { | |||
this.dispatch('u-form', 'on-form-item-remove', this); | |||
}, | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-form-item { | |||
display: flex; | |||
// align-items: flex-start; | |||
padding: 20rpx 0; | |||
font-size: 28rpx; | |||
color: $u-main-color; | |||
box-sizing: border-box; | |||
line-height: $u-form-item-height; | |||
flex-direction: column; | |||
&__border-bottom--error:after { | |||
border-color: $u-type-error; | |||
} | |||
&__body { | |||
display: flex; | |||
} | |||
&--left { | |||
display: flex; | |||
align-items: center; | |||
&__content { | |||
position: relative; | |||
display: flex; | |||
align-items: center; | |||
padding-right: 10rpx; | |||
flex: 1; | |||
&__icon { | |||
margin-right: 8rpx; | |||
} | |||
&--required { | |||
position: absolute; | |||
left: -16rpx; | |||
vertical-align: middle; | |||
color: $u-type-error; | |||
padding-top: 6rpx; | |||
} | |||
&__label { | |||
display: flex; | |||
align-items: center; | |||
flex: 1; | |||
} | |||
} | |||
} | |||
&--right { | |||
flex: 1; | |||
&__content { | |||
display: flex; | |||
align-items: center; | |||
flex: 1; | |||
&__slot { | |||
flex: 1; | |||
/* #ifndef MP */ | |||
display: flex; | |||
align-items: center; | |||
/* #endif */ | |||
} | |||
&__icon { | |||
margin-left: 10rpx; | |||
color: $u-light-color; | |||
font-size: 30rpx; | |||
} | |||
} | |||
} | |||
&__message { | |||
font-size: 24rpx; | |||
line-height: 24rpx; | |||
color: $u-type-error; | |||
margin-top: 12rpx; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,149 @@ | |||
<template> | |||
<view class="u-form"><slot /></view> | |||
</template> | |||
<script> | |||
/** | |||
* form 表单 | |||
* @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。 | |||
* @tutorial http://uviewui.com/components/form.html | |||
* @property {Object} model 表单数据对象 | |||
* @property {Boolean} border-bottom 是否显示表单域的下划线边框 | |||
* @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方 | |||
* @property {String Number} label-width 提示文字的宽度,单位rpx(默认90) | |||
* @property {Object} label-style lable的样式,对象形式 | |||
* @property {String} label-align lable的对齐方式 | |||
* @property {Object} rules 通过ref设置,见官网说明 | |||
* @property {Array} error-type 错误的提示方式,数组形式,见上方说明(默认['message']) | |||
* @example <u-form :model="form" ref="uForm"></u-form> | |||
*/ | |||
export default { | |||
name: 'u-form', | |||
props: { | |||
// 当前form的需要验证字段的集合 | |||
model: { | |||
type: Object, | |||
default() { | |||
return {}; | |||
} | |||
}, | |||
// 验证规则 | |||
// rules: { | |||
// type: [Object, Function, Array], | |||
// default() { | |||
// return {}; | |||
// } | |||
// }, | |||
// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色, | |||
// border-bottom-下边框呈现红色,none-无提示 | |||
errorType: { | |||
type: Array, | |||
default() { | |||
return ['message', 'toast'] | |||
} | |||
}, | |||
// 是否显示表单域的下划线边框 | |||
borderBottom: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// label的位置,left-左边,top-上边 | |||
labelPosition: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
// label的宽度,单位rpx | |||
labelWidth: { | |||
type: [String, Number], | |||
default: 90 | |||
}, | |||
// lable字体的对齐方式 | |||
labelAlign: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
// lable的样式,对象形式 | |||
labelStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
}, | |||
provide() { | |||
return { | |||
uForm: this | |||
}; | |||
}, | |||
data() { | |||
return { | |||
rules: {} | |||
}; | |||
}, | |||
created() { | |||
// 存储当前form下的所有u-form-item的实例 | |||
// 不能定义在data中,否则微信小程序会造成循环引用而报错 | |||
this.fields = []; | |||
// 存当前实例 | |||
let that = this; | |||
// 监听on-form-item-add事件,将子组件添加到fields中 | |||
this.$on('on-form-item-add', item => { | |||
if (item) { | |||
that.fields.push(item); | |||
} | |||
}); | |||
// 删除当前有的实例 | |||
this.$on('on-form-item-remove', item => { | |||
// 如果当前没有prop的话表示当前不要进行删除(因为没有注入) | |||
if (item.prop) { | |||
that.fields.splice(that.fields.indexOf(item), 1); | |||
} | |||
}); | |||
}, | |||
methods: { | |||
setRules(rules) { | |||
this.rules = rules; | |||
}, | |||
// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法 | |||
resetFields() { | |||
this.fields.map(field => { | |||
field.resetField(); | |||
}); | |||
}, | |||
// 校验全部数据 | |||
validate(callback) { | |||
return new Promise(resolve => { | |||
// 对所有的u-form-item进行校验 | |||
let valid = true; // 默认通过 | |||
let count = 0; // 用于标记是否检查完毕 | |||
let errorArr = []; // 存放错误信息 | |||
this.fields.map(field => { | |||
// 调用每一个u-form-item实例的validation的校验方法 | |||
field.validation('', error => { | |||
// 如果任意一个u-form-item校验不通过,就意味着整个表单不通过 | |||
if (error) { | |||
valid = false; | |||
errorArr.push(error); | |||
} | |||
// 当历遍了所有的u-form-item时,调用promise的then方法 | |||
if (++count === this.fields.length) { | |||
resolve(valid); // 进入promise的then方法 | |||
// 判断是否设置了toast的提示方式,只提示最前面的表单域的第一个错误信息 | |||
if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) { | |||
this.$u.toast(errorArr[0]); | |||
} | |||
// 调用回调方法 | |||
if (typeof callback == 'function') callback(valid); | |||
} | |||
}); | |||
}); | |||
}); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
</style> |
@@ -0,0 +1,52 @@ | |||
<template> | |||
<u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm"> | |||
<view class="u-update-content"> | |||
<rich-text :nodes="content"></rich-text> | |||
</view> | |||
</u-modal> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
show: false, | |||
content: ` | |||
1. 修复badge组件的size参数无效问题<br> | |||
2. 新增Modal模态框组件<br> | |||
3. 新增压窗屏组件,可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br> | |||
4. 修复键盘组件在微信小程序上遮罩无效的问题 | |||
`, | |||
} | |||
}, | |||
onReady() { | |||
this.show = true; | |||
}, | |||
methods: { | |||
cancel() { | |||
this.closeModal(); | |||
}, | |||
confirm() { | |||
this.closeModal(); | |||
}, | |||
closeModal() { | |||
uni.navigateBack(); | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-full-content { | |||
background-color: #00C777; | |||
} | |||
.u-update-content { | |||
font-size: 26rpx; | |||
color: $u-content-color; | |||
line-height: 1.7; | |||
padding: 30rpx; | |||
} | |||
</style> |
@@ -0,0 +1,54 @@ | |||
<template> | |||
<view class="u-gap" :style="[gapStyle]"></view> | |||
</template> | |||
<script> | |||
/** | |||
* gap 间隔槽 | |||
* @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量 | |||
* @tutorial https://www.uviewui.com/components/gap.html | |||
* @property {String} bg-color 背景颜色(默认#f3f4f6) | |||
* @property {String Number} height 分割槽高度,单位rpx(默认30) | |||
* @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0) | |||
* @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0) | |||
* @example <u-gap height="80" bg-color="#bbb"></u-gap> | |||
*/ | |||
export default { | |||
name: "u-gap", | |||
props: { | |||
bgColor: { | |||
type: String, | |||
default: 'transparent ' // 背景透明 | |||
}, | |||
// 高度 | |||
height: { | |||
type: [String, Number], | |||
default: 30 | |||
}, | |||
// 与上一个组件的距离 | |||
marginTop: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 与下一个组件的距离 | |||
marginBottom: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
}, | |||
computed: { | |||
gapStyle() { | |||
return { | |||
backgroundColor: this.bgColor, | |||
height: this.height + 'rpx', | |||
marginTop: this.marginTop + 'rpx', | |||
marginBottom: this.marginBottom + 'rpx' | |||
}; | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
</style> |
@@ -0,0 +1,113 @@ | |||
<template> | |||
<view class="u-grid-item" :hover-class="hoverClass" | |||
:hover-stay-time="200" @tap="click" :style="{ | |||
background: bgColor, | |||
width: width, | |||
}"> | |||
<view class="u-grid-item-box" :class="[showBorder ? 'u-border-right u-border-bottom' : '']"> | |||
<slot /> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* gridItem 提示 | |||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用 | |||
* @tutorial https://www.uviewui.com/components/grid.html | |||
* @property {String} bg-color 宫格的背景颜色(默认#ffffff) | |||
* @property {String Number} index 点击宫格时,返回的值 | |||
* @event {Function} click 点击宫格触发 | |||
* @example <u-grid-item></u-grid-item> | |||
*/ | |||
export default { | |||
name: "u-grid-item", | |||
props: { | |||
// 背景颜色 | |||
bgColor: { | |||
type: String, | |||
default: '#ffffff' | |||
}, | |||
// 点击时返回的index | |||
index: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
}, | |||
// 父组件通过provide传递过来的整个this | |||
inject: ['uGrid'], | |||
data() { | |||
return { | |||
hoverClass: '', // 按下去的时候,是否显示背景灰色 | |||
}; | |||
}, | |||
created() { | |||
this.hoverClass = this.uGrid.hoverClass; | |||
}, | |||
computed: { | |||
// 小于2,显示2列;大于12,显示12列 | |||
colNum() { | |||
return this.col < 2 ? 2 : this.col > 12 ? 12 : this.col; | |||
}, | |||
// 每个grid-item的宽度 | |||
width() { | |||
return 100 / Number(this.uGrid.col) + '%'; | |||
}, | |||
// 是否显示边框 | |||
// 为了迎合演示的需要,从created生命周期移到此,以为演示中可能需要动态修改有无边框 | |||
showBorder() { | |||
return this.uGrid.border; | |||
} | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click', this.index); | |||
this.uGrid.click(this.index); | |||
} | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-grid-item { | |||
box-sizing: border-box; | |||
background: #fff; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
position: relative; | |||
flex-direction: column; | |||
/* #ifdef MP */ | |||
position: relative; | |||
float: left; | |||
/* #endif */ | |||
} | |||
.u-grid-item-hover { | |||
background: #f7f7f7 !important; | |||
} | |||
.u-grid-marker-box { | |||
position: absolute; | |||
display: inline-block; | |||
line-height: 0; | |||
} | |||
.u-grid-marker-wrap { | |||
position: absolute; | |||
} | |||
.u-grid-item-box { | |||
padding: 30rpx 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
flex-direction: column; | |||
flex: 1; | |||
width: 100%; | |||
height: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,95 @@ | |||
<template> | |||
<view class="u-grid" :class="{'u-border-top u-border-left': border}" :style="[gridStyle]"><slot /></view> | |||
</template> | |||
<script> | |||
/** | |||
* grid 宫格布局 | |||
* @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。 | |||
* @tutorial https://www.uviewui.com/components/grid.html | |||
* @property {String Number} col 宫格的列数(默认3) | |||
* @property {Boolean} border 是否显示宫格的边框(默认true) | |||
* @property {Boolean} hover-class 点击宫格的时候,是否显示按下的灰色背景(默认false) | |||
* @event {Function} click 点击宫格触发 | |||
* @example <u-grid :col="3" @click="click"></u-grid> | |||
*/ | |||
export default { | |||
name: 'u-grid', | |||
props: { | |||
// 分成几列 | |||
col: { | |||
type: [Number, String], | |||
default: 3 | |||
}, | |||
// 是否显示边框 | |||
border: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 | |||
align: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
// 宫格按压时的样式类,"none"为无效果 | |||
hoverClass: { | |||
type: String, | |||
default: 'u-hover-class' | |||
} | |||
}, | |||
data() { | |||
return { | |||
index: 0, | |||
} | |||
}, | |||
provide() { | |||
return { | |||
uGrid: this | |||
} | |||
}, | |||
computed: { | |||
// 宫格对齐方式 | |||
gridStyle() { | |||
let style = {}; | |||
switch(this.align) { | |||
case 'left': | |||
style.justifyContent = 'flex-start'; | |||
break; | |||
case 'center': | |||
style.justifyContent = 'center'; | |||
break; | |||
case 'right': | |||
style.justifyContent = 'flex-end'; | |||
break; | |||
default: style.justifyContent = 'flex-start'; | |||
}; | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
click(index) { | |||
this.$emit('click', index); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-grid { | |||
width: 100%; | |||
/* #ifdef MP */ | |||
position: relative; | |||
box-sizing: border-box; | |||
overflow: hidden; | |||
/* #endif */ | |||
/* #ifndef MP */ | |||
display: flex; | |||
flex-wrap: wrap; | |||
align-items: center; | |||
/* #endif */ | |||
} | |||
</style> |
@@ -0,0 +1,238 @@ | |||
<template> | |||
<view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]"> | |||
<image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image> | |||
<text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass" @touchstart="touchstart"></text> | |||
<text v-if="label" class="u-icon__label" :style="{ | |||
color: labelColor, | |||
fontSize: $u.addUnit(labelSize), | |||
marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0, | |||
marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0, | |||
marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0, | |||
marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0, | |||
}">{{label}}</text> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* icon 图标 | |||
* @description 基于字体的图标集,包含了大多数常见场景的图标。 | |||
* @tutorial https://www.uviewui.com/components/icon.html | |||
* @property {String} name 图标名称,见示例图标集 | |||
* @property {String} color 图标颜色(默认inherit) | |||
* @property {String | Number} size 图标字体大小,单位rpx(默认32) | |||
* @property {String | Number} label-size label字体大小,单位rpx(默认28) | |||
* @property {String} label 图标右侧的label文字(默认28) | |||
* @property {String} label-pos label文字相对于图标的位置,只能right或bottom(默认right) | |||
* @property {String} label-color label字体颜色(默认#606266) | |||
* @property {Object} custom-style icon的样式,对象形式 | |||
* @property {String} custom-prefix 自定义字体图标库时,需要写上此值 | |||
* @property {String | Number} margin-left label在右侧时与图标的距离,单位rpx(默认6) | |||
* @property {String | Number} margin-top label在下方时与图标的距离,单位rpx(默认6) | |||
* @property {String | Number} margin-bottom label在上方时与图标的距离,单位rpx(默认6) | |||
* @property {String | Number} margin-right label在左侧时与图标的距离,单位rpx(默认6) | |||
* @property {String} label-pos label相对于图标的位置,只能right或bottom(默认right) | |||
* @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出 | |||
* @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网 | |||
* @event {Function} click 点击图标时触发 | |||
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon> | |||
*/ | |||
export default { | |||
name: 'u-icon', | |||
props: { | |||
// 图标类名 | |||
name: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 图标颜色,可接受主题色 | |||
color: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 字体大小,单位rpx | |||
size: { | |||
type: [Number, String], | |||
default: 'inherit' | |||
}, | |||
// 是否显示粗体 | |||
bold: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个) | |||
index: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
// 触摸图标时的类名 | |||
hoverClass: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 自定义扩展前缀,方便用户扩展自己的图标库 | |||
customPrefix: { | |||
type: String, | |||
default: 'uicon' | |||
}, | |||
// 图标右边或者下面的文字 | |||
label: { | |||
type: String, | |||
default: '' | |||
}, | |||
// label的位置,只能右边或者下边 | |||
labelPos: { | |||
type: String, | |||
default: 'right' | |||
}, | |||
// label的大小 | |||
labelSize: { | |||
type: [String, Number], | |||
default: '28' | |||
}, | |||
// label的颜色 | |||
labelColor: { | |||
type: String, | |||
default: '#606266' | |||
}, | |||
// label与图标的距离(横向排列) | |||
marginLeft: { | |||
type: [String, Number], | |||
default: '6' | |||
}, | |||
// label与图标的距离(竖向排列) | |||
marginTop: { | |||
type: [String, Number], | |||
default: '6' | |||
}, | |||
// label与图标的距离(竖向排列) | |||
marginRight: { | |||
type: [String, Number], | |||
default: '6' | |||
}, | |||
// label与图标的距离(竖向排列) | |||
marginBottom: { | |||
type: [String, Number], | |||
default: '6' | |||
}, | |||
// 图片的mode | |||
imgMode: { | |||
type: String, | |||
default: 'widthFix' | |||
}, | |||
// 自定义样式 | |||
customStyle: { | |||
type: Object, | |||
default() { | |||
return {} | |||
} | |||
}, | |||
}, | |||
computed: { | |||
customClass() { | |||
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 && this.$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: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size), | |||
fontWeight: this.bold ? 'bold' : 'normal' | |||
}; | |||
// 非主题色值时,才当作颜色值 | |||
if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color; | |||
return style; | |||
}, | |||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式 | |||
isImg() { | |||
return this.name.indexOf('/') !== -1; | |||
}, | |||
imgStyle() { | |||
let style = {}; | |||
style.width = this.$u.addUnit(this.size); | |||
style.height = this.$u.addUnit(this.size); | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
click() { | |||
this.$emit('click', this.index); | |||
}, | |||
touchstart() { | |||
this.$emit('touchstart', this.index); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
@import '../../iconfont.css'; | |||
.u-icon { | |||
display: inline-flex; | |||
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 { | |||
&--primary { | |||
color: $u-type-primary; | |||
} | |||
&--success { | |||
color: $u-type-success; | |||
} | |||
&--error { | |||
color: $u-type-error; | |||
} | |||
&--warning { | |||
color: $u-type-warning; | |||
} | |||
&--info { | |||
color: $u-type-info; | |||
} | |||
} | |||
&__img { | |||
height: auto; | |||
will-change: transform; | |||
} | |||
&__label { | |||
line-height: 1; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,220 @@ | |||
<template> | |||
<view | |||
class="u-image" | |||
@tap="onClick" | |||
:style="[wrapStyle, backgroundStyle]" | |||
> | |||
<image | |||
v-if="!isError" | |||
:src="src" | |||
:mode="mode" | |||
@error="onErrorHandler" | |||
@load="onLoadHandler" | |||
:lazy-load="lazyLoad" | |||
class="u-image__image" | |||
:style="{ | |||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius), | |||
}" | |||
></image> | |||
<view v-if="showLoading && loading" class="u-image__loading" :style="{ | |||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius), | |||
backgroundColor: this.bgColor | |||
}"> | |||
<slot v-if="$slots.loading" name="loading" /> | |||
<u-icon v-else :name="loadingIcon"></u-icon> | |||
</view> | |||
<view v-if="showError && isError && !loading" class="u-image__error" :style="{ | |||
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius), | |||
}"> | |||
<slot v-if="$slots.error" name="error" /> | |||
<u-icon v-else :name="errorIcon"></u-icon> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
// 图片地址 | |||
src: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 裁剪模式 | |||
mode: { | |||
type: String, | |||
default: 'aspectFill' | |||
}, | |||
// 宽度,单位任意 | |||
width: { | |||
type: [String, Number], | |||
default: '100%' | |||
}, | |||
// 高度,单位任意 | |||
height: { | |||
type: [String, Number], | |||
default: 'auto' | |||
}, | |||
// 图片形状,circle-圆形,square-方形 | |||
shape: { | |||
type: String, | |||
default: 'square' | |||
}, | |||
// 圆角,单位任意 | |||
borderRadius: { | |||
type: [String, Number], | |||
default: 0 | |||
}, | |||
// 是否懒加载,微信小程序、App、百度小程序、字节跳动小程序 | |||
lazyLoad: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 开启长按图片显示识别微信小程序码菜单 | |||
showMenuByLongpress: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 加载中的图标,或者小图片 | |||
loadingIcon: { | |||
type: String, | |||
default: 'photo' | |||
}, | |||
// 加载失败的图标,或者小图片 | |||
errorIcon: { | |||
type: String, | |||
default: 'error-circle' | |||
}, | |||
// 是否显示加载中的图标或者自定义的slot | |||
showLoading: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示加载错误的图标或者自定义的slot | |||
showError: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否需要淡入效果 | |||
fade: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 只支持网络资源,只对微信小程序有效 | |||
webp: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 过渡时间,单位ms | |||
duration: { | |||
type: [String, Number], | |||
default: 500 | |||
}, | |||
// 背景颜色,用于深色页面加载图片时,为了和背景色融合 | |||
bgColor: { | |||
type: String, | |||
default: '#f3f4f6' | |||
} | |||
}, | |||
data() { | |||
return { | |||
// 图片是否加载错误,如果是,则显示错误占位图 | |||
isError: false, | |||
// 初始化组件时,默认为加载中状态 | |||
loading: true, | |||
// 不透明度,为了实现淡入淡出的效果 | |||
opacity: 1, | |||
// 过渡时间,因为props的值无法修改,故需要一个中间值 | |||
durationTime: this.duration, | |||
// 图片加载完成时,去掉背景颜色,因为如果是png图片,就会显示灰色的背景 | |||
backgroundStyle: {} | |||
}; | |||
}, | |||
computed: { | |||
wrapStyle() { | |||
let style = {}; | |||
// 通过调用addUnit()方法,如果有单位,如百分比,px单位等,直接返回,如果是纯粹的数值,则加上rpx单位 | |||
style.width = this.$u.addUnit(this.width); | |||
style.height = this.$u.addUnit(this.height); | |||
// 如果是配置了圆形,设置50%的圆角,否则按照默认的配置值 | |||
style.borderRadius = this.shape == 'circle' ? '50%' : this.$u.addUnit(this.borderRadius); | |||
// 如果设置圆角,必须要有hidden,否则可能圆角无效 | |||
style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible'; | |||
if(this.fade) { | |||
style.opacity = this.opacity; | |||
style.transition = `opacity ${Number(this.durationTime) / 1000}s ease-in-out`; | |||
} | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
// 点击图片 | |||
onClick() { | |||
this.$emit('click'); | |||
}, | |||
// 图片加载失败 | |||
onErrorHandler() { | |||
this.loading = false; | |||
this.isError = true; | |||
this.$emit('error'); | |||
}, | |||
// 图片加载完成,标记loading结束 | |||
onLoadHandler() { | |||
this.loading = false; | |||
this.isError = false; | |||
this.$emit('load'); | |||
// 如果不需要动画效果,就不执行下方代码,同时移除加载时的背景颜色 | |||
// 否则无需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 scoped lang="scss"> | |||
@import '../../libs/css/style.components.scss'; | |||
.u-image { | |||
position: relative; | |||
transition: opacity 0.5s ease-in-out; | |||
&__image { | |||
width: 100%; | |||
height: 100%; | |||
} | |||
&__loading, &__error { | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background-color: $u-bg-color; | |||
color: $u-tips-color; | |||
font-size: 46rpx; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,84 @@ | |||
<template> | |||
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" --> | |||
<view> | |||
<view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]"> | |||
<view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]"> | |||
<slot v-if="useSlot" /> | |||
<block v-else> | |||
<text>{{ index }}</text> | |||
</block> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* indexAnchor 索引列表锚点 | |||
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用 | |||
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props | |||
* @property {Boolean} use-slot 是否使用自定义内容的插槽(默认false) | |||
* @property {String Number} index 索引字符,如果定义了use-slot,此参数自动失效 | |||
* @property {Object} custStyle 自定义样式,对象形式,如"{color: 'red'}" | |||
* @event {Function} default 锚点位置显示内容,默认为索引字符 | |||
* @example <u-index-anchor :index="item" /> | |||
*/ | |||
export default { | |||
name: "u-index-anchor", | |||
props: { | |||
useSlot: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
index: { | |||
type: String, | |||
default: '' | |||
}, | |||
customStyle: { | |||
type: Object, | |||
default () { | |||
return {} | |||
} | |||
} | |||
}, | |||
data() { | |||
return { | |||
active: false, | |||
wrapperStyle: {}, | |||
anchorStyle: {} | |||
} | |||
}, | |||
inject: ['UIndexList'], | |||
mounted() { | |||
this.UIndexList.children.push(this); | |||
this.UIndexList.updateData(); | |||
}, | |||
computed: { | |||
customAnchorStyle() { | |||
return Object.assign(this.anchorStyle, this.customStyle); | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-index-anchor { | |||
box-sizing: border-box; | |||
padding: 14rpx 24rpx; | |||
color: #606266; | |||
width: 100%; | |||
font-weight: 500; | |||
font-size: 28rpx; | |||
line-height: 1.2; | |||
background-color: rgb(245, 245, 245); | |||
} | |||
.u-index-anchor--active { | |||
right: 0; | |||
left: 0; | |||
color: #2979ff; | |||
background-color: #fff; | |||
} | |||
</style> |
@@ -0,0 +1,321 @@ | |||
<template> | |||
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸,所以在外面套一个"壳" --> | |||
<view> | |||
<view class="u-index-bar"> | |||
<slot /> | |||
<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove" | |||
@touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop"> | |||
<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}" | |||
:data-index="index"> | |||
{{ item }} | |||
</view> | |||
</view> | |||
<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{ | |||
zIndex: alertZIndex | |||
}"> | |||
<text>{{indexList[touchmoveIndex]}}</text> | |||
</view> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
var indexList = function() { | |||
var indexList = []; | |||
var charCodeOfA = 'A'.charCodeAt(0); | |||
for (var i = 0; i < 26; i++) { | |||
indexList.push(String.fromCharCode(charCodeOfA + i)); | |||
} | |||
return indexList; | |||
}; | |||
/** | |||
* indexList 索引列表 | |||
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用 | |||
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props | |||
* @property {Number String} scroll-top 当前滚动高度,自定义组件无法获得滚动条事件,所以依赖接入方传入 | |||
* @property {Array} index-list 索引字符列表,数组(默认A-Z) | |||
* @property {Number String} z-index 锚点吸顶时的层级(默认965) | |||
* @property {Boolean} sticky 是否开启锚点自动吸顶(默认true) | |||
* @property {Number String} offset-top 锚点自动吸顶时与顶部的距离(默认0) | |||
* @property {String} highlight-color 锚点和右边索引字符高亮颜色(默认#2979ff) | |||
* @event {Function} select 选中右边索引字符时触发 | |||
* @example <u-index-list :scrollTop="scrollTop"></u-index-list> | |||
*/ | |||
export default { | |||
name: "u-index-list", | |||
props: { | |||
sticky: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
zIndex: { | |||
type: [Number, String], | |||
default: '' | |||
}, | |||
scrollTop: { | |||
type: [Number, String], | |||
default: 0, | |||
}, | |||
offsetTop: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
indexList: { | |||
type: Array, | |||
default () { | |||
return indexList() | |||
} | |||
}, | |||
activeColor: { | |||
type: String, | |||
default: '#2979ff' | |||
} | |||
}, | |||
created() { | |||
// #ifdef H5 | |||
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44; | |||
// #endif | |||
// #ifndef H5 | |||
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0; | |||
// #endif | |||
// 只能在created生命周期定义children,如果在data定义,会因为在子组件中通过provide/inject | |||
// 进行push时而导致的莫名其妙的错误 | |||
this.children = []; | |||
}, | |||
provide() { | |||
return { | |||
UIndexList: this | |||
} | |||
}, | |||
data() { | |||
return { | |||
activeAnchorIndex: 0, | |||
showSidebar: true, | |||
// children: [], | |||
touchmove: false, | |||
touchmoveIndex: 0, | |||
} | |||
}, | |||
watch: { | |||
scrollTop() { | |||
this.updateData() | |||
} | |||
}, | |||
computed: { | |||
// 弹出toast的z-index值 | |||
alertZIndex() { | |||
return this.$u.zIndex.toast; | |||
} | |||
}, | |||
methods: { | |||
updateData() { | |||
this.timer && clearTimeout(this.timer); | |||
this.timer = setTimeout(() => { | |||
this.showSidebar = !!this.children.length; | |||
this.setRect().then(() => { | |||
this.onScroll(); | |||
}); | |||
}, 0); | |||
}, | |||
setRect() { | |||
return Promise.all([ | |||
this.setAnchorsRect(), | |||
this.setListRect(), | |||
this.setSiderbarRect() | |||
]); | |||
}, | |||
setAnchorsRect() { | |||
return Promise.all(this.children.map((anchor, index) => anchor | |||
.$uGetRect('.u-index-anchor-wrapper') | |||
.then((rect) => { | |||
Object.assign(anchor, { | |||
height: rect.height, | |||
top: rect.top | |||
}); | |||
}))); | |||
}, | |||
setListRect() { | |||
return this.$uGetRect('.u-index-bar').then((rect) => { | |||
Object.assign(this, { | |||
height: rect.height, | |||
top: rect.top + this.scrollTop | |||
}); | |||
}); | |||
}, | |||
setSiderbarRect() { | |||
return this.$uGetRect('.u-index-bar__sidebar').then(rect => { | |||
this.sidebar = { | |||
height: rect.height, | |||
top: rect.top | |||
}; | |||
}); | |||
}, | |||
getActiveAnchorIndex() { | |||
const { | |||
children | |||
} = this; | |||
const { | |||
sticky | |||
} = this; | |||
for (let i = this.children.length - 1; i >= 0; i--) { | |||
const preAnchorHeight = i > 0 ? children[i - 1].height : 0; | |||
const reachTop = sticky ? preAnchorHeight : 0; | |||
if (reachTop >= children[i].top) { | |||
return i; | |||
} | |||
} | |||
return -1; | |||
}, | |||
onScroll() { | |||
const { | |||
children = [] | |||
} = this; | |||
if (!children.length) { | |||
return; | |||
} | |||
const { | |||
sticky, | |||
stickyOffsetTop, | |||
zIndex, | |||
scrollTop, | |||
activeColor | |||
} = this; | |||
const active = this.getActiveAnchorIndex(); | |||
this.activeAnchorIndex = active; | |||
if (sticky) { | |||
let isActiveAnchorSticky = false; | |||
if (active !== -1) { | |||
isActiveAnchorSticky = | |||
children[active].top <= 0; | |||
} | |||
children.forEach((item, index) => { | |||
if (index === active) { | |||
let wrapperStyle = ''; | |||
let anchorStyle = { | |||
color: `${activeColor}` | |||
}; | |||
if (isActiveAnchorSticky) { | |||
wrapperStyle = { | |||
height: `${children[index].height}px` | |||
}; | |||
anchorStyle = { | |||
position: 'fixed', | |||
top: `${stickyOffsetTop}px`, | |||
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`, | |||
color: `${activeColor}` | |||
}; | |||
} | |||
item.active = active; | |||
item.wrapperStyle = wrapperStyle; | |||
item.anchorStyle = anchorStyle; | |||
} else if (index === active - 1) { | |||
const currentAnchor = children[index]; | |||
const currentOffsetTop = currentAnchor.top; | |||
const targetOffsetTop = index === children.length - 1 ? | |||
this.top : | |||
children[index + 1].top; | |||
const parentOffsetHeight = targetOffsetTop - currentOffsetTop; | |||
const translateY = parentOffsetHeight - currentAnchor.height; | |||
const anchorStyle = { | |||
position: 'relative', | |||
transform: `translate3d(0, ${translateY}px, 0)`, | |||
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`, | |||
color: `${activeColor}` | |||
}; | |||
item.active = active; | |||
item.anchorStyle = anchorStyle; | |||
} else { | |||
item.active = false; | |||
item.anchorStyle = ''; | |||
item.wrapperStyle = ''; | |||
} | |||
}); | |||
} | |||
}, | |||
onTouchMove(event) { | |||
this.touchmove = true; | |||
const sidebarLength = this.children.length; | |||
const touch = event.touches[0]; | |||
const itemHeight = this.sidebar.height / sidebarLength; | |||
let clientY = 0; | |||
clientY = touch.clientY; | |||
let index = Math.floor((clientY - this.sidebar.top) / itemHeight); | |||
if (index < 0) { | |||
index = 0; | |||
} else if (index > sidebarLength - 1) { | |||
index = sidebarLength - 1; | |||
} | |||
this.touchmoveIndex = index; | |||
this.scrollToAnchor(index); | |||
}, | |||
onTouchStop() { | |||
this.touchmove = false; | |||
this.scrollToAnchorIndex = null; | |||
}, | |||
scrollToAnchor(index) { | |||
if (this.scrollToAnchorIndex === index) { | |||
return; | |||
} | |||
this.scrollToAnchorIndex = index; | |||
const anchor = this.children.find((item) => item.index === this.indexList[index]); | |||
if (anchor) { | |||
this.$emit('select', anchor.index); | |||
uni.pageScrollTo({ | |||
duration: 0, | |||
scrollTop: anchor.top + this.scrollTop | |||
}); | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-index-bar { | |||
position: relative | |||
} | |||
.u-index-bar__sidebar { | |||
position: fixed; | |||
top: 50%; | |||
right: 0; | |||
display: flex; | |||
flex-direction: column; | |||
text-align: center; | |||
transform: translateY(-50%); | |||
user-select: none; | |||
z-index: 99; | |||
} | |||
.u-index-bar__index { | |||
font-weight: 500; | |||
padding: 8rpx 18rpx; | |||
font-size: 22rpx; | |||
line-height: 1 | |||
} | |||
.u-indexed-list-alert { | |||
position: fixed; | |||
width: 120rpx; | |||
height: 120rpx; | |||
right: 90rpx; | |||
top: 50%; | |||
margin-top: -60rpx; | |||
border-radius: 24rpx; | |||
font-size: 50rpx; | |||
color: #fff; | |||
background-color: rgba(0, 0, 0, 0.65); | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
padding: 0; | |||
z-index: 9999999; | |||
} | |||
.u-indexed-list-alert text { | |||
line-height: 50rpx; | |||
} | |||
</style> |
@@ -0,0 +1,329 @@ | |||
<template> | |||
<view | |||
class="u-input" | |||
:class="{ | |||
'u-input--border': border, | |||
'u-input--error': validateState | |||
}" | |||
:style="{ | |||
padding: `0 ${border ? 20 : 0}rpx`, | |||
borderColor: borderColor, | |||
textAlign: inputAlign | |||
}" | |||
@tap.stop="inputClick" | |||
> | |||
<textarea | |||
v-if="type == 'textarea'" | |||
class="u-input__input u-input__textarea" | |||
:style="[getStyle]" | |||
:value="value" | |||
:placeholder="placeholder" | |||
:placeholderStyle="placeholderStyle" | |||
:disabled="disabled" | |||
:maxlength="inputMaxlength" | |||
:fixed="fixed" | |||
:focus="focus" | |||
:autoHeight="autoHeight" | |||
@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" | |||
@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" v-if="clearable && value != '' && focused"> | |||
<u-icon size="32" name="close-circle-fill" color="#c0c4cc" @touchstart="onClear"/> | |||
</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> | |||
import Emitter from '../../libs/util/emitter.js'; | |||
/** | |||
* 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} 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', | |||
mixins: [Emitter], | |||
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 {}; | |||
} | |||
}, | |||
// 如果 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 | |||
} | |||
}, | |||
data() { | |||
return { | |||
defaultValue: this.value, | |||
inputHeight: 70, // input的高度 | |||
textareaHeight: 100, // textarea的高度 | |||
validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色 | |||
focused: false, // 当前是否处于获得焦点的状态 | |||
showPassword: false, // 是否预览密码 | |||
}; | |||
}, | |||
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); | |||
} | |||
}, | |||
created() { | |||
// 监听u-form-item发出的错误事件,将输入框边框变红色 | |||
this.$on('on-form-item-error', this.onFormItemError); | |||
}, | |||
methods: { | |||
/** | |||
* change 事件 | |||
* @param event | |||
*/ | |||
handleInput(event) { | |||
// 当前model 赋值 | |||
this.defaultValue = event.detail.value; | |||
// vue 原生的方法 return 出去 | |||
this.$emit('input', event.detail.value); | |||
// 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上 | |||
// 尚未更新到u-form-item,导致获取的值为空,从而校验混论 | |||
this.$nextTick(() => { | |||
// 将当前的值发送到 u-form-item 进行校验 | |||
this.dispatch('u-form-item', 'on-form-change', event.detail.value); | |||
}); | |||
}, | |||
/** | |||
* blur 事件 | |||
* @param event | |||
*/ | |||
handleBlur(event) { | |||
this.focused = false; | |||
// vue 原生的方法 return 出去 | |||
this.$emit('blur', event.detail.value); | |||
this.$nextTick(() => { | |||
// 将当前的值发送到 u-form-item 进行校验 | |||
this.dispatch('u-form-item', 'on-form-blur', event.detail.value); | |||
}); | |||
}, | |||
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> | |||
.u-input { | |||
position: relative; | |||
flex: 1; | |||
display: flex; | |||
&__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> |
@@ -0,0 +1,205 @@ | |||
<template> | |||
<u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" | |||
:safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :zIndex="uZIndex"> | |||
<slot /> | |||
<view class="u-tooltip" v-if="tooltip"> | |||
<view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel"> | |||
{{cancelBtn ? '取消' : ''}} | |||
</view> | |||
<view v-if="showTips" class="u-tooltip-item u-tooltip-tips"> | |||
{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}} | |||
</view> | |||
<view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover"> | |||
{{confirmBtn ? '完成' : ''}} | |||
</view> | |||
</view> | |||
<block v-if="mode == 'number' || mode == 'card'"> | |||
<u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard> | |||
</block> | |||
<block v-else> | |||
<u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard> | |||
</block> | |||
</u-popup> | |||
</template> | |||
<script> | |||
/** | |||
* keyboard 键盘 | |||
* @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。 | |||
* @tutorial https://www.uviewui.com/components/keyboard.html | |||
* @property {String} mode 键盘类型,见官网基本使用的说明(默认number) | |||
* @property {Boolean} dot-enabled 是否显示"."按键,只在mode=number时有效(默认true) | |||
* @property {Boolean} tooltip 是否显示键盘顶部工具条(默认true) | |||
* @property {String} tips 工具条中间的提示文字,见上方基本使用的说明,如不需要,请传""空字符 | |||
* @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮(默认true) | |||
* @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮(默认true) | |||
* @property {Boolean} mask 是否显示遮罩(默认true) | |||
* @property {Number String} z-index 弹出键盘的z-index值(默认1075) | |||
* @property {Boolean} random 是否打乱键盘按键的顺序(默认false) | |||
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false) | |||
* @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘(默认true) | |||
* @event {Function} change 按键被点击(不包含退格键被点击) | |||
* @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击 | |||
* @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击 | |||
* @event {Function} backspace 键盘退格键被点击 | |||
* @example <u-keyboard mode="number" v-model="show"></u-keyboard> | |||
*/ | |||
export default { | |||
name: "u-keyboard", | |||
props: { | |||
// 键盘的类型,number-数字键盘,card-身份证键盘,car-车牌号键盘 | |||
mode: { | |||
type: String, | |||
default: 'number' | |||
}, | |||
// 是否显示键盘的"."符号 | |||
dotEnabled: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示顶部工具条 | |||
tooltip: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示工具条中间的提示 | |||
showTips: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 工具条中间的提示文字 | |||
tips: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 是否显示工具条左边的"取消"按钮 | |||
cancelBtn: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否显示工具条右边的"完成"按钮 | |||
confirmBtn: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 是否打乱键盘按键的顺序 | |||
random: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距 | |||
safeAreaInsetBottom: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否允许通过点击遮罩关闭键盘 | |||
maskCloseAble: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 通过双向绑定控制键盘的弹出与收起 | |||
value: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 是否显示遮罩,某些时候数字键盘时,用户希望看到自己的数值,所以可能不想要遮罩 | |||
mask: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// z-index值 | |||
zIndex: { | |||
type: [Number, String], | |||
default: '' | |||
} | |||
}, | |||
data() { | |||
return { | |||
//show: false | |||
} | |||
}, | |||
computed: { | |||
uZIndex() { | |||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup; | |||
} | |||
}, | |||
methods: { | |||
change(e) { | |||
this.$emit('change', e); | |||
}, | |||
// 键盘关闭 | |||
popupClose() { | |||
// 通过发送input这个特殊的事件名,可以修改父组件传给props的value的变量,也即双向绑定 | |||
this.$emit('input', false); | |||
}, | |||
// 输入完成 | |||
onConfirm() { | |||
this.popupClose(); | |||
this.$emit('confirm'); | |||
}, | |||
// 取消输入 | |||
onCancel() { | |||
this.popupClose(); | |||
this.$emit('cancel'); | |||
}, | |||
// 退格键 | |||
backspace() { | |||
this.$emit('backspace'); | |||
}, | |||
// 关闭键盘 | |||
// close() { | |||
// this.show = false; | |||
// }, | |||
// // 打开键盘 | |||
// open() { | |||
// this.show = true; | |||
// } | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-keyboard { | |||
position: relative; | |||
z-index: 1003; | |||
} | |||
.u-tooltip { | |||
display: flex; | |||
justify-content: space-between; | |||
} | |||
.u-tooltip-item { | |||
color: #333333; | |||
flex: 0 0 33.333333%; | |||
text-align: center; | |||
padding: 20rpx 10rpx; | |||
font-size: 28rpx; | |||
} | |||
.u-tooltips-submit { | |||
text-align: right; | |||
flex-grow: 1; | |||
flex-wrap: 0; | |||
padding-right: 40rpx; | |||
color: $u-type-primary; | |||
} | |||
.u-tooltip-cancel { | |||
text-align: left; | |||
flex-grow: 1; | |||
flex-wrap: 0; | |||
padding-left: 40rpx; | |||
color: #888888; | |||
} | |||
.u-tooltips-submit-hover { | |||
color: $u-type-success; | |||
} | |||
.u-tooltip-cancel-hover { | |||
color: #333333; | |||
} | |||
</style> |
@@ -0,0 +1,227 @@ | |||
<template> | |||
<view class="u-wrap" :style="{ | |||
opacity: Number(opacity), | |||
borderRadius: borderRadius + 'rpx', | |||
// 因为time值需要改变,所以不直接用duration值(不能改变父组件prop传过来的值) | |||
transition: `opacity ${time / 1000}s ease-in-out` | |||
}" | |||
:class="'u-lazy-item-' + elIndex"> | |||
<view :class="'u-lazy-item-' + elIndex"> | |||
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" v-if="!isError" class="u-lazy-item" | |||
:src="isShow ? image : loadingImg" :mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg"></image> | |||
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" class="u-lazy-item error" v-else :src="errorImg" | |||
:mode="imgMode" @load="errorImgLoaded" @tap="clickImg"></image> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* lazyLoad 懒加载 | |||
* @description 懒加载使用的场景为:页面有很多图片时,APP会同时加载所有的图片,导致页面卡顿,各个位置的图片出现前后不一致等. | |||
* @tutorial https://www.uviewui.com/components/lazyLoad.html | |||
* @property {String Number} index 用户自定义值,在事件触发时回调,用以区分是哪个图片 | |||
* @property {String} image 图片路径 | |||
* @property {String} loading-img 预加载时的占位图 | |||
* @property {String} error-img 图片加载出错时的占位图 | |||
* @property {String} threshold 触发加载时的位置,见上方说明,单位 rpx(默认300) | |||
* @property {String Number} duration 图片加载成功时,淡入淡出时间,单位ms(默认) | |||
* @property {String} effect 图片加载成功时,淡入淡出的css动画效果(默认ease-in-out) | |||
* @property {Boolean} is-effect 图片加载成功时,是否启用淡入淡出效果(默认true) | |||
* @property {String Number} border-radius 图片圆角值,单位rpx(默认0) | |||
* @property {String Number} height 图片高度,注意:实际高度可能受img-mode参数影响(默认450) | |||
* @property {String Number} mg-mode 图片的裁剪模式,详见image组件裁剪模式(默认widthFix) | |||
* @event {Function} click 点击图片时触发 | |||
* @event {Function} load 图片加载成功时触发 | |||
* @event {Function} error 图片加载失败时触发 | |||
* @example <u-lazy-load :image="image" :loading-img="loadingImg" :error-img="errorImg"></u-lazy-load> | |||
*/ | |||
export default { | |||
name: 'u-lazy-load', | |||
props: { | |||
index: { | |||
type: [Number, String] | |||
}, | |||
// 要显示的图片 | |||
image: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 图片裁剪模式 | |||
imgMode: { | |||
type: String, | |||
default: 'widthFix' | |||
}, | |||
// 占位图片路径 | |||
loadingImg: { | |||
type: String, | |||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII=' | |||
}, | |||
// 加载失败的错误占位图 | |||
errorImg: { | |||
type: String, | |||
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII=' | |||
}, | |||
// 图片进入可见区域前多少像素时,单位rpx,开始加载图片 | |||
// 负数为图片超出屏幕底部多少距离后触发懒加载,正数为图片顶部距离屏幕底部多少距离时触发(图片还没出现在屏幕上) | |||
threshold: { | |||
type: [Number, String], | |||
default: 100 | |||
}, | |||
// 淡入淡出动画的过渡时间 | |||
duration: { | |||
type: [Number, String], | |||
default: 500 | |||
}, | |||
// 渡效果的速度曲线,各个之间差别不大,因为这是淡入淡出,且时间很短,不是那些变形或者移动的情况,会明显 | |||
// linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n); | |||
effect: { | |||
type: String, | |||
default: 'ease-in-out' | |||
}, | |||
// 是否使用过渡效果 | |||
isEffect: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 圆角值 | |||
borderRadius: { | |||
type: [Number, String], | |||
default: 0 | |||
}, | |||
// 图片高度,单位rpx | |||
height: { | |||
type: [Number, String], | |||
default: '450' | |||
} | |||
}, | |||
data() { | |||
return { | |||
isShow: false, | |||
opacity: 1, | |||
time: this.duration, | |||
loadStatus: '', // 默认是懒加载中的状态 | |||
isError: false, // 图片加载失败 | |||
elIndex: this.$u.guid() | |||
} | |||
}, | |||
computed: { | |||
// 将threshold从rpx转为px | |||
getThreshold() { | |||
// 先取绝对值,因为threshold可能是负数,最后根据this.threshold是正数或者负数,重新还原 | |||
let thresholdPx = uni.upx2px(Math.abs(this.threshold)); | |||
return this.threshold < 0 ? -thresholdPx : thresholdPx; | |||
}, | |||
// 计算图片的高度,可能为auto,带%,或者直接数值 | |||
imgHeight() { | |||
return this.height == 'auto' ? 'auto' : String(this.height).indexOf('%') != -1 ? this.height : this.height + 'rpx'; | |||
} | |||
}, | |||
created() { | |||
// 由于一些特殊原因,不能将此变量放到data中定义 | |||
this.observer = {}; | |||
}, | |||
watch: { | |||
isShow(nVal) { | |||
// 如果是不开启过渡效果,直接返回 | |||
if (!this.isEffect) return; | |||
this.time = 0; | |||
// 原来opacity为1(不透明,是为了显示占位图),改成0(透明,意味着该元素显示的是背景颜色,默认的白色),再改成1,是为了获得过渡效果 | |||
this.opacity = 0; | |||
// 延时30ms,否则在浏览器H5,过渡效果无效 | |||
setTimeout(() => { | |||
this.time = this.duration; | |||
this.opacity = 1; | |||
}, 30) | |||
} | |||
}, | |||
methods: { | |||
// 点击图片触发的事件,loadlazy-还是懒加载中状态,loading-图片正在加载,loaded-图片加加载完成 | |||
clickImg() { | |||
let whichImg = ''; | |||
// 如果isShow为false,意味着图片还没开始加载,点击的只能是最开始的占位图 | |||
if (this.isShow == false) whichImg = 'lazyImg'; | |||
// 如果isError为true,意味着图片加载失败,这是只剩下错误的占位图,所以点击的只能是错误占位图 | |||
// 当然,也可以给错误的占位图元素绑定点击事件,看你喜欢~ | |||
else if (this.isError == true) whichImg = 'errorImg'; | |||
// 总共三张图片,除了两个占位图,剩下的只能是正常的那张图片了 | |||
else whichImg = 'realImg'; | |||
// 只通知当前图片的index | |||
this.$emit('click', this.index); | |||
}, | |||
// 图片加载完成事件,可能是加载占位图时触发,也可能是加载真正的图片完成时触发,通过isShow区分 | |||
imgLoaded() { | |||
// 占位图加载完成 | |||
if (this.loadStatus == '') { | |||
this.loadStatus = 'lazyed'; | |||
} | |||
// 真正的图片加载完成 | |||
else if (this.loadStatus == 'lazyed') { | |||
this.loadStatus = 'loaded'; | |||
this.$emit('load', this.index); | |||
} | |||
}, | |||
// 错误的图片加载完成 | |||
errorImgLoaded() { | |||
this.$emit('error', this.index); | |||
}, | |||
// 图片加载失败 | |||
loadError() { | |||
this.isError = true; | |||
}, | |||
disconnectObserver(observerName) { | |||
const observer = this[observerName]; | |||
observer && observer.disconnect(); | |||
}, | |||
}, | |||
beforeDestroy() { | |||
// 销毁页面时,可能还没触发某张很底部的懒加载图片,所以把这个事件给去掉 | |||
//observer.disconnect(); | |||
}, | |||
mounted() { | |||
// 此uOnReachBottom事件由mixin.js发出,目的是让页面到底时,保证所有图片都进行加载,做到绝对稳定且可靠 | |||
this.$nextTick(() => { | |||
uni.$once('uOnReachBottom', () => { | |||
if (!this.isShow) this.isShow = true; | |||
}); | |||
}) | |||
// mounted的时候,不一定挂载了这个元素,延时30ms,否则会报错或者不报错,但是也没有效果 | |||
setTimeout(() => { | |||
// 这里是组件内获取布局状态,不能用uni.createIntersectionObserver,而必须用this.createIntersectionObserver | |||
this.disconnectObserver('contentObserver'); | |||
const contentObserver = uni.createIntersectionObserver(this); | |||
// 要理解这里怎么计算的,请看这个: | |||
// https://blog.csdn.net/qq_25324335/article/details/83687695 | |||
contentObserver.relativeToViewport({ | |||
bottom: this.getThreshold, | |||
}).observe('.u-lazy-item-' + this.elIndex, (res) => { | |||
if (res.intersectionRatio > 0) { | |||
// 懒加载状态改变 | |||
this.isShow = true; | |||
// 如果图片已经加载,去掉监听,减少性能的消耗 | |||
this.disconnectObserver('contentObserver'); | |||
} | |||
}) | |||
this.contentObserver = contentObserver; | |||
}, 30) | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-wrap { | |||
background-color: #eee; | |||
overflow: hidden; | |||
} | |||
.u-lazy-item { | |||
width: 100%; | |||
// 骗系统开启硬件加速 | |||
transform: transition3d(0, 0, 0); | |||
// 防止图片加载“闪一下” | |||
will-change: transform; | |||
display: block; | |||
} | |||
</style> |
@@ -0,0 +1,145 @@ | |||
<template> | |||
<view class="u-progress" :style="{ | |||
borderRadius: round ? '100rpx' : 0, | |||
height: height + 'rpx', | |||
backgroundColor: inactiveColor | |||
}"> | |||
<view :class="[ | |||
type ? `u-type-${type}-bg` : '', | |||
striped ? 'u-striped' : '', | |||
striped && stripedActive ? 'u-striped-active' : '' | |||
]" class="u-active" :style="[progressStyle]"> | |||
<slot v-if="$slots.default" /> | |||
<block v-else-if="showPercent"> | |||
{{percent + '%'}} | |||
</block> | |||
</view> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* lineProgress 线型进度条 | |||
* @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。 | |||
* @tutorial https://www.uviewui.com/components/lineProgress.html | |||
* @property {String Number} percent 进度条百分比值,为数值类型,0-100 | |||
* @property {Boolean} round 进度条两端是否为半圆(默认true) | |||
* @property {String} type 如设置,active-color值将会失效 | |||
* @property {String} active-color 进度条激活部分的颜色(默认#19be6b) | |||
* @property {String} inactive-color 进度条的底色(默认#ececec) | |||
* @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true) | |||
* @property {String Number} height 进度条的高度,单位rpx(默认28) | |||
* @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false) | |||
* @property {Boolean} striped-active 条纹是否具有动态效果(默认false) | |||
* @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress> | |||
*/ | |||
export default { | |||
name: "u-line-progress", | |||
props: { | |||
// 两端是否显示半圆形 | |||
round: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 主题颜色 | |||
type: { | |||
type: String, | |||
default: '' | |||
}, | |||
// 激活部分的颜色 | |||
activeColor: { | |||
type: String, | |||
default: '#19be6b' | |||
}, | |||
inactiveColor: { | |||
type: String, | |||
default: '#ececec' | |||
}, | |||
// 进度百分比,数值 | |||
percent: { | |||
type: Number, | |||
default: 0 | |||
}, | |||
// 是否在进度条内部显示百分比的值 | |||
showPercent: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 进度条的高度,单位rpx | |||
height: { | |||
type: [Number, String], | |||
default: 28 | |||
}, | |||
// 是否显示条纹 | |||
striped: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
// 条纹是否显示活动状态 | |||
stripedActive: { | |||
type: Boolean, | |||
default: false | |||
} | |||
}, | |||
data() { | |||
return { | |||
} | |||
}, | |||
computed: { | |||
progressStyle() { | |||
let style = {}; | |||
style.width = this.percent + '%'; | |||
if(this.activeColor) style.backgroundColor = this.activeColor; | |||
return style; | |||
} | |||
}, | |||
methods: { | |||
} | |||
} | |||
</script> | |||
<style lang="scss" scoped> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-progress { | |||
overflow: hidden; | |||
height: 15px; | |||
display: inline-flex; | |||
align-items: center; | |||
width: 100%; | |||
border-radius: 100rpx; | |||
} | |||
.u-active { | |||
width: 0; | |||
height: 100%; | |||
align-items: center; | |||
display: flex; | |||
justify-items: flex-end; | |||
justify-content: space-around; | |||
font-size: 20rpx; | |||
color: #ffffff; | |||
transition: all 0.4s ease; | |||
} | |||
.u-striped { | |||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); | |||
background-size: 39px 39px; | |||
} | |||
.u-striped-active { | |||
animation: progress-stripes 2s linear infinite; | |||
} | |||
@keyframes progress-stripes { | |||
0% { | |||
background-position: 0 0; | |||
} | |||
100% { | |||
background-position: 39px 0; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,84 @@ | |||
<template> | |||
<view class="u-line" :style="[lineStyle]"> | |||
</view> | |||
</template> | |||
<script> | |||
/** | |||
* line 线条 | |||
* @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单 | |||
* @tutorial https://www.uviewui.com/components/line.html | |||
* @property {String} color 线条的颜色(默认#e4e7ed) | |||
* @property {String} length 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等 | |||
* @property {String} direction 线条的方向,row-横向,col-竖向(默认row) | |||
* @property {String} border-style 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线(默认solid) | |||
* @property {Boolean} hair-line 是否显示细线条(默认true) | |||
* @property {String} margin 线条与上下左右元素的间距,字符串形式,如"30rpx" | |||
* @example <u-line color="red"></u-line> | |||
*/ | |||
export default { | |||
name: 'u-line', | |||
props: { | |||
color: { | |||
type: String, | |||
default: '#e4e7ed' | |||
}, | |||
// 长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带rpx单位的值等 | |||
length: { | |||
type: String, | |||
default: '100%' | |||
}, | |||
// 线条方向,col-竖向,row-横向 | |||
direction: { | |||
type: String, | |||
default: 'row' | |||
}, | |||
// 是否显示细边框 | |||
hairLine: { | |||
type: Boolean, | |||
default: true | |||
}, | |||
// 线条与上下左右元素的间距,字符串形式,如"30rpx"、"20rpx 30rpx" | |||
margin: { | |||
type: String, | |||
default: '0' | |||
}, | |||
// 线条的类型,solid-实线,dashed-方形虚线,dotted-圆点虚线 | |||
borderStyle: { | |||
type: String, | |||
default: 'solid' | |||
} | |||
}, | |||
computed: { | |||
lineStyle() { | |||
let style = {}; | |||
style.margin = this.margin; | |||
// 如果是水平线条,边框高度为1px,再通过transform缩小一半,就是0.5px了 | |||
if(this.direction == 'row') { | |||
// 此处采用兼容分开写,兼容nvue的写法 | |||
style.borderBottomWidth = '1px'; | |||
style.borderBottomStyle = this.borderStyle; | |||
style.width = this.$u.addUnit(this.length); | |||
if(this.hairLine) style.transform = 'scaleY(0.5)'; | |||
} else { | |||
// 如果是竖向线条,边框宽度为1px,再通过transform缩小一半,就是0.5px了 | |||
style.borderLeftWidth = '1px'; | |||
style.borderLeftStyle = this.borderStyle; | |||
style.height = this.$u.addUnit(this.length); | |||
if(this.hairLine) style.transform = 'scaleX(0.5)'; | |||
} | |||
style.borderColor = this.color; | |||
return style; | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="scss"> | |||
@import "../../libs/css/style.components.scss"; | |||
.u-line { | |||
vertical-align: middle; | |||
} | |||
</style> |