program init
This commit is contained in:
commit
6df18fd03d
17
.editorconfig
Normal file
17
.editorconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# all files
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# md files
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.nvue linguist-language=Vue
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
dist/**
|
||||
.project
|
||||
unpackage/
|
||||
.DS_Store
|
||||
wxcomponents/**/*.vue
|
||||
wxcomponents/**/*.css
|
||||
136
App.vue
Normal file
136
App.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<script>
|
||||
import { mapMutations } from 'vuex'
|
||||
export default {
|
||||
onLaunch: function() {
|
||||
console.log('App Launch');
|
||||
// #ifdef APP-PLUS
|
||||
// App平台检测升级,服务端代码是通过uniCloud的云函数实现的,详情可参考:https://ext.dcloud.net.cn/plugin?id=2226
|
||||
if(plus.runtime.appid !== 'HBuilder'){ // 真机运行不需要检查更新,真机运行时appid固定为'HBuilder',这是调试基座的appid
|
||||
uni.request({
|
||||
url: 'https://7a3e3fa9-7820-41d0-be80-11927ac2026c.bspapp.com/http/update', //检查更新的服务器地址
|
||||
data: {
|
||||
appid: plus.runtime.appid,
|
||||
version: plus.runtime.version,
|
||||
imei: plus.device.imei
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode == 200 && res.data.isUpdate) {
|
||||
// 提醒用户更新
|
||||
uni.showModal({
|
||||
title: '更新提示',
|
||||
content: res.data.note ? res.data.note : '是否选择更新',
|
||||
success: (ee) => {
|
||||
if (ee.confirm) {
|
||||
plus.runtime.openURL(res.data.url);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 一键登录预登陆,可以显著提高登录速度
|
||||
uni.preLogin({
|
||||
provider: 'univerify',
|
||||
success: (res) => {
|
||||
// 成功
|
||||
this.setUniverifyErrorMsg();
|
||||
console.log("preLogin success: ", res);
|
||||
},
|
||||
fail: (res) => {
|
||||
this.setUniverifyLogin(false);
|
||||
this.setUniverifyErrorMsg(res.errMsg);
|
||||
// 失败
|
||||
console.log("preLogin fail res: ", res);
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
onShow: function() {
|
||||
console.log('App Show')
|
||||
},
|
||||
onHide: function() {
|
||||
console.log('App Hide')
|
||||
},
|
||||
globalData: {
|
||||
test: ''
|
||||
},
|
||||
methods:{
|
||||
...mapMutations(['setUniverifyErrorMsg','setUniverifyLogin'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
/* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
|
||||
@import './common/uni.css';
|
||||
|
||||
/* H5 兼容 pc 所需 */
|
||||
/* #ifdef H5 */
|
||||
@media screen and (min-width: 768px) {
|
||||
body{
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
/* 顶栏通栏样式 */
|
||||
/* .uni-top-window {
|
||||
left: 0;
|
||||
right: 0;
|
||||
} */
|
||||
|
||||
uni-page-body {
|
||||
background-color: #F5F5F5 !important;
|
||||
min-height: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.uni-top-window uni-tabbar .uni-tabbar {
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.uni-app--showleftwindow .hideOnPc {
|
||||
display: none !important;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 以下样式用于 hello uni-app 演示所需 */
|
||||
page {
|
||||
background-color: #efeff4;
|
||||
height: 100%;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.fix-pc-padding {
|
||||
padding: 0 50px;
|
||||
}
|
||||
.uni-header-logo {
|
||||
padding: 30rpx;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.uni-header-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.uni-hello-text {
|
||||
color: #7A7E83;
|
||||
}
|
||||
|
||||
.uni-hello-addfile {
|
||||
text-align: center;
|
||||
line-height: 300rpx;
|
||||
background: #FFF;
|
||||
padding: 50rpx;
|
||||
margin-top: 10px;
|
||||
font-size: 38rpx;
|
||||
color: #808080;
|
||||
}
|
||||
/* #endif*/
|
||||
</style>
|
||||
129
README.md
Normal file
129
README.md
Normal file
@ -0,0 +1,129 @@
|
||||
# hello-uniapp
|
||||
|
||||
`uni-app`框架示例,一套代码,同时发行到iOS、Android、H5、小程序等多个平台,请使用手机在下方扫码快速体验`uni-app`的强大功能。[官方文档](https://uniapp.dcloud.net.cn/)
|
||||
|
||||
## 快速上手
|
||||
hello-uniapp 示例工程可以通过两种方式创建, 一种是 HBuilderX, 配套 IDE,集成开发;另一种是 CLI 创建;推荐前者。
|
||||
### 通过 HBuilderX 可视化界面创建(推荐)
|
||||
|
||||
可视化的方式比较简单,HBuilderX内置相关环境,开箱即用,无需配置nodejs。
|
||||
|
||||
开始之前,开发者需先下载安装如下工具:
|
||||
|
||||
- HBuilderX:[官方IDE下载地址](https://www.dcloud.io/hbuilderx.html)
|
||||
|
||||
HBuilderX是通用的前端开发工具,但为`uni-app`做了特别强化,请下载App开发版。
|
||||
|
||||
由于截图在 github 不便浏览,参见官方文档 [HBuilderX 可视化界面创建](https://uniapp.dcloud.net.cn/quickstart?id=_1-%e9%80%9a%e8%bf%87-hbuilderx-%e5%8f%af%e8%a7%86%e5%8c%96%e7%95%8c%e9%9d%a2)
|
||||
|
||||
### 通过 vue-cli 创建
|
||||
|
||||
```
|
||||
npm install -g @vue/cli
|
||||
```
|
||||
|
||||
#### 创建uni-app
|
||||
|
||||
**使用正式版**(对应HBuilderX最新正式版)
|
||||
|
||||
```
|
||||
vue create -p dcloudio/uni-preset-vue my-project
|
||||
```
|
||||
|
||||
**使用alpha版**(对应HBuilderX最新alpha版)
|
||||
|
||||
```
|
||||
vue create -p dcloudio/uni-preset-vue#alpha my-alpha-project
|
||||
```
|
||||
|
||||
此时,会提示选择项目模板,选择 `hello uni-app` 项目模板,如下所示:
|
||||
|
||||
<div>
|
||||
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/h5-cli-01.png" width="300">
|
||||
</div>
|
||||
|
||||
创建好后,进入项目目录
|
||||
```
|
||||
cd my-project
|
||||
```
|
||||
|
||||
执行该命令运行到 h5 端
|
||||
```
|
||||
npm run dev:h5
|
||||
```
|
||||
|
||||
欢迎提 issues,推荐到[官方社区](https://ask.dcloud.net.cn/explore/)提问。
|
||||
|
||||
## 扫码体验
|
||||
|
||||
<div class="quick">
|
||||
<p>一套代码编到10个平台,这不是梦想。眼见为实,扫描10个二维码,亲自体验最全面的跨平台效果!</p>
|
||||
<div style="display: flex;">
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/ba7d0750-517d-11eb-bdc1-8bd33eb6adaa.png" width="160" />
|
||||
</div>
|
||||
<b>Android版</b>
|
||||
</a>
|
||||
<a href="https://itunes.apple.com/cn/app/hello-uni-app/id1417078253?mt=8" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/bb3ef7c0-517d-11eb-bdc1-8bd33eb6adaa.png" width="160" />
|
||||
</div>
|
||||
<b>iOS版</b>
|
||||
</a>
|
||||
<a href="https://hellouniapp.dcloud.net.cn/" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/bb3ef7c0-517d-11eb-bdc1-8bd33eb6adaa.png" width="160" />
|
||||
</div>
|
||||
<b>H5版</b>
|
||||
</a>
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box"><img src="//img.cdn.aliyun.dcloud.net.cn/guide/uniapp/gh_33446d7f7a26_430.jpg" width="160" /></div>
|
||||
<b>微信小程序版</b>
|
||||
</a>
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box"><img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/b131e0d0-517d-11eb-a16f-5b3e54966275.png" width="160" /></div>
|
||||
<b>支付宝小程序版</b>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex-img-group-view" style="margin-top: 20px;">
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box"><img src="https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/b204e840-517d-11eb-8ff1-d5dcf8779628.png" width="160" /></div>
|
||||
<b>百度小程序版</b>
|
||||
</a>
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/mp-toutiao.png" width="160" />
|
||||
</div>
|
||||
<b>字节跳动小程序版</b>
|
||||
</a>
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qq.png" width="160" />
|
||||
</div>
|
||||
<b>QQ小程序版</b>
|
||||
</a>
|
||||
<a href="//m3w.cn/uniapp" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-qa-union.png" width="160" />
|
||||
</div>
|
||||
<b>快应用</b>
|
||||
</a>
|
||||
<a href="https://so.mp.360.cn/mp.html?appid=qh4j181qqtru354st6" target="_blank" class="clear-style barcode-view">
|
||||
<div class="barcode-img-box">
|
||||
<img src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/hello-uni-mp-360-qr.png" width="160" />
|
||||
</div>
|
||||
<b>360小程序</b>
|
||||
</a>
|
||||
</div>
|
||||
<p>
|
||||
<em>注:某些平台不能提交简单demo,故补充了一些其他功能;hello uni-app示例代码可从[github](https://github.com/dcloudio/hello-uniapp)获取</em></br>
|
||||
<em>快应用仅支持 vivo 、oppo、华为</em></br>
|
||||
<em>360小程序仅 windows平台支持,需要在360浏览器中打开</em></br>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
`uni-app`官网文档详见[https://uniapp.dcloud.io](https://uniapp.dcloud.io)
|
||||
|
||||
更多uni-app的模板、示例详见[插件市场](https://ext.dcloud.net.cn/)
|
||||
|
||||
262
common/airport.js
Normal file
262
common/airport.js
Normal file
@ -0,0 +1,262 @@
|
||||
module.exports ={
|
||||
"list": [{
|
||||
"letter": "A",
|
||||
"data": [
|
||||
"阿克苏机场",
|
||||
"阿拉山口机场",
|
||||
"阿勒泰机场",
|
||||
"阿里昆莎机场",
|
||||
"安庆天柱山机场",
|
||||
"澳门国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "B",
|
||||
"data": [
|
||||
"保山机场",
|
||||
"包头机场",
|
||||
"北海福成机场",
|
||||
"北京南苑机场",
|
||||
"北京首都国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "C",
|
||||
"data": [
|
||||
"长白山机场",
|
||||
"长春龙嘉国际机场",
|
||||
"常德桃花源机场",
|
||||
"昌都邦达机场",
|
||||
"长沙黄花国际机场",
|
||||
"长治王村机场",
|
||||
"常州奔牛机场",
|
||||
"成都双流国际机场",
|
||||
"赤峰机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "D",
|
||||
"data": [
|
||||
"大理机场",
|
||||
"大连周水子国际机场",
|
||||
"大庆萨尔图机场",
|
||||
"大同东王庄机场",
|
||||
"达州河市机场",
|
||||
"丹东浪头机场",
|
||||
"德宏芒市机场",
|
||||
"迪庆香格里拉机场",
|
||||
"东营机场",
|
||||
"敦煌机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "E",
|
||||
"data": [
|
||||
"鄂尔多斯机场",
|
||||
"恩施许家坪机场",
|
||||
"二连浩特赛乌苏国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "F",
|
||||
"data": [
|
||||
"阜阳西关机场",
|
||||
"福州长乐国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "G",
|
||||
"data": [
|
||||
"赣州黄金机场",
|
||||
"格尔木机场",
|
||||
"固原六盘山机场",
|
||||
"广元盘龙机场",
|
||||
"广州白云国际机场",
|
||||
"桂林两江国际机场",
|
||||
"贵阳龙洞堡国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "H",
|
||||
"data": [
|
||||
"哈尔滨太平国际机场",
|
||||
"哈密机场",
|
||||
"海口美兰国际机场",
|
||||
"海拉尔东山国际机场",
|
||||
"邯郸机场",
|
||||
"汉中机场",
|
||||
"杭州萧山国际机场",
|
||||
"合肥骆岗国际机场",
|
||||
"和田机场",
|
||||
"黑河机场",
|
||||
"呼和浩特白塔国际机场",
|
||||
"淮安涟水机场",
|
||||
"黄山屯溪国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "I",
|
||||
"data": []
|
||||
}, {
|
||||
"letter": "J",
|
||||
"data": [
|
||||
"济南遥墙国际机场",
|
||||
"济宁曲阜机场",
|
||||
"鸡西兴凯湖机场",
|
||||
"佳木斯东郊机场",
|
||||
"嘉峪关机场",
|
||||
"锦州小岭子机场",
|
||||
"景德镇机场",
|
||||
"井冈山机场",
|
||||
"九江庐山机场",
|
||||
"九寨黄龙机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "K",
|
||||
"data": [
|
||||
"喀什机场",
|
||||
"克拉玛依机场",
|
||||
"库车龟兹机场",
|
||||
"库尔勒机场",
|
||||
"昆明巫家坝国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "L",
|
||||
"data": [
|
||||
"拉萨贡嘎机场",
|
||||
"兰州中川机场",
|
||||
"丽江三义机场",
|
||||
"黎平机场",
|
||||
"连云港白塔埠机场",
|
||||
"临沧机场",
|
||||
"临沂机场",
|
||||
"林芝米林机场",
|
||||
"柳州白莲机场",
|
||||
"龙岩冠豸山机场",
|
||||
"泸州蓝田机场",
|
||||
"洛阳北郊机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "M",
|
||||
"data": [
|
||||
"满洲里西郊机场",
|
||||
"绵阳南郊机场",
|
||||
"漠河古莲机场",
|
||||
"牡丹江海浪机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "N",
|
||||
"data": [
|
||||
"南昌昌北国际机场",
|
||||
"南充高坪机场",
|
||||
"南京禄口国际机场",
|
||||
"南宁吴圩机场",
|
||||
"南通兴东机场",
|
||||
"南阳姜营机场",
|
||||
"宁波栎社国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "O",
|
||||
"data": []
|
||||
}, {
|
||||
"letter": "P",
|
||||
"data": [
|
||||
"普洱思茅机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "Q",
|
||||
"data": [
|
||||
"齐齐哈尔三家子机场",
|
||||
"秦皇岛山海关机场",
|
||||
"青岛流亭国际机场",
|
||||
"衢州机场",
|
||||
"泉州晋江机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "R",
|
||||
"data": [
|
||||
"日喀则和平机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "S",
|
||||
"data": [
|
||||
"三亚凤凰国际机场",
|
||||
"汕头外砂机场",
|
||||
"上海虹桥国际机场",
|
||||
"上海浦东国际机场",
|
||||
"深圳宝安国际机场",
|
||||
"沈阳桃仙国际机场",
|
||||
"石家庄正定国际机场",
|
||||
"苏南硕放国际机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "T",
|
||||
"data": [
|
||||
"塔城机场",
|
||||
"太原武宿国际机场",
|
||||
"台州路桥机场 (黄岩机场)",
|
||||
"唐山三女河机场",
|
||||
"腾冲驼峰机场",
|
||||
"天津滨海国际机场",
|
||||
"通辽机场",
|
||||
"铜仁凤凰机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "U",
|
||||
"data": []
|
||||
}, {
|
||||
"letter": "V",
|
||||
"data": []
|
||||
}, {
|
||||
"letter": "W",
|
||||
"data": [
|
||||
"万州五桥机场",
|
||||
"潍坊机场",
|
||||
"威海大水泊机场",
|
||||
"文山普者黑机场",
|
||||
"温州永强国际机场",
|
||||
"乌海机场",
|
||||
"武汉天河国际机场",
|
||||
"乌兰浩特机场",
|
||||
"乌鲁木齐地窝堡国际机场",
|
||||
"武夷山机场",
|
||||
"梧州长洲岛机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "X",
|
||||
"data": [
|
||||
"西安咸阳国际机场",
|
||||
"西昌青山机场",
|
||||
"锡林浩特机场",
|
||||
"西宁曹家堡机场",
|
||||
"西双版纳嘎洒机场",
|
||||
"厦门高崎国际机场",
|
||||
"香港国际机场",
|
||||
"襄阳刘集机场",
|
||||
"兴义机场",
|
||||
"徐州观音机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "Y",
|
||||
"data": [
|
||||
"延安二十里堡机场",
|
||||
"盐城机场",
|
||||
"延吉朝阳川机场",
|
||||
"烟台莱山国际机场",
|
||||
"宜宾菜坝机场",
|
||||
"宜昌三峡机场",
|
||||
"伊春林都机场",
|
||||
"伊宁机场",
|
||||
"义乌机场",
|
||||
"银川河东机场",
|
||||
"永州零陵机场",
|
||||
"榆林榆阳机场",
|
||||
"玉树巴塘机场",
|
||||
"运城张孝机场"
|
||||
]
|
||||
}, {
|
||||
"letter": "Z",
|
||||
"data": [
|
||||
"湛江机场",
|
||||
"昭通机场",
|
||||
"郑州新郑国际机场",
|
||||
"芷江机场",
|
||||
"重庆江北国际机场",
|
||||
"中卫香山机场",
|
||||
"舟山朱家尖机场",
|
||||
"珠海三灶机场"
|
||||
]
|
||||
}]
|
||||
}
|
||||
97
common/graceChecker.js
Executable file
97
common/graceChecker.js
Executable file
@ -0,0 +1,97 @@
|
||||
/**
|
||||
数据验证(表单验证)
|
||||
来自 grace.hcoder.net
|
||||
作者 hcoder 深海
|
||||
*/
|
||||
module.exports = {
|
||||
error:'',
|
||||
check : function (data, rule){
|
||||
for(var i = 0; i < rule.length; i++){
|
||||
if (!rule[i].checkType){return true;}
|
||||
if (!rule[i].name) {return true;}
|
||||
if (!rule[i].errorMsg) {return true;}
|
||||
if (!data[rule[i].name]) {this.error = rule[i].errorMsg; return false;}
|
||||
switch (rule[i].checkType){
|
||||
case 'string':
|
||||
var reg = new RegExp('^.{' + rule[i].checkRule + '}$');
|
||||
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
|
||||
break;
|
||||
case 'int':
|
||||
var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$');
|
||||
if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
|
||||
break;
|
||||
break;
|
||||
case 'between':
|
||||
if (!this.isNumber(data[rule[i].name])){
|
||||
this.error = rule[i].errorMsg;
|
||||
return false;
|
||||
}
|
||||
var minMax = rule[i].checkRule.split(',');
|
||||
minMax[0] = Number(minMax[0]);
|
||||
minMax[1] = Number(minMax[1]);
|
||||
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
|
||||
this.error = rule[i].errorMsg;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'betweenD':
|
||||
var reg = /^-?[1-9][0-9]?$/;
|
||||
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
|
||||
var minMax = rule[i].checkRule.split(',');
|
||||
minMax[0] = Number(minMax[0]);
|
||||
minMax[1] = Number(minMax[1]);
|
||||
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
|
||||
this.error = rule[i].errorMsg;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'betweenF':
|
||||
var reg = /^-?[0-9][0-9]?.+[0-9]+$/;
|
||||
if (!reg.test(data[rule[i].name])){this.error = rule[i].errorMsg; return false;}
|
||||
var minMax = rule[i].checkRule.split(',');
|
||||
minMax[0] = Number(minMax[0]);
|
||||
minMax[1] = Number(minMax[1]);
|
||||
if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
|
||||
this.error = rule[i].errorMsg;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'same':
|
||||
if (data[rule[i].name] != rule[i].checkRule) { this.error = rule[i].errorMsg; return false;}
|
||||
break;
|
||||
case 'notsame':
|
||||
if (data[rule[i].name] == rule[i].checkRule) { this.error = rule[i].errorMsg; return false; }
|
||||
break;
|
||||
case 'email':
|
||||
var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
|
||||
break;
|
||||
case 'phoneno':
|
||||
var reg = /^1[0-9]{10,10}$/;
|
||||
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
|
||||
break;
|
||||
case 'zipcode':
|
||||
var reg = /^[0-9]{6}$/;
|
||||
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
|
||||
break;
|
||||
case 'reg':
|
||||
var reg = new RegExp(rule[i].checkRule);
|
||||
if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
|
||||
break;
|
||||
case 'in':
|
||||
if(rule[i].checkRule.indexOf(data[rule[i].name]) == -1){
|
||||
this.error = rule[i].errorMsg; return false;
|
||||
}
|
||||
break;
|
||||
case 'notnull':
|
||||
if(data[rule[i].name] == null || data[rule[i].name].length < 1){this.error = rule[i].errorMsg; return false;}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
isNumber : function (checkVal){
|
||||
var reg = /^-?[1-9][0-9]?.?[0-9]*$/;
|
||||
return reg.test(checkVal);
|
||||
}
|
||||
}
|
||||
352
common/html-parser.js
Normal file
352
common/html-parser.js
Normal file
@ -0,0 +1,352 @@
|
||||
/*
|
||||
* HTML5 Parser By Sam Blowes
|
||||
*
|
||||
* Designed for HTML5 documents
|
||||
*
|
||||
* Original code by John Resig (ejohn.org)
|
||||
* http://ejohn.org/blog/pure-javascript-html-parser/
|
||||
* Original code by Erik Arvidsson, Mozilla Public License
|
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* License
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* This code is triple licensed using Apache Software License 2.0,
|
||||
* Mozilla Public License or GNU Public License
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License
|
||||
* Version 1.1 (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.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS"
|
||||
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing rights and limitations
|
||||
* under the License.
|
||||
*
|
||||
* The Original Code is Simple HTML Parser.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Erik Arvidsson.
|
||||
* Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
|
||||
* Reserved.
|
||||
*
|
||||
* ////////////////////////////////////////////////////////////////////////////
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* Usage
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* // Use like so:
|
||||
* HTMLParser(htmlString, {
|
||||
* start: function(tag, attrs, unary) {},
|
||||
* end: function(tag) {},
|
||||
* chars: function(text) {},
|
||||
* comment: function(text) {}
|
||||
* });
|
||||
*
|
||||
* // or to get an XML string:
|
||||
* HTMLtoXML(htmlString);
|
||||
*
|
||||
* // or to get an XML DOM Document
|
||||
* HTMLtoDOM(htmlString);
|
||||
*
|
||||
* // or to inject into an existing document/DOM node
|
||||
* HTMLtoDOM(htmlString, document);
|
||||
* HTMLtoDOM(htmlString, document.body);
|
||||
*
|
||||
*/
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
|
||||
var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
|
||||
var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
|
||||
|
||||
var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
|
||||
// fixed by xxx 将 ins 标签从块级名单中移除
|
||||
|
||||
var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
|
||||
|
||||
var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
|
||||
// (and which close themselves)
|
||||
|
||||
var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
|
||||
|
||||
var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
|
||||
|
||||
var special = makeMap('script,style');
|
||||
function HTMLParser(html, handler) {
|
||||
var index;
|
||||
var chars;
|
||||
var match;
|
||||
var stack = [];
|
||||
var last = html;
|
||||
|
||||
stack.last = function () {
|
||||
return this[this.length - 1];
|
||||
};
|
||||
|
||||
while (html) {
|
||||
chars = true; // Make sure we're not in a script or style element
|
||||
|
||||
if (!stack.last() || !special[stack.last()]) {
|
||||
// Comment
|
||||
if (html.indexOf('<!--') == 0) {
|
||||
index = html.indexOf('-->');
|
||||
|
||||
if (index >= 0) {
|
||||
if (handler.comment) {
|
||||
handler.comment(html.substring(4, index));
|
||||
}
|
||||
|
||||
html = html.substring(index + 3);
|
||||
chars = false;
|
||||
} // end tag
|
||||
|
||||
} else if (html.indexOf('</') == 0) {
|
||||
match = html.match(endTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(endTag, parseEndTag);
|
||||
chars = false;
|
||||
} // start tag
|
||||
|
||||
} else if (html.indexOf('<') == 0) {
|
||||
match = html.match(startTag);
|
||||
|
||||
if (match) {
|
||||
html = html.substring(match[0].length);
|
||||
match[0].replace(startTag, parseStartTag);
|
||||
chars = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (chars) {
|
||||
index = html.indexOf('<');
|
||||
var text = index < 0 ? html : html.substring(0, index);
|
||||
html = index < 0 ? '' : html.substring(index);
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
|
||||
text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
|
||||
|
||||
if (handler.chars) {
|
||||
handler.chars(text);
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
|
||||
if (html == last) {
|
||||
throw 'Parse Error: ' + html;
|
||||
}
|
||||
|
||||
last = html;
|
||||
} // Clean up any remaining tags
|
||||
|
||||
|
||||
parseEndTag();
|
||||
|
||||
function parseStartTag(tag, tagName, rest, unary) {
|
||||
tagName = tagName.toLowerCase();
|
||||
|
||||
if (block[tagName]) {
|
||||
while (stack.last() && inline[stack.last()]) {
|
||||
parseEndTag('', stack.last());
|
||||
}
|
||||
}
|
||||
|
||||
if (closeSelf[tagName] && stack.last() == tagName) {
|
||||
parseEndTag('', tagName);
|
||||
}
|
||||
|
||||
unary = empty[tagName] || !!unary;
|
||||
|
||||
if (!unary) {
|
||||
stack.push(tagName);
|
||||
}
|
||||
|
||||
if (handler.start) {
|
||||
var attrs = [];
|
||||
rest.replace(attr, function (match, name) {
|
||||
var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
|
||||
attrs.push({
|
||||
name: name,
|
||||
value: value,
|
||||
escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
if (handler.start) {
|
||||
handler.start(tagName, attrs, unary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseEndTag(tag, tagName) {
|
||||
// If no tag name is provided, clean shop
|
||||
if (!tagName) {
|
||||
var pos = 0;
|
||||
} // Find the closest opened tag of the same type
|
||||
else {
|
||||
for (var pos = stack.length - 1; pos >= 0; pos--) {
|
||||
if (stack[pos] == tagName) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos >= 0) {
|
||||
// Close all the open elements, up the stack
|
||||
for (var i = stack.length - 1; i >= pos; i--) {
|
||||
if (handler.end) {
|
||||
handler.end(stack[i]);
|
||||
}
|
||||
} // Remove the open elements from the stack
|
||||
|
||||
|
||||
stack.length = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function makeMap(str) {
|
||||
var obj = {};
|
||||
var items = str.split(',');
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
obj[items[i]] = true;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function removeDOCTYPE(html) {
|
||||
return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
|
||||
}
|
||||
|
||||
function parseAttrs(attrs) {
|
||||
return attrs.reduce(function (pre, attr) {
|
||||
var value = attr.value;
|
||||
var name = attr.name;
|
||||
|
||||
if (pre[name]) {
|
||||
pre[name] = pre[name] + " " + value;
|
||||
} else {
|
||||
pre[name] = value;
|
||||
}
|
||||
|
||||
return pre;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function parseHtml(html) {
|
||||
html = removeDOCTYPE(html);
|
||||
var stacks = [];
|
||||
var results = {
|
||||
node: 'root',
|
||||
children: []
|
||||
};
|
||||
HTMLParser(html, {
|
||||
start: function start(tag, attrs, unary) {
|
||||
var node = {
|
||||
name: tag
|
||||
};
|
||||
|
||||
if (attrs.length !== 0) {
|
||||
node.attrs = parseAttrs(attrs);
|
||||
}
|
||||
|
||||
if (unary) {
|
||||
var parent = stacks[0] || results;
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
} else {
|
||||
stacks.unshift(node);
|
||||
}
|
||||
},
|
||||
end: function end(tag) {
|
||||
var node = stacks.shift();
|
||||
if (node.name !== tag) console.error('invalid state: mismatch end tag');
|
||||
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
},
|
||||
chars: function chars(text) {
|
||||
var node = {
|
||||
type: 'text',
|
||||
text: text
|
||||
};
|
||||
|
||||
if (stacks.length === 0) {
|
||||
results.children.push(node);
|
||||
} else {
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
},
|
||||
comment: function comment(text) {
|
||||
var node = {
|
||||
node: 'comment',
|
||||
text: text
|
||||
};
|
||||
var parent = stacks[0];
|
||||
|
||||
if (!parent.children) {
|
||||
parent.children = [];
|
||||
}
|
||||
|
||||
parent.children.push(node);
|
||||
}
|
||||
});
|
||||
return results.children;
|
||||
}
|
||||
|
||||
export default parseHtml;
|
||||
245
common/permission.js
Normal file
245
common/permission.js
Normal file
@ -0,0 +1,245 @@
|
||||
/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启
|
||||
|
||||
var isIOS
|
||||
|
||||
function album() {
|
||||
var result = 0;
|
||||
var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
|
||||
var authStatus = PHPhotoLibrary.authorizationStatus();
|
||||
if (authStatus === 0) {
|
||||
result = null;
|
||||
} else if (authStatus == 3) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
plus.ios.deleteObject(PHPhotoLibrary);
|
||||
return result;
|
||||
}
|
||||
|
||||
function camera() {
|
||||
var result = 0;
|
||||
var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
|
||||
var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
|
||||
if (authStatus === 0) {
|
||||
result = null;
|
||||
} else if (authStatus == 3) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
plus.ios.deleteObject(AVCaptureDevice);
|
||||
return result;
|
||||
}
|
||||
|
||||
function location() {
|
||||
var result = 0;
|
||||
var cllocationManger = plus.ios.import("CLLocationManager");
|
||||
var enable = cllocationManger.locationServicesEnabled();
|
||||
var status = cllocationManger.authorizationStatus();
|
||||
if (!enable) {
|
||||
result = 2;
|
||||
} else if (status === 0) {
|
||||
result = null;
|
||||
} else if (status === 3 || status === 4) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
plus.ios.deleteObject(cllocationManger);
|
||||
return result;
|
||||
}
|
||||
|
||||
function push() {
|
||||
var result = 0;
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var app = UIApplication.sharedApplication();
|
||||
var enabledTypes = 0;
|
||||
if (app.currentUserNotificationSettings) {
|
||||
var settings = app.currentUserNotificationSettings();
|
||||
enabledTypes = settings.plusGetAttribute("types");
|
||||
if (enabledTypes == 0) {
|
||||
result = 0;
|
||||
console.log("推送权限没有开启");
|
||||
} else {
|
||||
result = 1;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
plus.ios.deleteObject(settings);
|
||||
} else {
|
||||
enabledTypes = app.enabledRemoteNotificationTypes();
|
||||
if (enabledTypes == 0) {
|
||||
result = 3;
|
||||
console.log("推送权限没有开启!");
|
||||
} else {
|
||||
result = 4;
|
||||
console.log("已经开启推送功能!")
|
||||
}
|
||||
}
|
||||
plus.ios.deleteObject(app);
|
||||
plus.ios.deleteObject(UIApplication);
|
||||
return result;
|
||||
}
|
||||
|
||||
function contact() {
|
||||
var result = 0;
|
||||
var CNContactStore = plus.ios.import("CNContactStore");
|
||||
var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
|
||||
if (cnAuthStatus === 0) {
|
||||
result = null;
|
||||
} else if (cnAuthStatus == 3) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
plus.ios.deleteObject(CNContactStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
function record() {
|
||||
var result = null;
|
||||
var avaudiosession = plus.ios.import("AVAudioSession");
|
||||
var avaudio = avaudiosession.sharedInstance();
|
||||
var status = avaudio.recordPermission();
|
||||
console.log("permissionStatus:" + status);
|
||||
if (status === 1970168948) {
|
||||
result = null;
|
||||
} else if (status === 1735552628) {
|
||||
result = 1;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
plus.ios.deleteObject(avaudiosession);
|
||||
return result;
|
||||
}
|
||||
|
||||
function calendar() {
|
||||
var result = null;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = 1;
|
||||
console.log("日历权限已经开启");
|
||||
} else {
|
||||
console.log("日历权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
function memo() {
|
||||
var result = null;
|
||||
var EKEventStore = plus.ios.import("EKEventStore");
|
||||
var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
|
||||
if (ekAuthStatus == 3) {
|
||||
result = 1;
|
||||
console.log("备忘录权限已经开启");
|
||||
} else {
|
||||
console.log("备忘录权限没有开启");
|
||||
}
|
||||
plus.ios.deleteObject(EKEventStore);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function requestIOS(permissionID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
switch (permissionID) {
|
||||
case "push":
|
||||
resolve(push());
|
||||
break;
|
||||
case "location":
|
||||
resolve(location());
|
||||
break;
|
||||
case "record":
|
||||
resolve(record());
|
||||
break;
|
||||
case "camera":
|
||||
resolve(camera());
|
||||
break;
|
||||
case "album":
|
||||
resolve(album());
|
||||
break;
|
||||
case "contact":
|
||||
resolve(contact());
|
||||
break;
|
||||
case "calendar":
|
||||
resolve(calendar());
|
||||
break;
|
||||
case "memo":
|
||||
resolve(memo());
|
||||
break;
|
||||
default:
|
||||
resolve(0);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function requestAndroid(permissionID) {
|
||||
return new Promise((resolve, reject) => {
|
||||
plus.android.requestPermissions(
|
||||
[permissionID],
|
||||
function(resultObj) {
|
||||
var result = 0;
|
||||
for (var i = 0; i < resultObj.granted.length; i++) {
|
||||
var grantedPermission = resultObj.granted[i];
|
||||
console.log('已获取的权限:' + grantedPermission);
|
||||
result = 1
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedPresent.length; i++) {
|
||||
var deniedPresentPermission = resultObj.deniedPresent[i];
|
||||
console.log('拒绝本次申请的权限:' + deniedPresentPermission);
|
||||
result = 0
|
||||
}
|
||||
for (var i = 0; i < resultObj.deniedAlways.length; i++) {
|
||||
var deniedAlwaysPermission = resultObj.deniedAlways[i];
|
||||
console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
|
||||
result = -1
|
||||
}
|
||||
resolve(result);
|
||||
},
|
||||
function(error) {
|
||||
console.log('result error: ' + error.message)
|
||||
resolve({
|
||||
code: error.code,
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function gotoAppPermissionSetting() {
|
||||
if (permission.isIOS) {
|
||||
var UIApplication = plus.ios.import("UIApplication");
|
||||
var application2 = UIApplication.sharedApplication();
|
||||
var NSURL2 = plus.ios.import("NSURL");
|
||||
var setting2 = NSURL2.URLWithString("app-settings:");
|
||||
application2.openURL(setting2);
|
||||
plus.ios.deleteObject(setting2);
|
||||
plus.ios.deleteObject(NSURL2);
|
||||
plus.ios.deleteObject(application2);
|
||||
} else {
|
||||
var Intent = plus.android.importClass("android.content.Intent");
|
||||
var Settings = plus.android.importClass("android.provider.Settings");
|
||||
var Uri = plus.android.importClass("android.net.Uri");
|
||||
var mainActivity = plus.android.runtimeMainActivity();
|
||||
var intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
|
||||
intent.setData(uri);
|
||||
mainActivity.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
const permission = {
|
||||
get isIOS(){
|
||||
return typeof isIOS === 'boolean' ? isIOS : (isIOS = uni.getSystemInfoSync().platform === 'ios')
|
||||
},
|
||||
requestIOS: requestIOS,
|
||||
requestAndroid: requestAndroid,
|
||||
gotoAppSetting: gotoAppPermissionSetting
|
||||
}
|
||||
|
||||
module.exports = permission
|
||||
136
common/uni-nvue.css
Normal file
136
common/uni-nvue.css
Normal file
@ -0,0 +1,136 @@
|
||||
/* #ifndef APP-PLUS-NVUE */
|
||||
page {
|
||||
min-height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
/* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */
|
||||
/* #ifdef MP-TOUTIAO */
|
||||
/* text :not(view) {
|
||||
font-family: uniicons;
|
||||
} */
|
||||
/* #endif */
|
||||
|
||||
.uni-icon {
|
||||
font-family: uniicons;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.uni-container {
|
||||
padding: 15px;
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.uni-header-logo {
|
||||
/* #ifdef H5 */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding: 15px 15px;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.uni-header-image {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.uni-hello-text {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.hello-text {
|
||||
color: #7A7E83;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.hello-link {
|
||||
color: #7A7E83;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.uni-panel {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.uni-panel-h {
|
||||
/* #ifdef H5 */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
background-color: #ffffff;
|
||||
flex-direction: row !important;
|
||||
/* justify-content: space-between !important; */
|
||||
align-items: center !important;
|
||||
padding: 12px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
/*
|
||||
.uni-panel-h:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
*/
|
||||
.uni-panel-h-on {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.uni-panel-text {
|
||||
flex: 1;
|
||||
color: #000000;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.uni-panel-icon {
|
||||
margin-left: 15px;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
transform: rotate(0deg);
|
||||
transition-duration: 0s;
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.uni-panel-icon-on {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.uni-navigate-item {
|
||||
/* #ifdef H5 */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
background-color: #FFFFFF;
|
||||
border-top-style: solid;
|
||||
border-top-color: #f0f0f0;
|
||||
border-top-width: 1px;
|
||||
padding: 12px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-navigate-item:active {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
.uni-navigate-text {
|
||||
flex: 1;
|
||||
color: #000000;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.uni-navigate-icon {
|
||||
margin-left: 15px;
|
||||
color: #999999;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
1463
common/uni.css
Normal file
1463
common/uni.css
Normal file
File diff suppressed because it is too large
Load Diff
73
common/util.js
Normal file
73
common/util.js
Normal file
@ -0,0 +1,73 @@
|
||||
function formatTime(time) {
|
||||
if (typeof time !== 'number' || time < 0) {
|
||||
return time
|
||||
}
|
||||
|
||||
var hour = parseInt(time / 3600)
|
||||
time = time % 3600
|
||||
var minute = parseInt(time / 60)
|
||||
time = time % 60
|
||||
var second = time
|
||||
|
||||
return ([hour, minute, second]).map(function (n) {
|
||||
n = n.toString()
|
||||
return n[1] ? n : '0' + n
|
||||
}).join(':')
|
||||
}
|
||||
|
||||
function formatLocation(longitude, latitude) {
|
||||
if (typeof longitude === 'string' && typeof latitude === 'string') {
|
||||
longitude = parseFloat(longitude)
|
||||
latitude = parseFloat(latitude)
|
||||
}
|
||||
|
||||
longitude = longitude.toFixed(2)
|
||||
latitude = latitude.toFixed(2)
|
||||
|
||||
return {
|
||||
longitude: longitude.toString().split('.'),
|
||||
latitude: latitude.toString().split('.')
|
||||
}
|
||||
}
|
||||
var dateUtils = {
|
||||
UNITS: {
|
||||
'年': 31557600000,
|
||||
'月': 2629800000,
|
||||
'天': 86400000,
|
||||
'小时': 3600000,
|
||||
'分钟': 60000,
|
||||
'秒': 1000
|
||||
},
|
||||
humanize: function (milliseconds) {
|
||||
var humanize = '';
|
||||
for (var key in this.UNITS) {
|
||||
if (milliseconds >= this.UNITS[key]) {
|
||||
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
|
||||
break;
|
||||
}
|
||||
}
|
||||
return humanize || '刚刚';
|
||||
},
|
||||
format: function (dateStr) {
|
||||
var date = this.parse(dateStr)
|
||||
var diff = Date.now() - date.getTime();
|
||||
if (diff < this.UNITS['天']) {
|
||||
return this.humanize(diff);
|
||||
}
|
||||
var _format = function (number) {
|
||||
return (number < 10 ? ('0' + number) : number);
|
||||
};
|
||||
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
|
||||
_format(date.getHours()) + ':' + _format(date.getMinutes());
|
||||
},
|
||||
parse: function (str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串,转化为一个Date对象
|
||||
var a = str.split(/[^0-9]/);
|
||||
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
formatTime: formatTime,
|
||||
formatLocation: formatLocation,
|
||||
dateUtils: dateUtils
|
||||
}
|
||||
181
components/amap-wx/js/util.js
Normal file
181
components/amap-wx/js/util.js
Normal file
@ -0,0 +1,181 @@
|
||||
import amap from '@/components/amap-wx/lib/amap-wx.js';
|
||||
// 地铁颜色图
|
||||
const line = {
|
||||
'1号线': '#C43B33',
|
||||
'2号线': '#016299',
|
||||
'4号线/大兴线': '#008E9C',
|
||||
'5号线': '#A42380',
|
||||
'6号线': '#D09900',
|
||||
'7号线': '#F2C172',
|
||||
'8号线': '#009D6A',
|
||||
'9号线': '#8FC41E',
|
||||
'10号线': '#009DBE',
|
||||
'13号线': '#F9E701',
|
||||
'14号线东段': '#D4A7A2',
|
||||
'14号线西段': '#D4A7A2',
|
||||
'15号线': '#5D2D69',
|
||||
'八通线': '#C33A32',
|
||||
'昌平线': '#DE82B1',
|
||||
'亦庄线': '#E40177',
|
||||
'房山线': '#E66021',
|
||||
'机场线': '#A29BBC',
|
||||
}
|
||||
|
||||
// 150500:地铁站 ,150700:公交站 , 190700:地名地址
|
||||
const typecode = [{
|
||||
id: '150500',
|
||||
icon: 'icon-ditie'
|
||||
}, {
|
||||
id: '150700',
|
||||
icon: 'icon-gongjiao'
|
||||
}, {
|
||||
id: '190700',
|
||||
icon: 'icon-gonglu'
|
||||
}];
|
||||
|
||||
const util = {
|
||||
key:'b526b09b86cd2996e7732be8ab8c4430',
|
||||
/**
|
||||
* 初始化高德地图api
|
||||
*/
|
||||
mapInit() {
|
||||
return new amap.AMapWX({
|
||||
key: this.key
|
||||
});
|
||||
},
|
||||
// 服务状态吗
|
||||
typecode,
|
||||
/**
|
||||
* 获取地图颜色
|
||||
*/
|
||||
lineColor(name) {
|
||||
if (line[name]) {
|
||||
return line[name];
|
||||
} else {
|
||||
return '#ccc';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 关键字颜色变化
|
||||
*/
|
||||
serachNmme(val, name) {
|
||||
let namestr = new RegExp(val);
|
||||
let nameresult =
|
||||
`<div style="font-size: 14px;color: #333;line-height: 1.5;">
|
||||
${name.replace(namestr, "<span style='color:#66ccff;'>" + val + '</span>')}
|
||||
</div>`
|
||||
.trim();
|
||||
|
||||
return nameresult;
|
||||
},
|
||||
/**
|
||||
* 地址转地铁线路
|
||||
*/
|
||||
addressToLine(address, type) {
|
||||
let addr = address.split(';');
|
||||
let dt = '';
|
||||
addr.forEach(elm => {
|
||||
let color = '#cccccc';
|
||||
if (type === typecode[0].id) {
|
||||
color = this.lineColor(elm)
|
||||
} else if (type === typecode[1].id) {
|
||||
color = '#4075cb'
|
||||
}
|
||||
let style = 'margin:5px 0;margin-right:5px;padding:0 5px;background:' + color +
|
||||
';font-size:12px;color:#fff;border-radius:3px;';
|
||||
dt += `<div style=\'${style}\'>${elm}</div>`;
|
||||
|
||||
});
|
||||
return `<div style="display:flex;flex-wrap: wrap;">${dt}</div>`;
|
||||
},
|
||||
/**
|
||||
* 数据处理
|
||||
*/
|
||||
dataHandle(item, val) {
|
||||
// 改变字体颜色
|
||||
if (val) {
|
||||
item.nameNodes = util.serachNmme(val, item.name);
|
||||
} else {
|
||||
item.nameNodes = `<div style="font-size: 14px;color: #333;line-height: 1.5;">${item.name}</div>`;
|
||||
|
||||
}
|
||||
// 地址解析 地铁
|
||||
if (
|
||||
item.typecode === util.typecode[0].id ||
|
||||
item.typecode === util.typecode[1].id
|
||||
) {
|
||||
item.addressNodes = util.addressToLine(item.address, item.typecode);
|
||||
if (item.typecode === util.typecode[0].id) {
|
||||
item.icon = util.typecode[0].icon;
|
||||
} else if (item.typecode === util.typecode[1].id) {
|
||||
item.icon = util.typecode[1].icon;
|
||||
}
|
||||
} else {
|
||||
item.addressNodes = `<span>${item.district}${
|
||||
item.address.length > 0 ? '·' + item.address : ''
|
||||
}</span>`.trim();
|
||||
item.icon = 'icon-weizhi';
|
||||
}
|
||||
|
||||
if (item.location && item.location.length === 0) {
|
||||
item.icon = 'icon-sousuo';
|
||||
}
|
||||
|
||||
return item;
|
||||
},
|
||||
/**
|
||||
* 存储历史数据
|
||||
* val [string | object]需要存储的内容
|
||||
*/
|
||||
setHistory(val) {
|
||||
let searchHistory = uni.getStorageSync('search:history');
|
||||
if (!searchHistory) searchHistory = [];
|
||||
let serachData = {};
|
||||
if (typeof(val) === 'string') {
|
||||
serachData = {
|
||||
adcode: [],
|
||||
address: [],
|
||||
city: [],
|
||||
district: [],
|
||||
id: [],
|
||||
location: [],
|
||||
name: val,
|
||||
typecode: []
|
||||
};
|
||||
} else {
|
||||
serachData = val
|
||||
}
|
||||
|
||||
// 判断数组是否存在,如果存在,那么将放到最前面
|
||||
for (var i = 0; i < searchHistory.length; i++) {
|
||||
if (searchHistory[i].name === serachData.name) {
|
||||
searchHistory.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
searchHistory.unshift(util.dataHandle(serachData));
|
||||
uni.setStorage({
|
||||
key: 'search:history',
|
||||
data: searchHistory,
|
||||
success: function() {
|
||||
// console.log('success');
|
||||
}
|
||||
});
|
||||
},
|
||||
getHistory() {
|
||||
|
||||
},
|
||||
removeHistory() {
|
||||
uni.removeStorage({
|
||||
key: 'search:history',
|
||||
success: function(res) {
|
||||
console.log('success');
|
||||
}
|
||||
});
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default util;
|
||||
1
components/amap-wx/lib/amap-wx.js
Normal file
1
components/amap-wx/lib/amap-wx.js
Normal file
File diff suppressed because one or more lines are too long
156
components/api-set-tabbar.nvue
Normal file
156
components/api-set-tabbar.nvue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<view class="uni-padding-wrap">
|
||||
<page-head :title="title"></page-head>
|
||||
<button class="button" @click="setTabBarBadge">{{ !hasSetTabBarBadge ? '设置tab徽标' : '移除tab徽标' }}</button>
|
||||
<button class="button" @click="showTabBarRedDot">{{ !hasShownTabBarRedDot ? '显示红点' : '移除红点'}}</button>
|
||||
<button class="button" @click="customStyle">{{ !hasCustomedStyle ? '自定义Tab样式' : '移除自定义样式'}}</button>
|
||||
<button class="button" @click="customItem">{{ !hasCustomedItem ? '自定义Tab信息' : '移除自定义信息' }}</button>
|
||||
<button class="button" @click="hideTabBar">{{ !hasHiddenTabBar ? '隐藏TabBar' : '显示TabBar' }}</button>
|
||||
<view class="btn-area">
|
||||
<button class="button" type="primary" @click="navigateBack">返回上一级</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
title: 'tababr',
|
||||
hasSetTabBarBadge: false,
|
||||
hasShownTabBarRedDot: false,
|
||||
hasCustomedStyle: false,
|
||||
hasCustomedItem: false,
|
||||
hasHiddenTabBar: false
|
||||
}
|
||||
},
|
||||
destroyed() {
|
||||
if (this.hasSetTabBarBadge) {
|
||||
uni.removeTabBarBadge({
|
||||
index: 1
|
||||
})
|
||||
}
|
||||
if (this.hasShownTabBarRedDot) {
|
||||
uni.hideTabBarRedDot({
|
||||
index: 1
|
||||
})
|
||||
}
|
||||
if (this.hasHiddenTabBar) {
|
||||
uni.showTabBar()
|
||||
}
|
||||
if (this.hasCustomedStyle) {
|
||||
uni.setTabBarStyle({
|
||||
color: '#7A7E83',
|
||||
selectedColor: '#007AFF',
|
||||
backgroundColor: '#F8F8F8',
|
||||
borderStyle: 'black'
|
||||
})
|
||||
}
|
||||
|
||||
if (this.hasCustomedItem) {
|
||||
let tabBarOptions = {
|
||||
index: 1,
|
||||
text: '接口',
|
||||
iconPath: '/static/api.png',
|
||||
selectedIconPath: '/static/apiHL.png'
|
||||
}
|
||||
uni.setTabBarItem(tabBarOptions)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
navigateBack() {
|
||||
this.$emit('unmount')
|
||||
},
|
||||
setTabBarBadge() {
|
||||
if(this.hasShownTabBarRedDot){
|
||||
uni.hideTabBarRedDot({
|
||||
index: 1
|
||||
})
|
||||
this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
|
||||
}
|
||||
if (!this.hasSetTabBarBadge) {
|
||||
uni.setTabBarBadge({
|
||||
index: 1,
|
||||
text: '1'
|
||||
})
|
||||
} else {
|
||||
uni.removeTabBarBadge({
|
||||
index: 1
|
||||
})
|
||||
}
|
||||
this.hasSetTabBarBadge = !this.hasSetTabBarBadge
|
||||
},
|
||||
showTabBarRedDot() {
|
||||
if(this.hasSetTabBarBadge) {
|
||||
uni.removeTabBarBadge({
|
||||
index: 1
|
||||
})
|
||||
this.hasSetTabBarBadge = !this.hasSetTabBarBadge
|
||||
}
|
||||
if (!this.hasShownTabBarRedDot) {
|
||||
uni.showTabBarRedDot({
|
||||
index: 1
|
||||
})
|
||||
} else {
|
||||
uni.hideTabBarRedDot({
|
||||
index: 1
|
||||
})
|
||||
}
|
||||
this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
|
||||
},
|
||||
hideTabBar() {
|
||||
if (!this.hasHiddenTabBar) {
|
||||
uni.hideTabBar()
|
||||
} else {
|
||||
uni.showTabBar()
|
||||
}
|
||||
this.hasHiddenTabBar = !this.hasHiddenTabBar
|
||||
},
|
||||
customStyle() {
|
||||
if (this.hasCustomedStyle) {
|
||||
uni.setTabBarStyle({
|
||||
color: '#7A7E83',
|
||||
selectedColor: '#007AFF',
|
||||
backgroundColor: '#F8F8F8',
|
||||
borderStyle: 'black'
|
||||
})
|
||||
} else {
|
||||
uni.setTabBarStyle({
|
||||
color: '#FFF',
|
||||
selectedColor: '#007AFF',
|
||||
backgroundColor: '#000000',
|
||||
borderStyle: 'black'
|
||||
})
|
||||
}
|
||||
this.hasCustomedStyle = !this.hasCustomedStyle
|
||||
},
|
||||
customItem() {
|
||||
let tabBarOptions = {
|
||||
index: 1,
|
||||
text: '接口',
|
||||
iconPath: '/static/api.png',
|
||||
selectedIconPath: '/static/apiHL.png'
|
||||
}
|
||||
if (this.hasCustomedItem) {
|
||||
uni.setTabBarItem(tabBarOptions)
|
||||
} else {
|
||||
tabBarOptions.text = 'API'
|
||||
uni.setTabBarItem(tabBarOptions)
|
||||
}
|
||||
this.hasCustomedItem = !this.hasCustomedItem
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.button {
|
||||
margin-top: 30rpx;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.btn-area {
|
||||
padding-top: 30rpx;
|
||||
}
|
||||
</style>
|
||||
1
components/marked/index.js
Normal file
1
components/marked/index.js
Normal file
@ -0,0 +1 @@
|
||||
module.exports = require('./lib/marked');
|
||||
1573
components/marked/lib/marked.js
Normal file
1573
components/marked/lib/marked.js
Normal file
File diff suppressed because it is too large
Load Diff
12542
components/mpvue-citypicker/city-data/area.js
Normal file
12542
components/mpvue-citypicker/city-data/area.js
Normal file
File diff suppressed because it is too large
Load Diff
1503
components/mpvue-citypicker/city-data/city.js
Normal file
1503
components/mpvue-citypicker/city-data/city.js
Normal file
File diff suppressed because it is too large
Load Diff
139
components/mpvue-citypicker/city-data/province.js
Normal file
139
components/mpvue-citypicker/city-data/province.js
Normal file
@ -0,0 +1,139 @@
|
||||
/* eslint-disable */
|
||||
var provinceData = [{
|
||||
"label": "北京市",
|
||||
"value": "11"
|
||||
},
|
||||
{
|
||||
"label": "天津市",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "河北省",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "山西省",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "内蒙古自治区",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "辽宁省",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"label": "吉林省",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"label": "黑龙江省",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"label": "上海市",
|
||||
"value": "31"
|
||||
},
|
||||
{
|
||||
"label": "江苏省",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"label": "浙江省",
|
||||
"value": "33"
|
||||
},
|
||||
{
|
||||
"label": "安徽省",
|
||||
"value": "34"
|
||||
},
|
||||
{
|
||||
"label": "福建省",
|
||||
"value": "35"
|
||||
},
|
||||
{
|
||||
"label": "江西省",
|
||||
"value": "36"
|
||||
},
|
||||
{
|
||||
"label": "山东省",
|
||||
"value": "37"
|
||||
},
|
||||
{
|
||||
"label": "河南省",
|
||||
"value": "41"
|
||||
},
|
||||
{
|
||||
"label": "湖北省",
|
||||
"value": "42"
|
||||
},
|
||||
{
|
||||
"label": "湖南省",
|
||||
"value": "43"
|
||||
},
|
||||
{
|
||||
"label": "广东省",
|
||||
"value": "44"
|
||||
},
|
||||
{
|
||||
"label": "广西壮族自治区",
|
||||
"value": "45"
|
||||
},
|
||||
{
|
||||
"label": "海南省",
|
||||
"value": "46"
|
||||
},
|
||||
{
|
||||
"label": "重庆市",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"label": "四川省",
|
||||
"value": "51"
|
||||
},
|
||||
{
|
||||
"label": "贵州省",
|
||||
"value": "52"
|
||||
},
|
||||
{
|
||||
"label": "云南省",
|
||||
"value": "53"
|
||||
},
|
||||
{
|
||||
"label": "西藏自治区",
|
||||
"value": "54"
|
||||
},
|
||||
{
|
||||
"label": "陕西省",
|
||||
"value": "61"
|
||||
},
|
||||
{
|
||||
"label": "甘肃省",
|
||||
"value": "62"
|
||||
},
|
||||
{
|
||||
"label": "青海省",
|
||||
"value": "63"
|
||||
},
|
||||
{
|
||||
"label": "宁夏回族自治区",
|
||||
"value": "64"
|
||||
},
|
||||
{
|
||||
"label": "新疆维吾尔自治区",
|
||||
"value": "65"
|
||||
},
|
||||
{
|
||||
"label": "台湾",
|
||||
"value": "66"
|
||||
},
|
||||
{
|
||||
"label": "香港",
|
||||
"value": "67"
|
||||
},
|
||||
{
|
||||
"label": "澳门",
|
||||
"value": "68"
|
||||
}
|
||||
]
|
||||
export default provinceData;
|
||||
230
components/mpvue-citypicker/mpvueCityPicker.vue
Normal file
230
components/mpvue-citypicker/mpvueCityPicker.vue
Normal file
@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div class="mpvue-picker">
|
||||
<div :class="{'pickerMask':showPicker}" @click="maskClick" catchtouchmove="true"></div>
|
||||
<div class="mpvue-picker-content " :class="{'mpvue-picker-view-show':showPicker}">
|
||||
<div class="mpvue-picker__hd" catchtouchmove="true">
|
||||
<div class="mpvue-picker__action" @click="pickerCancel">取消</div>
|
||||
<div class="mpvue-picker__action" :style="{color:themeColor}" @click="pickerConfirm">确定</div>
|
||||
</div>
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange">
|
||||
<block>
|
||||
<picker-view-column>
|
||||
<div class="picker-item" v-for="(item,index) in provinceDataList" :key="index">{{item.label}}</div>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<div class="picker-item" v-for="(item,index) in cityDataList" :key="index">{{item.label}}</div>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<div class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.label}}</div>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import provinceData from './city-data/province.js';
|
||||
import cityData from './city-data/city.js';
|
||||
import areaData from './city-data/area.js';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickerValue: [0, 0, 0],
|
||||
provinceDataList: provinceData,
|
||||
cityDataList: cityData[0],
|
||||
areaDataList: areaData[0][0],
|
||||
/* 是否显示控件 */
|
||||
showPicker: false,
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
},
|
||||
props: {
|
||||
/* 默认值 */
|
||||
pickerValueDefault: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [0, 0, 0]
|
||||
}
|
||||
},
|
||||
/* 主题色 */
|
||||
themeColor: String
|
||||
},
|
||||
watch: {
|
||||
pickerValueDefault() {
|
||||
this.init();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.handPickValueDefault(); // 对 pickerValueDefault 做兼容处理
|
||||
|
||||
const pickerValueDefault = this.pickerValueDefault
|
||||
|
||||
this.cityDataList = cityData[pickerValueDefault[0]];
|
||||
this.areaDataList = areaData[pickerValueDefault[0]][pickerValueDefault[1]];
|
||||
this.pickerValue = pickerValueDefault;
|
||||
},
|
||||
show() {
|
||||
setTimeout(() => {
|
||||
this.showPicker = true;
|
||||
}, 0);
|
||||
},
|
||||
maskClick() {
|
||||
this.pickerCancel();
|
||||
},
|
||||
pickerCancel() {
|
||||
this.showPicker = false;
|
||||
this._$emit('onCancel');
|
||||
},
|
||||
pickerConfirm(e) {
|
||||
this.showPicker = false;
|
||||
this._$emit('onConfirm');
|
||||
},
|
||||
showPickerView() {
|
||||
this.showPicker = true;
|
||||
},
|
||||
handPickValueDefault() {
|
||||
const pickerValueDefault = this.pickerValueDefault
|
||||
|
||||
let provinceIndex = pickerValueDefault[0]
|
||||
let cityIndex = pickerValueDefault[1]
|
||||
const areaIndex = pickerValueDefault[2]
|
||||
if (
|
||||
provinceIndex !== 0 ||
|
||||
cityIndex !== 0 ||
|
||||
areaIndex !== 0
|
||||
) {
|
||||
if (provinceIndex > provinceData.length - 1) {
|
||||
this.pickerValueDefault[0] = provinceIndex = provinceData.length - 1;
|
||||
}
|
||||
if (cityIndex > cityData[provinceIndex].length - 1) {
|
||||
this.pickerValueDefault[1] = cityIndex = cityData[provinceIndex].length - 1;
|
||||
}
|
||||
if (areaIndex > areaData[provinceIndex][cityIndex].length - 1) {
|
||||
this.pickerValueDefault[2] = areaData[provinceIndex][cityIndex].length - 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
pickerChange(e) {
|
||||
let changePickerValue = e.mp.detail.value;
|
||||
if (this.pickerValue[0] !== changePickerValue[0]) {
|
||||
// 第一级发生滚动
|
||||
this.cityDataList = cityData[changePickerValue[0]];
|
||||
this.areaDataList = areaData[changePickerValue[0]][0];
|
||||
changePickerValue[1] = 0;
|
||||
changePickerValue[2] = 0;
|
||||
} else if (this.pickerValue[1] !== changePickerValue[1]) {
|
||||
// 第二级滚动
|
||||
this.areaDataList =
|
||||
areaData[changePickerValue[0]][changePickerValue[1]];
|
||||
changePickerValue[2] = 0;
|
||||
}
|
||||
this.pickerValue = changePickerValue;
|
||||
this._$emit('onChange');
|
||||
},
|
||||
_$emit(emitName) {
|
||||
let pickObj = {
|
||||
label: this._getLabel(),
|
||||
value: this.pickerValue,
|
||||
cityCode: this._getCityCode()
|
||||
};
|
||||
this.$emit(emitName, pickObj);
|
||||
},
|
||||
_getLabel() {
|
||||
let pcikerLabel =
|
||||
this.provinceDataList[this.pickerValue[0]].label +
|
||||
'-' +
|
||||
this.cityDataList[this.pickerValue[1]].label +
|
||||
'-' +
|
||||
this.areaDataList[this.pickerValue[2]].label;
|
||||
return pcikerLabel;
|
||||
},
|
||||
_getCityCode() {
|
||||
return this.areaDataList[this.pickerValue[2]].value;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.pickerMask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.mpvue-picker-content {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.mpvue-picker-view-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.mpvue-picker__hd {
|
||||
display: flex;
|
||||
padding: 9px 15px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.mpvue-picker__hd:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
color: #e5e5e5;
|
||||
transform-origin: 0 100%;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.mpvue-picker__action {
|
||||
display: block;
|
||||
flex: 1;
|
||||
color: #1aad19;
|
||||
}
|
||||
|
||||
.mpvue-picker__action:first-child {
|
||||
text-align: left;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.mpvue-picker__action:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mpvue-picker-view {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 238px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
</style>
|
||||
123
components/mpvue-echarts/src/echarts.vue
Normal file
123
components/mpvue-echarts/src/echarts.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<canvas v-if="canvasId" class="ec-canvas" :id="canvasId" :canvasId="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @error="error"></canvas>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WxCanvas from './wx-canvas';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
canvasId: {
|
||||
type: String,
|
||||
default: 'ec-canvas'
|
||||
},
|
||||
lazyLoad: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disableTouch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
throttleTouch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
// #ifdef H5
|
||||
mounted() {
|
||||
if (!this.lazyLoad) this.init();
|
||||
},
|
||||
// #endif
|
||||
// #ifndef H5
|
||||
onReady() {
|
||||
if (!this.lazyLoad) this.init();
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
setChart(chart){
|
||||
this.chart = chart
|
||||
},
|
||||
init() {
|
||||
const { canvasId } = this;
|
||||
this.ctx = wx.createCanvasContext(canvasId, this);
|
||||
|
||||
this.canvas = new WxCanvas(this.ctx, canvasId);
|
||||
|
||||
const query = wx.createSelectorQuery().in(this);
|
||||
query
|
||||
.select(`#${canvasId}`)
|
||||
.boundingClientRect(res => {
|
||||
if (!res) {
|
||||
setTimeout(() => this.init(), 50);
|
||||
return;
|
||||
}
|
||||
this.$emit('onInit', {
|
||||
width: res.width,
|
||||
height: res.height
|
||||
});
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
canvasToTempFilePath(opt) {
|
||||
const { canvasId } = this;
|
||||
this.ctx.draw(true, () => {
|
||||
wx.canvasToTempFilePath({
|
||||
canvasId,
|
||||
...opt
|
||||
});
|
||||
});
|
||||
},
|
||||
touchStart(e) {
|
||||
const { disableTouch, chart } = this;
|
||||
if (disableTouch || !chart || !e.mp.touches.length) return;
|
||||
const touch = e.mp.touches[0];
|
||||
chart._zr.handler.dispatch('mousedown', {
|
||||
zrX: touch.x,
|
||||
zrY: touch.y
|
||||
});
|
||||
chart._zr.handler.dispatch('mousemove', {
|
||||
zrX: touch.x,
|
||||
zrY: touch.y
|
||||
});
|
||||
},
|
||||
touchMove(e) {
|
||||
const { disableTouch, throttleTouch, chart, lastMoveTime } = this;
|
||||
if (disableTouch || !chart || !e.mp.touches.length) return;
|
||||
|
||||
if (throttleTouch) {
|
||||
const currMoveTime = Date.now();
|
||||
if (currMoveTime - lastMoveTime < 240) return;
|
||||
this.lastMoveTime = currMoveTime;
|
||||
}
|
||||
|
||||
const touch = e.mp.touches[0];
|
||||
chart._zr.handler.dispatch('mousemove', {
|
||||
zrX: touch.x,
|
||||
zrY: touch.y
|
||||
});
|
||||
},
|
||||
touchEnd(e) {
|
||||
const { disableTouch, chart } = this;
|
||||
if (disableTouch || !chart) return;
|
||||
const touch = e.mp.changedTouches ? e.mp.changedTouches[0] : {};
|
||||
chart._zr.handler.dispatch('mouseup', {
|
||||
zrX: touch.x,
|
||||
zrY: touch.y
|
||||
});
|
||||
chart._zr.handler.dispatch('click', {
|
||||
zrX: touch.x,
|
||||
zrY: touch.y
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ec-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
73
components/mpvue-echarts/src/wx-canvas.js
Normal file
73
components/mpvue-echarts/src/wx-canvas.js
Normal file
@ -0,0 +1,73 @@
|
||||
export default class WxCanvas {
|
||||
constructor(ctx, canvasId) {
|
||||
this.ctx = ctx;
|
||||
this.canvasId = canvasId;
|
||||
this.chart = null;
|
||||
|
||||
WxCanvas.initStyle(ctx);
|
||||
this.initEvent();
|
||||
}
|
||||
|
||||
getContext(contextType) {
|
||||
return contextType === '2d' ? this.ctx : null;
|
||||
}
|
||||
|
||||
setChart(chart) {
|
||||
this.chart = chart;
|
||||
}
|
||||
|
||||
attachEvent() {
|
||||
// noop
|
||||
}
|
||||
|
||||
detachEvent() {
|
||||
// noop
|
||||
}
|
||||
|
||||
static initStyle(ctx) {
|
||||
const styles = ['fillStyle', 'strokeStyle', 'globalAlpha',
|
||||
'textAlign', 'textBaseAlign', 'shadow', 'lineWidth',
|
||||
'lineCap', 'lineJoin', 'lineDash', 'miterLimit', 'fontSize'];
|
||||
|
||||
styles.forEach((style) => {
|
||||
Object.defineProperty(ctx, style, {
|
||||
set: (value) => {
|
||||
if ((style !== 'fillStyle' && style !== 'strokeStyle')
|
||||
|| (value !== 'none' && value !== null)
|
||||
) {
|
||||
ctx[`set${style.charAt(0).toUpperCase()}${style.slice(1)}`](value);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
ctx.createRadialGradient = () => ctx.createCircularGradient(arguments);
|
||||
}
|
||||
|
||||
initEvent() {
|
||||
this.event = {};
|
||||
const eventNames = [{
|
||||
wxName: 'touchStart',
|
||||
ecName: 'mousedown',
|
||||
}, {
|
||||
wxName: 'touchMove',
|
||||
ecName: 'mousemove',
|
||||
}, {
|
||||
wxName: 'touchEnd',
|
||||
ecName: 'mouseup',
|
||||
}, {
|
||||
wxName: 'touchEnd',
|
||||
ecName: 'click',
|
||||
}];
|
||||
|
||||
eventNames.forEach((name) => {
|
||||
this.event[name.wxName] = (e) => {
|
||||
const touch = e.mp.touches[0];
|
||||
this.chart._zr.handler.dispatch(name.ecName, {
|
||||
zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
|
||||
zrY: name.wxName === 'tap' ? touch.clientY : touch.y,
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
463
components/mpvue-picker/mpvuePicker.vue
Normal file
463
components/mpvue-picker/mpvuePicker.vue
Normal file
@ -0,0 +1,463 @@
|
||||
<template>
|
||||
<view class="mpvue-picker">
|
||||
<view :class="{'pickerMask':showPicker}" @click="maskClick" catchtouchmove="true"></view>
|
||||
<view class="mpvue-picker-content " :class="{'mpvue-picker-view-show':showPicker}">
|
||||
<view class="mpvue-picker__hd" catchtouchmove="true">
|
||||
<view class="mpvue-picker__action" @click="pickerCancel">取消</view>
|
||||
<view class="mpvue-picker__action" :style="{color:themeColor}" @click="pickerConfirm">确定</view>
|
||||
</view>
|
||||
<!-- 单列 -->
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange" v-if="mode==='selector' && pickerValueSingleArray.length > 0">
|
||||
<block>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueSingleArray" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
<!-- 时间选择器 -->
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange" v-if="mode==='timeSelector'">
|
||||
<block>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueHour" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMinute" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
<!-- 多列选择 -->
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange" v-if="mode==='multiSelector'">
|
||||
<block v-for="(n,index) in pickerValueMulArray.length" :key="index">
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index1) in pickerValueMulArray[n]" :key="index1">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
<!-- 二级联动 -->
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChangeMul" v-if="mode==='multiLinkageSelector' && deepLength===2">
|
||||
<block>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMulTwoOne" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMulTwoTwo" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
<!-- 三级联动 -->
|
||||
<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChangeMul" v-if="mode==='multiLinkageSelector' && deepLength===3">
|
||||
<block>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMulThreeOne" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMulThreeTwo" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="picker-item" v-for="(item,index) in pickerValueMulThreeThree" :key="index">{{item.label}}</view>
|
||||
</picker-view-column>
|
||||
</block>
|
||||
</picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickerChangeValue: [],
|
||||
pickerValue: [],
|
||||
pickerValueArrayChange: true,
|
||||
modeChange: false,
|
||||
pickerValueSingleArray: [],
|
||||
pickerValueHour: [],
|
||||
pickerValueMinute: [],
|
||||
pickerValueMulArray: [],
|
||||
pickerValueMulTwoOne: [],
|
||||
pickerValueMulTwoTwo: [],
|
||||
pickerValueMulThreeOne: [],
|
||||
pickerValueMulThreeTwo: [],
|
||||
pickerValueMulThreeThree: [],
|
||||
/* 是否显示控件 */
|
||||
showPicker: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
/* mode */
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'selector'
|
||||
},
|
||||
/* picker 数值 */
|
||||
pickerValueArray: {
|
||||
type: Array,
|
||||
default(){
|
||||
return []
|
||||
}
|
||||
},
|
||||
/* 默认值 */
|
||||
pickerValueDefault: {
|
||||
type: Array,
|
||||
default(){
|
||||
return []
|
||||
}
|
||||
},
|
||||
/* 几级联动 */
|
||||
deepLength: {
|
||||
type: Number,
|
||||
default: 2
|
||||
},
|
||||
/* 主题色 */
|
||||
themeColor: String
|
||||
},
|
||||
watch: {
|
||||
pickerValueArray(oldVal, newVal) {
|
||||
this.pickerValueArrayChange = true;
|
||||
},
|
||||
mode(oldVal, newVal) {
|
||||
this.modeChange = true;
|
||||
},
|
||||
pickerValueArray(val){
|
||||
this.initPicker(val);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initPicker(valueArray) {
|
||||
let pickerValueArray = valueArray;
|
||||
this.pickerValue = this.pickerValueDefault;
|
||||
// 初始化多级联动
|
||||
if (this.mode === 'selector') {
|
||||
this.pickerValueSingleArray = valueArray;
|
||||
} else if (this.mode === 'timeSelector') {
|
||||
this.modeChange = false;
|
||||
let hourArray = [];
|
||||
let minuteArray = [];
|
||||
for (let i = 0; i < 24; i++) {
|
||||
hourArray.push({
|
||||
value: i,
|
||||
label: i > 9 ? `${i} 时` : `0${i} 时`
|
||||
});
|
||||
}
|
||||
for (let i = 0; i < 60; i++) {
|
||||
minuteArray.push({
|
||||
value: i,
|
||||
label: i > 9 ? `${i} 分` : `0${i} 分`
|
||||
});
|
||||
}
|
||||
this.pickerValueHour = hourArray;
|
||||
this.pickerValueMinute = minuteArray;
|
||||
} else if (this.mode === 'multiSelector') {
|
||||
this.pickerValueMulArray = valueArray;
|
||||
} else if (this.mode === 'multiLinkageSelector' && this.deepLength === 2) {
|
||||
// 两级联动
|
||||
let pickerValueMulTwoOne = [];
|
||||
let pickerValueMulTwoTwo = [];
|
||||
// 第一列
|
||||
for (let i = 0, length = pickerValueArray.length; i < length; i++) {
|
||||
pickerValueMulTwoOne.push(pickerValueArray[i]);
|
||||
}
|
||||
// 渲染第二列
|
||||
// 如果有设定的默认值
|
||||
if (this.pickerValueDefault.length === 2) {
|
||||
let num = this.pickerValueDefault[0];
|
||||
for (
|
||||
let i = 0, length = pickerValueArray[num].children.length; i < length; i++
|
||||
) {
|
||||
pickerValueMulTwoTwo.push(pickerValueArray[num].children[i]);
|
||||
}
|
||||
} else {
|
||||
for (
|
||||
let i = 0, length = pickerValueArray[0].children.length; i < length; i++
|
||||
) {
|
||||
pickerValueMulTwoTwo.push(pickerValueArray[0].children[i]);
|
||||
}
|
||||
}
|
||||
this.pickerValueMulTwoOne = pickerValueMulTwoOne;
|
||||
this.pickerValueMulTwoTwo = pickerValueMulTwoTwo;
|
||||
} else if (
|
||||
this.mode === 'multiLinkageSelector' &&
|
||||
this.deepLength === 3
|
||||
) {
|
||||
let pickerValueMulThreeOne = [];
|
||||
let pickerValueMulThreeTwo = [];
|
||||
let pickerValueMulThreeThree = [];
|
||||
// 第一列
|
||||
for (let i = 0, length = pickerValueArray.length; i < length; i++) {
|
||||
pickerValueMulThreeOne.push(pickerValueArray[i]);
|
||||
}
|
||||
// 渲染第二列
|
||||
this.pickerValueDefault =
|
||||
this.pickerValueDefault.length === 3 ?
|
||||
this.pickerValueDefault :
|
||||
[0, 0, 0];
|
||||
if (this.pickerValueDefault.length === 3) {
|
||||
let num = this.pickerValueDefault[0];
|
||||
for (
|
||||
let i = 0, length = pickerValueArray[num].children.length; i < length; i++
|
||||
) {
|
||||
pickerValueMulThreeTwo.push(pickerValueArray[num].children[i]);
|
||||
}
|
||||
// 第三列
|
||||
let numSecond = this.pickerValueDefault[1];
|
||||
for (let i = 0, length = pickerValueArray[num].children[numSecond].children.length; i < length; i++) {
|
||||
pickerValueMulThreeThree.push(
|
||||
pickerValueArray[num].children[numSecond].children[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
this.pickerValueMulThreeOne = pickerValueMulThreeOne;
|
||||
this.pickerValueMulThreeTwo = pickerValueMulThreeTwo;
|
||||
this.pickerValueMulThreeThree = pickerValueMulThreeThree;
|
||||
}
|
||||
},
|
||||
show() {
|
||||
setTimeout(() => {
|
||||
if (this.pickerValueArrayChange || this.modeChange) {
|
||||
this.initPicker(this.pickerValueArray);
|
||||
this.showPicker = true;
|
||||
this.pickerValueArrayChange = false;
|
||||
this.modeChange = false;
|
||||
} else {
|
||||
this.showPicker = true;
|
||||
}
|
||||
}, 0);
|
||||
},
|
||||
maskClick() {
|
||||
this.pickerCancel();
|
||||
},
|
||||
pickerCancel() {
|
||||
this.showPicker = false;
|
||||
this._initPickerVale();
|
||||
let pickObj = {
|
||||
index: this.pickerValue,
|
||||
value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
|
||||
label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
|
||||
};
|
||||
this.$emit('onCancel', pickObj);
|
||||
},
|
||||
pickerConfirm(e) {
|
||||
this.showPicker = false;
|
||||
this._initPickerVale();
|
||||
let pickObj = {
|
||||
index: this.pickerValue,
|
||||
value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
|
||||
label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
|
||||
};
|
||||
this.$emit('onConfirm', pickObj);
|
||||
},
|
||||
showPickerView() {
|
||||
this.showPicker = true;
|
||||
},
|
||||
pickerChange(e) {
|
||||
this.pickerValue = e.mp.detail.value;
|
||||
let pickObj = {
|
||||
index: this.pickerValue,
|
||||
value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
|
||||
label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
|
||||
};
|
||||
this.$emit('onChange', pickObj);
|
||||
},
|
||||
pickerChangeMul(e) {
|
||||
if (this.deepLength === 2) {
|
||||
let pickerValueArray = this.pickerValueArray;
|
||||
let changeValue = e.mp.detail.value;
|
||||
// 处理第一列滚动
|
||||
if (changeValue[0] !== this.pickerValue[0]) {
|
||||
let pickerValueMulTwoTwo = [];
|
||||
// 第一列滚动第二列数据更新
|
||||
for (let i = 0, length = pickerValueArray[changeValue[0]].children.length; i < length; i++) {
|
||||
pickerValueMulTwoTwo.push(pickerValueArray[changeValue[0]].children[i]);
|
||||
}
|
||||
this.pickerValueMulTwoTwo = pickerValueMulTwoTwo;
|
||||
// 第二列初始化为 0
|
||||
changeValue[1] = 0;
|
||||
}
|
||||
this.pickerValue = changeValue;
|
||||
} else if (this.deepLength === 3) {
|
||||
let pickerValueArray = this.pickerValueArray;
|
||||
let changeValue = e.mp.detail.value;
|
||||
let pickerValueMulThreeTwo = [];
|
||||
let pickerValueMulThreeThree = [];
|
||||
// 重新渲染第二列
|
||||
// 如果是第一列滚动
|
||||
if (changeValue[0] !== this.pickerValue[0]) {
|
||||
this.pickerValueMulThreeTwo = [];
|
||||
for (let i = 0, length = pickerValueArray[changeValue[0]].children.length; i < length; i++) {
|
||||
pickerValueMulThreeTwo.push(pickerValueArray[changeValue[0]].children[i]);
|
||||
}
|
||||
// 重新渲染第三列
|
||||
for (let i = 0, length = pickerValueArray[changeValue[0]].children[0].children.length; i <
|
||||
length; i++) {
|
||||
pickerValueMulThreeThree.push(pickerValueArray[changeValue[0]].children[0].children[i]);
|
||||
}
|
||||
changeValue[1] = 0;
|
||||
changeValue[2] = 0;
|
||||
this.pickerValueMulThreeTwo = pickerValueMulThreeTwo;
|
||||
this.pickerValueMulThreeThree = pickerValueMulThreeThree;
|
||||
} else if (changeValue[1] !== this.pickerValue[1]) {
|
||||
// 第二列滚动
|
||||
// 重新渲染第三列
|
||||
this.pickerValueMulThreeThree = [];
|
||||
pickerValueMulThreeTwo = this.pickerValueMulThreeTwo;
|
||||
for (let i = 0, length = pickerValueArray[changeValue[0]].children[changeValue[1]].children.length; i <
|
||||
length; i++) {
|
||||
pickerValueMulThreeThree.push(pickerValueArray[changeValue[0]].children[changeValue[1]].children[
|
||||
i]);
|
||||
}
|
||||
changeValue[2] = 0;
|
||||
this.pickerValueMulThreeThree = pickerValueMulThreeThree;
|
||||
}
|
||||
this.pickerValue = changeValue;
|
||||
}
|
||||
let pickObj = {
|
||||
index: this.pickerValue,
|
||||
value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
|
||||
label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
|
||||
};
|
||||
this.$emit('onChange', pickObj);
|
||||
},
|
||||
// 获取 pxikerLabel
|
||||
_getPickerLabelAndValue(value, mode) {
|
||||
let pickerLable;
|
||||
let pickerGetValue = [];
|
||||
// selector
|
||||
if (mode === 'selector') {
|
||||
pickerLable = this.pickerValueSingleArray[value].label;
|
||||
pickerGetValue.push(this.pickerValueSingleArray[value].value);
|
||||
} else if (mode === 'timeSelector') {
|
||||
pickerLable = `${this.pickerValueHour[value[0]].label}-${this.pickerValueMinute[value[1]].label}`;
|
||||
pickerGetValue.push(this.pickerValueHour[value[0]].value);
|
||||
pickerGetValue.push(this.pickerValueHour[value[1]].value);
|
||||
} else if (mode === 'multiSelector') {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (i > 0) {
|
||||
pickerLable += this.pickerValueMulArray[i][value[i]].label + (i === value.length - 1 ? '' :
|
||||
'-');
|
||||
} else {
|
||||
pickerLable = this.pickerValueMulArray[i][value[i]].label + '-';
|
||||
}
|
||||
pickerGetValue.push(this.pickerValueMulArray[i][value[i]].value);
|
||||
}
|
||||
} else if (mode === 'multiLinkageSelector') {
|
||||
/* eslint-disable indent */
|
||||
pickerLable =
|
||||
this.deepLength === 2 ?
|
||||
`${this.pickerValueMulTwoOne[value[0]].label}-${this.pickerValueMulTwoTwo[value[1]].label}` :
|
||||
`${this.pickerValueMulThreeOne[value[0]].label}-${this.pickerValueMulThreeTwo[value[1]].label}-${this.pickerValueMulThreeThree[value[2]].label}`;
|
||||
if (this.deepLength === 2) {
|
||||
pickerGetValue.push(this.pickerValueMulTwoOne[value[0]].value);
|
||||
pickerGetValue.push(this.pickerValueMulTwoTwo[value[1]].value);
|
||||
} else {
|
||||
pickerGetValue.push(this.pickerValueMulThreeOne[value[0]].value);
|
||||
pickerGetValue.push(this.pickerValueMulThreeTwo[value[1]].value);
|
||||
pickerGetValue.push(this.pickerValueMulThreeThree[value[2]].value);
|
||||
}
|
||||
/* eslint-enable indent */
|
||||
}
|
||||
return {
|
||||
label: pickerLable,
|
||||
value: pickerGetValue
|
||||
};
|
||||
},
|
||||
// 初始化 pickerValue 默认值
|
||||
_initPickerVale() {
|
||||
if (this.pickerValue.length === 0) {
|
||||
if (this.mode === 'selector') {
|
||||
this.pickerValue = [0];
|
||||
} else if (this.mode === 'multiSelector') {
|
||||
this.pickerValue = new Int8Array(this.pickerValueArray.length);
|
||||
} else if (
|
||||
this.mode === 'multiLinkageSelector' &&
|
||||
this.deepLength === 2
|
||||
) {
|
||||
this.pickerValue = [0, 0];
|
||||
} else if (
|
||||
this.mode === 'multiLinkageSelector' &&
|
||||
this.deepLength === 3
|
||||
) {
|
||||
this.pickerValue = [0, 0, 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.pickerMask {
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.mpvue-picker-content {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateY(100%);
|
||||
z-index: 3000;
|
||||
}
|
||||
|
||||
.mpvue-picker-view-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.mpvue-picker__hd {
|
||||
display: flex;
|
||||
padding: 9px 15px;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.mpvue-picker__hd:after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
color: #e5e5e5;
|
||||
transform-origin: 0 100%;
|
||||
transform: scaleY(0.5);
|
||||
}
|
||||
|
||||
.mpvue-picker__action {
|
||||
display: block;
|
||||
flex: 1;
|
||||
color: #1aad19;
|
||||
}
|
||||
|
||||
.mpvue-picker__action:first-child {
|
||||
text-align: left;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.mpvue-picker__action:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.picker-item {
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.mpvue-picker-view {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 238px;
|
||||
background-color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
</style>
|
||||
175
components/mpvueGestureLock/gestureLock.js
Normal file
175
components/mpvueGestureLock/gestureLock.js
Normal file
@ -0,0 +1,175 @@
|
||||
class GestureLock {
|
||||
|
||||
constructor(containerWidth, cycleRadius) {
|
||||
this.containerWidth = containerWidth; // 容器宽度
|
||||
this.cycleRadius = cycleRadius; // 圆的半径
|
||||
|
||||
this.circleArray = []; // 全部圆的对象数组
|
||||
this.checkPoints = []; // 选中的圆的对象数组
|
||||
this.lineArray = []; // 已激活锁之间的线段数组
|
||||
this.lastCheckPoint = 0; // 最后一个激活的锁
|
||||
this.offsetX = 0; // 容器的 X 偏移
|
||||
this.offsetY = 0; // 容器的 Y 偏移
|
||||
this.activeLine = {}; // 最后一个激活的锁与当前位置之间的线段
|
||||
|
||||
this.windowWidth = wx.getSystemInfoSync().windowWidth; // 窗口大小(用于rpx 和 px 转换)
|
||||
|
||||
this.initCircleArray();
|
||||
}
|
||||
|
||||
// 初始化 画布上的 9个圆
|
||||
initCircleArray() {
|
||||
const cycleMargin = (this.containerWidth - 6 * this.cycleRadius) / 6;
|
||||
let count = 0;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
for (let j = 0; j < 3; j++) {
|
||||
count++;
|
||||
this.circleArray.push({
|
||||
count: count,
|
||||
x: this.rpxTopx((cycleMargin + this.cycleRadius) * (j * 2 + 1)),
|
||||
y: this.rpxTopx((cycleMargin + this.cycleRadius) * (i * 2 + 1)),
|
||||
radius: this.rpxTopx(this.cycleRadius),
|
||||
check: false,
|
||||
style: {
|
||||
left: (cycleMargin + this.cycleRadius) * (j * 2 + 1) - this.cycleRadius + 'rpx',
|
||||
top: (cycleMargin + this.cycleRadius) * (i * 2 + 1) - this.cycleRadius + 'rpx',
|
||||
width: this.cycleRadius * 2 + 'rpx',
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTouchStart(e) {
|
||||
this.setOffset(e);
|
||||
this.checkTouch({
|
||||
x: e.touches[0].pageX - this.offsetX,
|
||||
y: e.touches[0].pageY - this.offsetY
|
||||
});
|
||||
}
|
||||
|
||||
onTouchMove(e) {
|
||||
this.moveDraw(e)
|
||||
}
|
||||
|
||||
onTouchEnd(e) {
|
||||
const checkPoints = this.checkPoints;
|
||||
this.reset();
|
||||
return checkPoints;
|
||||
}
|
||||
|
||||
// 初始化 偏移量
|
||||
setOffset(e) {
|
||||
this.offsetX = e.currentTarget.offsetLeft;
|
||||
this.offsetY = e.currentTarget.offsetTop;
|
||||
}
|
||||
|
||||
// 检测当时 触摸位置是否位于 锁上
|
||||
checkTouch({
|
||||
x,
|
||||
y
|
||||
}) {
|
||||
for (let i = 0; i < this.circleArray.length; i++) {
|
||||
let point = this.circleArray[i];
|
||||
if (this.isPointInCycle(x, y, point.x, point.y, point.radius)) {
|
||||
if (!point.check) {
|
||||
this.checkPoints.push(point.count);
|
||||
if (this.lastCheckPoint != 0) {
|
||||
// 已激活锁之间的线段
|
||||
const line = this.drawLine(this.lastCheckPoint, point);
|
||||
this.lineArray.push(line);
|
||||
}
|
||||
this.lastCheckPoint = point;
|
||||
}
|
||||
point.check = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 画线 - 返回 样式 对象
|
||||
drawLine(start, end) {
|
||||
const width = this.getPointDis(start.x, start.y, end.x, end.y);
|
||||
const rotate = this.getAngle(start, end);
|
||||
|
||||
return {
|
||||
activeLeft: start.x + 'px',
|
||||
activeTop: start.y + 'px',
|
||||
activeWidth: width + 'px',
|
||||
activeRotate: rotate + 'deg'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 获取 画线的 角度
|
||||
getAngle(start, end) {
|
||||
var diff_x = end.x - start.x,
|
||||
diff_y = end.y - start.y;
|
||||
if (diff_x >= 0) {
|
||||
return 360 * Math.atan(diff_y / diff_x) / (2 * Math.PI);
|
||||
} else {
|
||||
return 180 + 360 * Math.atan(diff_y / diff_x) / (2 * Math.PI);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断 当前点是否位于 锁内
|
||||
isPointInCycle(x, y, circleX, circleY, radius) {
|
||||
return (this.getPointDis(x, y, circleX, circleY) < radius) ? true : false;
|
||||
}
|
||||
|
||||
// 获取两点之间距离
|
||||
getPointDis(ax, ay, bx, by) {
|
||||
return Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));
|
||||
}
|
||||
|
||||
// 移动 绘制
|
||||
moveDraw(e) {
|
||||
// 画经过的圆
|
||||
const x = e.touches[0].pageX - this.offsetX;
|
||||
const y = e.touches[0].pageY - this.offsetY;
|
||||
this.checkTouch({
|
||||
x,
|
||||
y
|
||||
});
|
||||
|
||||
// 画 最后一个激活的锁与当前位置之间的线段
|
||||
this.activeLine = this.drawLine(this.lastCheckPoint, {
|
||||
x,
|
||||
y
|
||||
});
|
||||
}
|
||||
|
||||
// 使 画布 恢复初始状态
|
||||
reset() {
|
||||
this.circleArray.forEach((item) => {
|
||||
item.check = false;
|
||||
});
|
||||
this.checkPoints = [];
|
||||
this.lineArray = [];
|
||||
this.activeLine = {};
|
||||
this.lastCheckPoint = 0;
|
||||
}
|
||||
|
||||
|
||||
// 获取 最后一个激活的锁与当前位置之间的线段
|
||||
getActiveLine() {
|
||||
return this.activeLine;
|
||||
}
|
||||
|
||||
// 获取 圆对象数组
|
||||
getCycleArray() {
|
||||
return this.circleArray;
|
||||
}
|
||||
|
||||
// 获取 已激活锁之间的线段
|
||||
getLineArray() {
|
||||
return this.lineArray;
|
||||
}
|
||||
|
||||
// 将 RPX 转换成 PX
|
||||
rpxTopx(rpx) {
|
||||
return rpx / 750 * this.windowWidth;
|
||||
}
|
||||
}
|
||||
|
||||
export default GestureLock;
|
||||
138
components/mpvueGestureLock/index.vue
Normal file
138
components/mpvueGestureLock/index.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<view class="gesture-lock" :class="{error:error}" :style="{width: containerWidth +'rpx', height:containerWidth +'rpx'}"
|
||||
@touchstart.stop="onTouchStart" @touchmove.stop="onTouchMove" @touchend.stop="onTouchEnd">
|
||||
<!-- 同级 v-for 的 key 重复会有问题,需要套一层。 -->
|
||||
<!-- 9 个圆 -->
|
||||
<view>
|
||||
<view v-for="(item,i) in circleArray" :key="i" class="cycle" :class="{check:item.check}" :style="{left:item.style.left,top:item.style.top,width:item.style.width,height:item.style.width}">
|
||||
</view>
|
||||
</view>
|
||||
<view>
|
||||
<!-- 已激活锁之间的线段 -->
|
||||
<view v-for="(item,i) in lineArray" :key="i" class="line" :style="{left:item.activeLeft,top:item.activeTop,width:item.activeWidth,'-webkit-transform':'rotate('+item.activeRotate+')',transform:'rotate('+item.activeRotate+')'}">
|
||||
</view>
|
||||
</view>
|
||||
<!-- 最后一个激活的锁与当前位置之间的线段 -->
|
||||
<view class="line" :style="{left:activeLine.activeLeft,top:activeLine.activeTop,width:activeLine.activeWidth,'-webkit-transform':'rotate('+activeLine.activeRotate+')',transform:'rotate('+activeLine.activeRotate+')'}">
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import GestureLock from './gestureLock';
|
||||
|
||||
export default {
|
||||
name: 'index',
|
||||
props: {
|
||||
/**
|
||||
* 容器宽度
|
||||
*/
|
||||
containerWidth: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 圆的半径
|
||||
*/
|
||||
cycleRadius: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 已设定的密码
|
||||
*/
|
||||
password: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gestureLock: {}, // 锁对象
|
||||
circleArray: [], // 圆对象数组
|
||||
lineArray: [], // 已激活锁之间的线段
|
||||
activeLine: {}, // 最后一个激活的锁与当前位置之间的线段
|
||||
error: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onTouchStart(e) {
|
||||
this.gestureLock.onTouchStart(e);
|
||||
this.refesh();
|
||||
},
|
||||
|
||||
onTouchMove(e) {
|
||||
this.gestureLock.onTouchMove(e);
|
||||
this.refesh();
|
||||
},
|
||||
|
||||
onTouchEnd(e) {
|
||||
const checkPoints = this.gestureLock.onTouchEnd(e);
|
||||
if (!this.password.length || checkPoints.join('') == this.password.join('')) {
|
||||
this.refesh();
|
||||
this.$emit('end', checkPoints);
|
||||
} else {
|
||||
this.error = true;
|
||||
setTimeout(() => {
|
||||
this.refesh();
|
||||
this.$emit('end', checkPoints);
|
||||
}, 800);
|
||||
}
|
||||
|
||||
},
|
||||
refesh() {
|
||||
this.error = false;
|
||||
this.circleArray = this.gestureLock.getCycleArray();
|
||||
this.lineArray = this.gestureLock.getLineArray();
|
||||
this.activeLine = this.gestureLock.getActiveLine();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.gestureLock = new GestureLock(this.containerWidth, this.cycleRadius);
|
||||
this.refesh();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.gesture-lock {
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.gesture-lock .cycle {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
border: 2px solid #66aaff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.gesture-lock .cycle.check:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 32%;
|
||||
height: 32%;
|
||||
border: 2px solid #66aaff;
|
||||
border-radius: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.gesture-lock .line {
|
||||
height: 0;
|
||||
border-top: 2px solid #66aaff;
|
||||
position: absolute;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.gesture-lock.error .cycle.check,
|
||||
.gesture-lock.error .cycle.check:after,
|
||||
.gesture-lock.error .line {
|
||||
border-color: #ffa197;
|
||||
}
|
||||
</style>
|
||||
38
components/page-foot/page-foot.vue
Normal file
38
components/page-foot/page-foot.vue
Normal file
@ -0,0 +1,38 @@
|
||||
<template name="page-foot">
|
||||
<view class="page-share-title">
|
||||
<text>感谢{{name}}提供本示例,</text>
|
||||
<text class="submit" @tap="submit">我也提交</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "page-foot",
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
uni.showModal({
|
||||
content:"hello uni-app开源地址为https://github.com/dcloudio/uni-app/tree/master/examples,请在这个开源项目上贡献你的代码",
|
||||
showCancel:false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.page-share-title {
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
color: #BEBEBE;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.submit {
|
||||
border-bottom: 1rpx solid #BEBEBE;
|
||||
}
|
||||
</style>
|
||||
16
components/page-head/page-head.vue
Normal file
16
components/page-head/page-head.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<template name="page-head">
|
||||
<view class="uni-page-head">
|
||||
<view class="uni-page-head-title">{{title}}</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: "page-head",
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
66
components/product.vue
Normal file
66
components/product.vue
Normal file
@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<view class="product">
|
||||
<image class="product-image" :src="image ? image : 'https://via.placeholder.com/150x200'"></image>
|
||||
<view class="product-title">{{title}}</view>
|
||||
<view class="product-price">
|
||||
<text class="product-price-favour">¥{{originalPrice}}</text>
|
||||
<text class="product-price-original">¥{{favourPrice}}</text>
|
||||
<text class="product-tip">{{tip}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'product',
|
||||
props: ['image', 'title', 'originalPrice', 'favourPrice', 'tip']
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.product {
|
||||
padding: 10rpx 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.product-image {
|
||||
height: 330rpx;
|
||||
width: 330rpx;
|
||||
}
|
||||
|
||||
.product-title {
|
||||
width: 300rpx;
|
||||
font-size: 32rpx;
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 28rpx;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.product-price-original {
|
||||
color: #E80080;
|
||||
}
|
||||
|
||||
.product-price-favour {
|
||||
color: #888888;
|
||||
text-decoration: line-through;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.product-tip {
|
||||
position: absolute;
|
||||
right: 10rpx;
|
||||
background-color: #FF3333;
|
||||
color: #FFFFFF;
|
||||
padding: 0 10rpx;
|
||||
border-radius: 5rpx;
|
||||
}
|
||||
</style>
|
||||
175
components/tab-nvue/mediaList.vue
Normal file
175
components/tab-nvue/mediaList.vue
Normal file
@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<view class="view">
|
||||
<view class="list-cell view" hover-class="uni-list-cell-hover" @click="bindClick">
|
||||
<view class="media-list view" v-if="options.title">
|
||||
<view class="view" :class="{'media-image-right': options.article_type === 2, 'media-image-left': options.article_type === 1}">
|
||||
<text class="media-title" :class="{'media-title2': options.article_type === 1 || options.article_type === 2}">{{options.title}}</text>
|
||||
<view v-if="options.image_list || options.image_url" class="image-section view" :class="{'image-section-right': options.article_type === 2, 'image-section-left': options.article_type === 1}">
|
||||
<image class="image-list1" :class="{'image-list2': options.article_type === 1 || options.article_type === 2}"
|
||||
v-if="options.image_url" :src="options.image_url"></image>
|
||||
<image class="image-list3" v-if="options.image_list" :src="source.url" v-for="(source, i) in options.image_list"
|
||||
:key="i" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="media-foot view">
|
||||
<view class="media-info view">
|
||||
<text class="info-text">{{options.source}}</text>
|
||||
<text class="info-text">{{options.comment_count}}条评论</text>
|
||||
<text class="info-text">{{options.datetime}}</text>
|
||||
</view>
|
||||
<view class="max-close-view view" @click.stop="close">
|
||||
<view class="close-view view"><text class="close">×</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
default: function(e) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close(e) {
|
||||
this.$emit('close');
|
||||
},
|
||||
bindClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.list-cell {
|
||||
width: 750rpx;
|
||||
padding: 0 30rpx;
|
||||
}
|
||||
|
||||
.uni-list-cell-hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
.media-list {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #c8c7cc;
|
||||
padding: 20rpx 0;
|
||||
}
|
||||
|
||||
.media-image-right {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.media-image-left {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.media-title {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.media-title {
|
||||
lines: 3;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 32rpx;
|
||||
color: #555555;
|
||||
}
|
||||
|
||||
.media-title2 {
|
||||
flex: 1;
|
||||
margin-top: 6rpx;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.image-section {
|
||||
margin-top: 20rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.image-section-right {
|
||||
margin-top: 0rpx;
|
||||
margin-left: 10rpx;
|
||||
width: 225rpx;
|
||||
height: 146rpx;
|
||||
}
|
||||
|
||||
.image-section-left {
|
||||
margin-top: 0rpx;
|
||||
margin-right: 10rpx;
|
||||
width: 225rpx;
|
||||
height: 146rpx;
|
||||
}
|
||||
|
||||
.image-list1 {
|
||||
width: 690rpx;
|
||||
height: 481rpx;
|
||||
}
|
||||
|
||||
.image-list2 {
|
||||
width: 225rpx;
|
||||
height: 146rpx;
|
||||
}
|
||||
|
||||
.image-list3 {
|
||||
width: 225rpx;
|
||||
height: 146rpx;
|
||||
}
|
||||
|
||||
.media-info {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
margin-right: 20rpx;
|
||||
color: #999999;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.media-foot {
|
||||
margin-top: 20rpx;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.max-close-view {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-direction: row;
|
||||
height: 40rpx;
|
||||
width: 80rpx;
|
||||
}
|
||||
|
||||
.close-view {
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: #999999;
|
||||
border-radius: 10rpx;
|
||||
justify-content: center;
|
||||
height: 30rpx;
|
||||
width: 40rpx;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.close {
|
||||
text-align: center;
|
||||
color: #999999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
5046
components/u-charts/u-charts.js
Normal file
5046
components/u-charts/u-charts.js
Normal file
File diff suppressed because it is too large
Load Diff
59
components/u-link/u-link.vue
Normal file
59
components/u-link/u-link.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<text style="text-decoration:underline" :href="href" @click="openURL" :inWhiteList="inWhiteList">{{text}}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* @description u-link是一个外部网页超链接组件,在小程序内打开内部web-view组件或复制url,在app内打开外部浏览器,在h5端打开新网页
|
||||
* @property {String} href 点击后打开的外部网页url,小程序中必须以https://开头
|
||||
* @property {String} text 显示的文字
|
||||
* @property {Boolean} inWhiteList 是否在小程序白名单中,如果在的话,在小程序端会直接打开内置web-view,否则会只会复制url,提示在外部打开
|
||||
* @example * <u-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn" :inWhiteList="true"></u-link>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-link',
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
inWhiteList: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openURL() {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(this.href) //这里默认使用外部浏览器打开而不是内部web-view组件打开
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
window.open(this.href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
if (this.inWhiteList) { //如果在小程序的网址白名单中,会走内置webview打开,否则会复制网址提示在外部浏览器打开
|
||||
uni.navigateTo({
|
||||
url: '/pages/component/web-view/web-view?url=' + this.href
|
||||
});
|
||||
} else {
|
||||
uni.setClipboardData({
|
||||
data: this.href
|
||||
});
|
||||
uni.showModal({
|
||||
content: '本网址无法直接在小程序内打开。已自动复制网址,请在手机浏览器里粘贴该网址',
|
||||
showCancel: false
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
151
components/uni-badge/uni-badge.vue
Normal file
151
components/uni-badge/uni-badge.vue
Normal file
@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<text v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size" :style="badgeStyle" class="uni-badge" @click="onClick()">{{ text }}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Badge 数字角标
|
||||
* @description 数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21
|
||||
* @property {String} text 角标内容
|
||||
* @property {String} type = [default|primary|success|warning|error] 颜色类型
|
||||
* @value default 灰色
|
||||
* @value primary 蓝色
|
||||
* @value success 绿色
|
||||
* @value warning 黄色
|
||||
* @value error 红色
|
||||
* @property {String} size = [normal|small] Badge 大小
|
||||
* @value normal 一般尺寸
|
||||
* @value small 小尺寸
|
||||
* @property {String} inverted = [true|false] 是否无需背景颜色
|
||||
* @event {Function} click 点击 Badge 触发事件
|
||||
* @example <uni-badge text="1"></uni-badge>
|
||||
*/
|
||||
export default {
|
||||
name: 'UniBadge',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
inverted: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
text: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
badgeStyle: ''
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
text() {
|
||||
this.setStyle()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.setStyle()
|
||||
},
|
||||
methods: {
|
||||
setStyle() {
|
||||
this.badgeStyle = `width: ${String(this.text).length * 8 + 12}px`
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-badge {
|
||||
/* #ifndef APP-PLUS */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
color: #333;
|
||||
border-radius: 100px;
|
||||
background-color: #f1f1f1;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
font-family: 'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
padding: 0px 6px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-badge--inverted {
|
||||
padding: 0 5px 0 0;
|
||||
color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-badge--default {
|
||||
color: #333;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-badge--default-inverted {
|
||||
color: #999;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-badge--primary {
|
||||
color: #fff;
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.uni-badge--primary-inverted {
|
||||
color: #007aff;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-badge--success {
|
||||
color: #fff;
|
||||
background-color: #4cd964;
|
||||
}
|
||||
|
||||
.uni-badge--success-inverted {
|
||||
color: #4cd964;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-badge--warning {
|
||||
color: #fff;
|
||||
background-color: #f0ad4e;
|
||||
}
|
||||
|
||||
.uni-badge--warning-inverted {
|
||||
color: #f0ad4e;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-badge--error {
|
||||
color: #fff;
|
||||
background-color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-badge--error-inverted {
|
||||
color: #dd524d;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-badge--small {
|
||||
transform: scale(0.8);
|
||||
transform-origin: center center;
|
||||
}
|
||||
</style>
|
||||
546
components/uni-calendar/calendar.js
Normal file
546
components/uni-calendar/calendar.js
Normal file
@ -0,0 +1,546 @@
|
||||
/**
|
||||
* @1900-2100区间内的公历、农历互转
|
||||
* @charset UTF-8
|
||||
* @github https://github.com/jjonline/calendar.js
|
||||
* @Author Jea杨(JJonline@JJonline.Cn)
|
||||
* @Time 2014-7-21
|
||||
* @Time 2016-8-13 Fixed 2033hex、Attribution Annals
|
||||
* @Time 2016-9-25 Fixed lunar LeapMonth Param Bug
|
||||
* @Time 2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
|
||||
* @Version 1.0.3
|
||||
* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
|
||||
* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
|
||||
*/
|
||||
/* eslint-disable */
|
||||
var calendar = {
|
||||
|
||||
/**
|
||||
* 农历1900-2100的润大小信息表
|
||||
* @Array Of Property
|
||||
* @return Hex
|
||||
*/
|
||||
lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
|
||||
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
|
||||
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
|
||||
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
|
||||
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
|
||||
0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
|
||||
0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
|
||||
0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
|
||||
0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
|
||||
0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
|
||||
0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
|
||||
0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
|
||||
0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
|
||||
0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
|
||||
0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
|
||||
/** Add By JJonline@JJonline.Cn**/
|
||||
0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
|
||||
0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
|
||||
0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
|
||||
0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
|
||||
0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
|
||||
0x0d520], // 2100
|
||||
|
||||
/**
|
||||
* 公历每个月份的天数普通表
|
||||
* @Array Of Property
|
||||
* @return Number
|
||||
*/
|
||||
solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
|
||||
|
||||
/**
|
||||
* 天干地支之天干速查表
|
||||
* @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表
|
||||
* @Array Of Property
|
||||
* @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
|
||||
|
||||
/**
|
||||
* 天干地支之地支速查表<=>生肖
|
||||
* @Array Of Property
|
||||
* @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
|
||||
* @return Cn string
|
||||
*/
|
||||
Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
|
||||
|
||||
/**
|
||||
* 24节气速查表
|
||||
* @Array Of Property
|
||||
* @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
|
||||
* @return Cn string
|
||||
*/
|
||||
solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
|
||||
|
||||
/**
|
||||
* 1900-2100各年的24节气日期速查表
|
||||
* @Array Of Property
|
||||
* @return 0x string For splice
|
||||
*/
|
||||
sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
|
||||
'97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
|
||||
'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
|
||||
'97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
|
||||
'97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
|
||||
'97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
|
||||
'97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
|
||||
'7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
|
||||
'97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
|
||||
'97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
|
||||
'9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
|
||||
'97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
|
||||
'977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
|
||||
'7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
|
||||
'977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
|
||||
'977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
|
||||
'7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
|
||||
'7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
|
||||
'7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
|
||||
'7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
|
||||
'7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
|
||||
'7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
|
||||
'665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
|
||||
'7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
|
||||
'7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
|
||||
'7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
|
||||
|
||||
/**
|
||||
* 数字转中文速查表
|
||||
* @Array Of Property
|
||||
* @trans ['日','一','二','三','四','五','六','七','八','九','十']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
|
||||
|
||||
/**
|
||||
* 日期转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['初','十','廿','卅']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
|
||||
|
||||
/**
|
||||
* 月份转农历称呼速查表
|
||||
* @Array Of Property
|
||||
* @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
|
||||
* @return Cn string
|
||||
*/
|
||||
nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
|
||||
|
||||
/**
|
||||
* 返回农历y年一整年的总天数
|
||||
* @param lunar Year
|
||||
* @return Number
|
||||
* @eg:var count = calendar.lYearDays(1987) ;//count=387
|
||||
*/
|
||||
lYearDays: function (y) {
|
||||
var i; var sum = 348
|
||||
for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
|
||||
return (sum + this.leapDays(y))
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0-12)
|
||||
* @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
|
||||
*/
|
||||
leapMonth: function (y) { // 闰字编码 \u95f0
|
||||
return (this.lunarInfo[y - 1900] & 0xf)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年闰月的天数 若该年没有闰月则返回0
|
||||
* @param lunar Year
|
||||
* @return Number (0、29、30)
|
||||
* @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
|
||||
*/
|
||||
leapDays: function (y) {
|
||||
if (this.leapMonth(y)) {
|
||||
return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
|
||||
}
|
||||
return (0)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
|
||||
* @param lunar Year
|
||||
* @return Number (-1、29、30)
|
||||
* @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
|
||||
*/
|
||||
monthDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
|
||||
return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回公历(!)y年m月的天数
|
||||
* @param solar Year
|
||||
* @return Number (-1、28、29、30、31)
|
||||
* @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
|
||||
*/
|
||||
solarDays: function (y, m) {
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var ms = m - 1
|
||||
if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
|
||||
return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
|
||||
} else {
|
||||
return (this.solarMonth[ms])
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 农历年份转换为干支纪年
|
||||
* @param lYear 农历年的年份数
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhiYear: function (lYear) {
|
||||
var ganKey = (lYear - 3) % 10
|
||||
var zhiKey = (lYear - 3) % 12
|
||||
if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
|
||||
if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
|
||||
return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
|
||||
},
|
||||
|
||||
/**
|
||||
* 公历月、日判断所属星座
|
||||
* @param cMonth [description]
|
||||
* @param cDay [description]
|
||||
* @return Cn string
|
||||
*/
|
||||
toAstro: function (cMonth, cDay) {
|
||||
var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
|
||||
var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
|
||||
return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入offset偏移量返回干支
|
||||
* @param offset 相对甲子的偏移量
|
||||
* @return Cn string
|
||||
*/
|
||||
toGanZhi: function (offset) {
|
||||
return this.Gan[offset % 10] + this.Zhi[offset % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入公历(!)y年获得该年第n个节气的公历日期
|
||||
* @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
|
||||
* @return day Number
|
||||
* @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
|
||||
*/
|
||||
getTerm: function (y, n) {
|
||||
if (y < 1900 || y > 2100) { return -1 }
|
||||
if (n < 1 || n > 24) { return -1 }
|
||||
var _table = this.sTermInfo[y - 1900]
|
||||
var _info = [
|
||||
parseInt('0x' + _table.substr(0, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(5, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(10, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(15, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(20, 5)).toString(),
|
||||
parseInt('0x' + _table.substr(25, 5)).toString()
|
||||
]
|
||||
var _calday = [
|
||||
_info[0].substr(0, 1),
|
||||
_info[0].substr(1, 2),
|
||||
_info[0].substr(3, 1),
|
||||
_info[0].substr(4, 2),
|
||||
|
||||
_info[1].substr(0, 1),
|
||||
_info[1].substr(1, 2),
|
||||
_info[1].substr(3, 1),
|
||||
_info[1].substr(4, 2),
|
||||
|
||||
_info[2].substr(0, 1),
|
||||
_info[2].substr(1, 2),
|
||||
_info[2].substr(3, 1),
|
||||
_info[2].substr(4, 2),
|
||||
|
||||
_info[3].substr(0, 1),
|
||||
_info[3].substr(1, 2),
|
||||
_info[3].substr(3, 1),
|
||||
_info[3].substr(4, 2),
|
||||
|
||||
_info[4].substr(0, 1),
|
||||
_info[4].substr(1, 2),
|
||||
_info[4].substr(3, 1),
|
||||
_info[4].substr(4, 2),
|
||||
|
||||
_info[5].substr(0, 1),
|
||||
_info[5].substr(1, 2),
|
||||
_info[5].substr(3, 1),
|
||||
_info[5].substr(4, 2)
|
||||
]
|
||||
return parseInt(_calday[n - 1])
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历数字月份返回汉语通俗表示法
|
||||
* @param lunar month
|
||||
* @return Cn string
|
||||
* @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
|
||||
*/
|
||||
toChinaMonth: function (m) { // 月 => \u6708
|
||||
if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
|
||||
var s = this.nStr3[m - 1]
|
||||
s += '\u6708'// 加上月字
|
||||
return s
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历日期数字返回汉字表示法
|
||||
* @param lunar day
|
||||
* @return Cn string
|
||||
* @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
|
||||
*/
|
||||
toChinaDay: function (d) { // 日 => \u65e5
|
||||
var s
|
||||
switch (d) {
|
||||
case 10:
|
||||
s = '\u521d\u5341'; break
|
||||
case 20:
|
||||
s = '\u4e8c\u5341'; break
|
||||
break
|
||||
case 30:
|
||||
s = '\u4e09\u5341'; break
|
||||
break
|
||||
default :
|
||||
s = this.nStr2[Math.floor(d / 10)]
|
||||
s += this.nStr1[d % 10]
|
||||
}
|
||||
return (s)
|
||||
},
|
||||
|
||||
/**
|
||||
* 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
|
||||
* @param y year
|
||||
* @return Cn string
|
||||
* @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
|
||||
*/
|
||||
getAnimal: function (y) {
|
||||
return this.Animals[(y - 4) % 12]
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y solar year
|
||||
* @param m solar month
|
||||
* @param d solar day
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.solar2lunar(1987,11,01));
|
||||
*/
|
||||
solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
|
||||
// 年份限定、上限
|
||||
if (y < 1900 || y > 2100) {
|
||||
return -1// undefined转换为数字变为NaN
|
||||
}
|
||||
// 公历传参最下限
|
||||
if (y == 1900 && m == 1 && d < 31) {
|
||||
return -1
|
||||
}
|
||||
// 未传参 获得当天
|
||||
if (!y) {
|
||||
var objDate = new Date()
|
||||
} else {
|
||||
var objDate = new Date(y, parseInt(m) - 1, d)
|
||||
}
|
||||
var i; var leap = 0; var temp = 0
|
||||
// 修正ymd参数
|
||||
var y = objDate.getFullYear()
|
||||
var m = objDate.getMonth() + 1
|
||||
var d = objDate.getDate()
|
||||
var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
|
||||
for (i = 1900; i < 2101 && offset > 0; i++) {
|
||||
temp = this.lYearDays(i)
|
||||
offset -= temp
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; i--
|
||||
}
|
||||
|
||||
// 是否今天
|
||||
var isTodayObj = new Date()
|
||||
var isToday = false
|
||||
if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
|
||||
isToday = true
|
||||
}
|
||||
// 星期几
|
||||
var nWeek = objDate.getDay()
|
||||
var cWeek = this.nStr1[nWeek]
|
||||
// 数字表示周几顺应天朝周一开始的惯例
|
||||
if (nWeek == 0) {
|
||||
nWeek = 7
|
||||
}
|
||||
// 农历年
|
||||
var year = i
|
||||
var leap = this.leapMonth(i) // 闰哪个月
|
||||
var isLeap = false
|
||||
|
||||
// 效验闰月
|
||||
for (i = 1; i < 13 && offset > 0; i++) {
|
||||
// 闰月
|
||||
if (leap > 0 && i == (leap + 1) && isLeap == false) {
|
||||
--i
|
||||
isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
|
||||
} else {
|
||||
temp = this.monthDays(year, i)// 计算农历普通月天数
|
||||
}
|
||||
// 解除闰月
|
||||
if (isLeap == true && i == (leap + 1)) { isLeap = false }
|
||||
offset -= temp
|
||||
}
|
||||
// 闰月导致数组下标重叠取反
|
||||
if (offset == 0 && leap > 0 && i == leap + 1) {
|
||||
if (isLeap) {
|
||||
isLeap = false
|
||||
} else {
|
||||
isLeap = true; --i
|
||||
}
|
||||
}
|
||||
if (offset < 0) {
|
||||
offset += temp; --i
|
||||
}
|
||||
// 农历月
|
||||
var month = i
|
||||
// 农历日
|
||||
var day = offset + 1
|
||||
// 天干地支处理
|
||||
var sm = m - 1
|
||||
var gzY = this.toGanZhiYear(year)
|
||||
|
||||
// 当月的两个节气
|
||||
// bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
|
||||
var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
|
||||
var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
|
||||
|
||||
// 依据12节气修正干支月
|
||||
var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
|
||||
if (d >= firstNode) {
|
||||
gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
|
||||
}
|
||||
|
||||
// 传入的日期的节气与否
|
||||
var isTerm = false
|
||||
var Term = null
|
||||
if (firstNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 2]
|
||||
}
|
||||
if (secondNode == d) {
|
||||
isTerm = true
|
||||
Term = this.solarTerm[m * 2 - 1]
|
||||
}
|
||||
// 日柱 当月一日与 1900/1/1 相差天数
|
||||
var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
|
||||
var gzD = this.toGanZhi(dayCyclical + d - 1)
|
||||
// 该日期所属的星座
|
||||
var astro = this.toAstro(m, d)
|
||||
|
||||
return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
|
||||
},
|
||||
|
||||
/**
|
||||
* 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
|
||||
* @param y lunar year
|
||||
* @param m lunar month
|
||||
* @param d lunar day
|
||||
* @param isLeapMonth lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
|
||||
* @return JSON object
|
||||
* @eg:console.log(calendar.lunar2solar(1987,9,10));
|
||||
*/
|
||||
lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
|
||||
var isLeapMonth = !!isLeapMonth
|
||||
var leapOffset = 0
|
||||
var leapMonth = this.leapMonth(y)
|
||||
var leapDay = this.leapDays(y)
|
||||
if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
|
||||
if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
|
||||
var day = this.monthDays(y, m)
|
||||
var _day = day
|
||||
// bugFix 2016-9-25
|
||||
// if month is leap, _day use leapDays method
|
||||
if (isLeapMonth) {
|
||||
_day = this.leapDays(y, m)
|
||||
}
|
||||
if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
|
||||
|
||||
// 计算农历的时间差
|
||||
var offset = 0
|
||||
for (var i = 1900; i < y; i++) {
|
||||
offset += this.lYearDays(i)
|
||||
}
|
||||
var leap = 0; var isAdd = false
|
||||
for (var i = 1; i < m; i++) {
|
||||
leap = this.leapMonth(y)
|
||||
if (!isAdd) { // 处理闰月
|
||||
if (leap <= i && leap > 0) {
|
||||
offset += this.leapDays(y); isAdd = true
|
||||
}
|
||||
}
|
||||
offset += this.monthDays(y, i)
|
||||
}
|
||||
// 转换闰月农历 需补充该年闰月的前一个月的时差
|
||||
if (isLeapMonth) { offset += day }
|
||||
// 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
|
||||
var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
|
||||
var calObj = new Date((offset + d - 31) * 86400000 + stmap)
|
||||
var cY = calObj.getUTCFullYear()
|
||||
var cM = calObj.getUTCMonth() + 1
|
||||
var cD = calObj.getUTCDate()
|
||||
|
||||
return this.solar2lunar(cY, cM, cD)
|
||||
}
|
||||
}
|
||||
|
||||
export default calendar
|
||||
170
components/uni-calendar/uni-calendar-item.vue
Normal file
170
components/uni-calendar/uni-calendar-item.vue
Normal file
@ -0,0 +1,170 @@
|
||||
<template>
|
||||
<view class="uni-calendar-item__weeks-box" :class="{
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay) ,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}" @click="choiceDate(weeks)">
|
||||
<view class="uni-calendar-item__weeks-box-item">
|
||||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text>
|
||||
<text class="uni-calendar-item__weeks-box-text" :class="{
|
||||
'uni-calendar-item--isDay-text': weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.date}}</text>
|
||||
<text v-if="!lunar&&!weeks.extraInfo && weeks.isDay" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
}">今天</text>
|
||||
<text v-if="lunar&&!weeks.extraInfo" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.isDay?'今天': (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>
|
||||
<text v-if="weeks.extraInfo&&weeks.extraInfo.info" class="uni-calendar-item__weeks-lunar-text" :class="{
|
||||
'uni-calendar-item--extra':weeks.extraInfo.info,
|
||||
'uni-calendar-item--isDay-text':weeks.isDay,
|
||||
'uni-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay,
|
||||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay,
|
||||
'uni-calendar-item--before-checked':weeks.beforeMultiple,
|
||||
'uni-calendar-item--multiple': weeks.multiple,
|
||||
'uni-calendar-item--after-checked':weeks.afterMultiple,
|
||||
'uni-calendar-item--disable':weeks.disable,
|
||||
}">{{weeks.extraInfo.info}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
weeks: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
calendar: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
choiceDate(weeks) {
|
||||
this.$emit('change', weeks)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-calendar-item__weeks-box {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-lunar-text {
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-item {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
}
|
||||
|
||||
.uni-calendar-item__weeks-box-circle {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 8px;
|
||||
background-color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-calendar-item--disable {
|
||||
background-color: rgba(249, 249, 249, 0.3);
|
||||
color: #c0c0c0;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay-text {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--isDay {
|
||||
background-color: #007aff;
|
||||
opacity: 0.8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--extra {
|
||||
color: #dd524d;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--checked {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--multiple {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.uni-calendar-item--before-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar-item--after-checked {
|
||||
background-color: #ff5a5f;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
503
components/uni-calendar/uni-calendar.vue
Normal file
503
components/uni-calendar/uni-calendar.vue
Normal file
@ -0,0 +1,503 @@
|
||||
<template>
|
||||
<view class="uni-calendar">
|
||||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" @click="clean"></view>
|
||||
<view v-if="insert || show" class="uni-calendar__content" :class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow}">
|
||||
<view v-if="!insert" class="uni-calendar__header uni-calendar--fixed-top">
|
||||
<view class="uni-calendar__header-btn-box" @click="close">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">取消</text>
|
||||
</view>
|
||||
<view class="uni-calendar__header-btn-box" @click="confirm">
|
||||
<text class="uni-calendar__header-text uni-calendar--fixed-width">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__header">
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="pre">
|
||||
<view class="uni-calendar__header-btn uni-calendar--left"></view>
|
||||
</view>
|
||||
<picker mode="date" :value="date" fields="month" @change="bindDateChange">
|
||||
<text class="uni-calendar__header-text">{{ (nowDate.year||'') +'年'+( nowDate.month||'') +'月'}}</text>
|
||||
</picker>
|
||||
<view class="uni-calendar__header-btn-box" @click.stop="next">
|
||||
<view class="uni-calendar__header-btn uni-calendar--right"></view>
|
||||
</view>
|
||||
<text class="uni-calendar__backtoday" @click="backtoday">回到今天</text>
|
||||
|
||||
</view>
|
||||
<view class="uni-calendar__box">
|
||||
<view v-if="showMonth" class="uni-calendar__box-bg">
|
||||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks">
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">日</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">一</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">二</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">三</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">四</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">五</text>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks-day">
|
||||
<text class="uni-calendar__weeks-day-text">六</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex">
|
||||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex">
|
||||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" :selected="selected" :lunar="lunar" @change="choiceDate"></calendar-item>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Calendar from './util.js';
|
||||
import calendarItem from './uni-calendar-item.vue'
|
||||
/**
|
||||
* Calendar 日历
|
||||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56
|
||||
* @property {String} date 自定义当前时间,默认为今天
|
||||
* @property {Boolean} lunar 显示农历
|
||||
* @property {String} startDate 日期选择范围-开始日期
|
||||
* @property {String} endDate 日期选择范围-结束日期
|
||||
* @property {Boolean} range 范围选择
|
||||
* @property {Boolean} insert = [true|false] 插入模式,默认为false
|
||||
* @value true 弹窗模式
|
||||
* @value false 插入模式
|
||||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容
|
||||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]
|
||||
* @property {Boolean} showMonth 是否选择月份为背景
|
||||
* @event {Function} change 日期改变,`insert :ture` 时生效
|
||||
* @event {Function} confirm 确认选择`insert :false` 时生效
|
||||
* @event {Function} monthSwitch 切换月份时触发
|
||||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" />
|
||||
*/
|
||||
export default {
|
||||
components: {
|
||||
calendarItem
|
||||
},
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selected: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
lunar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
startDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
endDate: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
range: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
insert: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showMonth: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
clearDate: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
weeks: [],
|
||||
calendar: {},
|
||||
nowDate: '',
|
||||
aniMaskShow: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
date(newVal) {
|
||||
// this.cale.setDate(newVal)
|
||||
this.init(newVal)
|
||||
},
|
||||
startDate(val) {
|
||||
this.cale.resetSatrtDate(val)
|
||||
},
|
||||
endDate(val) {
|
||||
this.cale.resetEndDate(val)
|
||||
},
|
||||
selected(newVal) {
|
||||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal)
|
||||
this.weeks = this.cale.weeks
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取日历方法实例
|
||||
this.cale = new Calendar({
|
||||
// date: new Date(),
|
||||
selected: this.selected,
|
||||
startDate: this.startDate,
|
||||
endDate: this.endDate,
|
||||
range: this.range,
|
||||
})
|
||||
// 选中某一天
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
// this.setDay
|
||||
},
|
||||
methods: {
|
||||
// 取消穿透
|
||||
clean() {},
|
||||
bindDateChange(e) {
|
||||
const value = e.detail.value + '-1'
|
||||
console.log(this.cale.getDate(value));
|
||||
this.init(value)
|
||||
},
|
||||
/**
|
||||
* 初始化日期显示
|
||||
* @param {Object} date
|
||||
*/
|
||||
init(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.calendar = this.cale.getInfo(date)
|
||||
},
|
||||
/**
|
||||
* 打开日历弹窗
|
||||
*/
|
||||
open() {
|
||||
// 弹窗模式并且清理数据
|
||||
if (this.clearDate && !this.insert) {
|
||||
this.cale.cleanMultipleStatus()
|
||||
// this.cale.setDate(this.date)
|
||||
this.init(this.date)
|
||||
}
|
||||
this.show = true
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.aniMaskShow = true
|
||||
}, 50)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 关闭日历弹窗
|
||||
*/
|
||||
close() {
|
||||
this.aniMaskShow = false
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.show = false
|
||||
this.$emit('close')
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 确认按钮
|
||||
*/
|
||||
confirm() {
|
||||
this.setEmit('confirm')
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 变化触发
|
||||
*/
|
||||
change() {
|
||||
if (!this.insert) return
|
||||
this.setEmit('change')
|
||||
},
|
||||
/**
|
||||
* 选择月份触发
|
||||
*/
|
||||
monthSwitch() {
|
||||
let {
|
||||
year,
|
||||
month
|
||||
} = this.nowDate
|
||||
this.$emit('monthSwitch', {
|
||||
year,
|
||||
month: Number(month)
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 派发事件
|
||||
* @param {Object} name
|
||||
*/
|
||||
setEmit(name) {
|
||||
let {
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fullDate,
|
||||
lunar,
|
||||
extraInfo
|
||||
} = this.calendar
|
||||
this.$emit(name, {
|
||||
range: this.cale.multipleStatus,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
fulldate: fullDate,
|
||||
lunar,
|
||||
extraInfo: extraInfo || {}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 选择天触发
|
||||
* @param {Object} weeks
|
||||
*/
|
||||
choiceDate(weeks) {
|
||||
if (weeks.disable) return
|
||||
this.calendar = weeks
|
||||
// 设置多选
|
||||
this.cale.setMultiple(this.calendar.fullDate)
|
||||
this.weeks = this.cale.weeks
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 回到今天
|
||||
*/
|
||||
backtoday() {
|
||||
console.log(this.cale.getDate(new Date()).fullDate);
|
||||
let date = this.cale.getDate(new Date()).fullDate
|
||||
// this.cale.setDate(date)
|
||||
this.init(date)
|
||||
this.change()
|
||||
},
|
||||
/**
|
||||
* 上个月
|
||||
*/
|
||||
pre() {
|
||||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate
|
||||
this.setDate(preDate)
|
||||
this.monthSwitch()
|
||||
|
||||
},
|
||||
/**
|
||||
* 下个月
|
||||
*/
|
||||
next() {
|
||||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate
|
||||
this.setDate(nextDate)
|
||||
this.monthSwitch()
|
||||
},
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.cale.setDate(date)
|
||||
this.weeks = this.cale.weeks
|
||||
this.nowDate = this.cale.getInfo(date)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-calendar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-calendar__mask {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
opacity: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--mask-show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transform: translateY(460px);
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-calendar--ani-show {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-calendar__content {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-calendar__header {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
border-bottom-color: #e5e5e5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-top {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar--fixed-width {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__backtoday {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 25rpx;
|
||||
padding: 0 5px;
|
||||
padding-left: 10px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
border-top-left-radius: 25px;
|
||||
border-bottom-left-radius: 25px;
|
||||
color: #333;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-calendar__header-text {
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.uni-calendar__header-btn {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-left-color: #808080;
|
||||
border-left-style: solid;
|
||||
border-left-width: 2px;
|
||||
border-top-color: #555555;
|
||||
border-top-style: solid;
|
||||
border-top-width: 2px;
|
||||
}
|
||||
|
||||
.uni-calendar--left {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-calendar--right {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.uni-calendar__weeks {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
border-bottom-color: #F5F5F5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.uni-calendar__weeks-day-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-calendar__box {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-calendar__box-bg-text {
|
||||
font-size: 200px;
|
||||
font-weight: bold;
|
||||
color: #999;
|
||||
opacity: 0.1;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
352
components/uni-calendar/util.js
Normal file
352
components/uni-calendar/util.js
Normal file
@ -0,0 +1,352 @@
|
||||
import CALENDAR from './calendar.js'
|
||||
|
||||
class Calendar {
|
||||
constructor({
|
||||
date,
|
||||
selected,
|
||||
startDate,
|
||||
endDate,
|
||||
range
|
||||
} = {}) {
|
||||
// 当前日期
|
||||
this.date = this.getDate(new Date()) // 当前初入日期
|
||||
// 打点信息
|
||||
this.selected = selected || [];
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
this.range = range
|
||||
// 多选状态
|
||||
this.cleanMultipleStatus()
|
||||
// 每周日期
|
||||
this.weeks = {}
|
||||
// this._getWeek(this.date.fullDate)
|
||||
}
|
||||
/**
|
||||
* 设置日期
|
||||
* @param {Object} date
|
||||
*/
|
||||
setDate(date) {
|
||||
this.selectDate = this.getDate(date)
|
||||
this._getWeek(this.selectDate.fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理多选状态
|
||||
*/
|
||||
cleanMultipleStatus() {
|
||||
this.multipleStatus = {
|
||||
before: '',
|
||||
after: '',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置开始日期
|
||||
*/
|
||||
resetSatrtDate(startDate) {
|
||||
// 范围开始
|
||||
this.startDate = startDate
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置结束日期
|
||||
*/
|
||||
resetEndDate(endDate) {
|
||||
// 范围结束
|
||||
this.endDate = endDate
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取任意时间
|
||||
*/
|
||||
getDate(date, AddDayCount = 0, str = 'day') {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
if (typeof date !== 'object') {
|
||||
date = date.replace(/-/g, '/')
|
||||
}
|
||||
const dd = new Date(date)
|
||||
switch (str) {
|
||||
case 'day':
|
||||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
case 'month':
|
||||
if (dd.getDate() === 31) {
|
||||
dd.setDate(dd.getDate() + AddDayCount)
|
||||
} else {
|
||||
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
}
|
||||
break
|
||||
case 'year':
|
||||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
||||
break
|
||||
}
|
||||
const y = dd.getFullYear()
|
||||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
||||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
||||
return {
|
||||
fullDate: y + '-' + m + '-' + d,
|
||||
year: y,
|
||||
month: m,
|
||||
date: d,
|
||||
day: dd.getDay()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取上月剩余天数
|
||||
*/
|
||||
_getLastMonthDays(firstDay, full) {
|
||||
let dateArr = []
|
||||
for (let i = firstDay; i > 0; i--) {
|
||||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()
|
||||
dateArr.push({
|
||||
date: beforeDate,
|
||||
month: full.month - 1,
|
||||
lunar: this.getlunar(full.year, full.month - 1, beforeDate),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取本月天数
|
||||
*/
|
||||
_currentMonthDys(dateData, full) {
|
||||
let dateArr = []
|
||||
let fullDate = this.date.fullDate
|
||||
for (let i = 1; i <= dateData; i++) {
|
||||
let isinfo = false
|
||||
let nowDate = full.year + '-' + (full.month < 10 ?
|
||||
full.month : full.month) + '-' + (i < 10 ?
|
||||
'0' + i : i)
|
||||
// 是否今天
|
||||
let isDay = fullDate === nowDate
|
||||
// 获取打点信息
|
||||
let info = this.selected && this.selected.find((item) => {
|
||||
if (this.dateEqual(nowDate, item.date)) {
|
||||
return item
|
||||
}
|
||||
})
|
||||
|
||||
// 日期禁用
|
||||
let disableBefore = true
|
||||
let disableAfter = true
|
||||
if (this.startDate) {
|
||||
let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
||||
disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
||||
}
|
||||
|
||||
if (this.endDate) {
|
||||
let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
||||
disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
||||
}
|
||||
let multiples = this.multipleStatus.data
|
||||
let checked = false
|
||||
let multiplesStatus = -1
|
||||
if (this.range) {
|
||||
if (multiples) {
|
||||
multiplesStatus = multiples.findIndex((item) => {
|
||||
return this.dateEqual(item, nowDate)
|
||||
})
|
||||
}
|
||||
if (multiplesStatus !== -1) {
|
||||
checked = true
|
||||
}
|
||||
}
|
||||
let data = {
|
||||
fullDate: nowDate,
|
||||
year: full.year,
|
||||
date: i,
|
||||
multiple: this.range ? checked : false,
|
||||
beforeMultiple: this.dateEqual(this.multipleStatus.before, nowDate),
|
||||
afterMultiple: this.dateEqual(this.multipleStatus.after, nowDate),
|
||||
month: full.month,
|
||||
lunar: this.getlunar(full.year, full.month, i),
|
||||
disable: !disableBefore || !disableAfter,
|
||||
isDay
|
||||
}
|
||||
if (info) {
|
||||
data.extraInfo = info
|
||||
}
|
||||
|
||||
dateArr.push(data)
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
/**
|
||||
* 获取下月天数
|
||||
*/
|
||||
_getNextMonthDays(surplus, full) {
|
||||
let dateArr = []
|
||||
for (let i = 1; i < surplus + 1; i++) {
|
||||
dateArr.push({
|
||||
date: i,
|
||||
month: Number(full.month) + 1,
|
||||
lunar: this.getlunar(full.year, Number(full.month) + 1, i),
|
||||
disable: true
|
||||
})
|
||||
}
|
||||
return dateArr
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前日期详情
|
||||
* @param {Object} date
|
||||
*/
|
||||
getInfo(date) {
|
||||
if (!date) {
|
||||
date = new Date()
|
||||
}
|
||||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)
|
||||
return dateInfo
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间大小
|
||||
*/
|
||||
dateCompare(startDate, endDate) {
|
||||
// 计算截止时间
|
||||
startDate = new Date(startDate.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
endDate = new Date(endDate.replace('-', '/').replace('-', '/'))
|
||||
if (startDate <= endDate) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较时间是否相等
|
||||
*/
|
||||
dateEqual(before, after) {
|
||||
// 计算截止时间
|
||||
before = new Date(before.replace('-', '/').replace('-', '/'))
|
||||
// 计算详细项的截止时间
|
||||
after = new Date(after.replace('-', '/').replace('-', '/'))
|
||||
if (before.getTime() - after.getTime() === 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取日期范围内所有日期
|
||||
* @param {Object} begin
|
||||
* @param {Object} end
|
||||
*/
|
||||
geDateAll(begin, end) {
|
||||
var arr = []
|
||||
var ab = begin.split('-')
|
||||
var ae = end.split('-')
|
||||
var db = new Date()
|
||||
db.setFullYear(ab[0], ab[1] - 1, ab[2])
|
||||
var de = new Date()
|
||||
de.setFullYear(ae[0], ae[1] - 1, ae[2])
|
||||
var unixDb = db.getTime() - 24 * 60 * 60 * 1000
|
||||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000
|
||||
for (var k = unixDb; k <= unixDe;) {
|
||||
k = k + 24 * 60 * 60 * 1000
|
||||
arr.push(this.getDate(new Date(parseInt(k))).fullDate)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
/**
|
||||
* 计算阴历日期显示
|
||||
*/
|
||||
getlunar(year, month, date) {
|
||||
return CALENDAR.solar2lunar(year, month, date)
|
||||
}
|
||||
/**
|
||||
* 设置打点
|
||||
*/
|
||||
setSelectInfo(data, value) {
|
||||
this.selected = value
|
||||
this._getWeek(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取多选状态
|
||||
*/
|
||||
setMultiple(fullDate) {
|
||||
let {
|
||||
before,
|
||||
after
|
||||
} = this.multipleStatus
|
||||
|
||||
if (!this.range) return
|
||||
if (before && after) {
|
||||
this.multipleStatus.before = ''
|
||||
this.multipleStatus.after = ''
|
||||
this.multipleStatus.data = []
|
||||
} else {
|
||||
if (!before) {
|
||||
this.multipleStatus.before = fullDate
|
||||
} else {
|
||||
this.multipleStatus.after = fullDate
|
||||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after);
|
||||
} else {
|
||||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before);
|
||||
}
|
||||
}
|
||||
}
|
||||
this._getWeek(fullDate)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每周数据
|
||||
* @param {Object} dateData
|
||||
*/
|
||||
_getWeek(dateData) {
|
||||
const {
|
||||
fullDate,
|
||||
year,
|
||||
month,
|
||||
date,
|
||||
day
|
||||
} = this.getDate(dateData)
|
||||
let firstDay = new Date(year, month - 1, 1).getDay()
|
||||
let currentDay = new Date(year, month, 0).getDate()
|
||||
let dates = {
|
||||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
||||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
||||
nextMonthDays: [], // 下个月开始几天
|
||||
weeks: []
|
||||
}
|
||||
let canlender = []
|
||||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)
|
||||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))
|
||||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)
|
||||
let weeks = {}
|
||||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
||||
for (let i = 0; i < canlender.length; i++) {
|
||||
if (i % 7 === 0) {
|
||||
weeks[parseInt(i / 7)] = new Array(7)
|
||||
}
|
||||
weeks[parseInt(i / 7)][i % 7] = canlender[i]
|
||||
}
|
||||
this.canlender = canlender
|
||||
this.weeks = weeks
|
||||
}
|
||||
|
||||
//静态方法
|
||||
// static init(date) {
|
||||
// if (!this.instance) {
|
||||
// this.instance = new Calendar(date);
|
||||
// }
|
||||
// return this.instance;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
export default Calendar
|
||||
401
components/uni-card/uni-card.vue
Normal file
401
components/uni-card/uni-card.vue
Normal file
@ -0,0 +1,401 @@
|
||||
<template>
|
||||
<view class="uni-card uni-border" :class="{ 'uni-card--full': isFull === true || isFull === 'true', 'uni-card--shadow': isShadow === true || isShadow === 'true'}">
|
||||
<!-- 基础 -->
|
||||
<view v-if="mode === 'basic' && title" class="uni-card__header uni-border-bottom" @click.stop="onClick">
|
||||
<view v-if="thumbnail" class="uni-card__header-extra-img-view">
|
||||
<image :src="thumbnail" class="uni-card__header-extra-img" />
|
||||
</view>
|
||||
<text class="uni-card__header-title-text">{{ title }}</text>
|
||||
<text v-if="extra" class="uni-card__header-extra-text">{{ extra }}</text>
|
||||
</view>
|
||||
<!-- 标题 -->
|
||||
<view v-if="mode === 'title'" class="uni-card__title uni-border-bottom" @click.stop="onClick">
|
||||
<view class="uni-card__title-box">
|
||||
<view class="uni-card__title-header">
|
||||
<image class="uni-card__title-header-image" :src="thumbnail" mode="scaleToFill" />
|
||||
</view>
|
||||
<view class="uni-card__title-content">
|
||||
<text class="uni-card__title-content-title uni-ellipsis">{{ title }}</text>
|
||||
<text class="uni-card__title-content-extra uni-ellipsis">{{ subTitle }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="extra">
|
||||
<text class="uni-card__header-extra-text">{{ extra }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 图文 -->
|
||||
<view v-if="mode === 'style'" class="uni-card__thumbnailimage" @click.stop="onClick">
|
||||
<view class="uni-card__thumbnailimage-box">
|
||||
<image class="uni-card__thumbnailimage-image" :src="thumbnail" mode="aspectFill" />
|
||||
</view>
|
||||
<view v-if="title" class="uni-card__thumbnailimage-title"><text class="uni-card__thumbnailimage-title-text">{{ title }}</text></view>
|
||||
</view>
|
||||
<!-- 内容 -->
|
||||
<view class="uni-card__content uni-card__content--pd" @click.stop="onClick">
|
||||
<view v-if="mode === 'style' && extra" class=""><text class="uni-card__content-extra">{{ extra }}</text></view>
|
||||
<slot />
|
||||
</view>
|
||||
<!-- 底部 -->
|
||||
<view v-if="note" class="uni-card__footer uni-border-top">
|
||||
<slot name="footer">
|
||||
<text class="uni-card__footer-text">{{ note }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Card 卡片
|
||||
* @description 卡片视图组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=22
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} subTitle 副标题(仅仅mode=title下生效)
|
||||
* @property {String} extra 标题额外信息
|
||||
* @property {String} note 标题左侧缩略图
|
||||
* @property {String} thumbnail 底部信息
|
||||
* @property {String} mode = [basic|style|title] 卡片模式
|
||||
* @value basic 基础卡片
|
||||
* @value style 图文卡片
|
||||
* @value title 标题卡片
|
||||
* @property {Boolean} isFull = [true | false] 卡片内容是否通栏,为 true 时将去除padding值
|
||||
* @property {Boolean} isShadow = [true | false] 卡片内容是否开启阴影
|
||||
* @event {Function} click 点击 Card 触发事件
|
||||
* @example <uni-card title="标题文字" thumbnail="xxx.jpg" extra="额外信息" note="Tips">内容主体,可自定义内容及样式</uni-card>
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCard',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
extra: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumbnail: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'basic'
|
||||
},
|
||||
isFull: {
|
||||
// 内容区域是否通栏
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isShadow: {
|
||||
// 是否开启阴影
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-card {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex: 1;
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||
/* #endif */
|
||||
margin: 12px 15px;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-border {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-color: #e5e5e5;
|
||||
border-style: solid;
|
||||
border-width: 0.5px;
|
||||
/* #endif */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-border:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 10px;
|
||||
box-sizing: border-box;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: left top;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-border-bottom {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-bottom-color: #e5e5e5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0.5px;
|
||||
/* #endif */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-border-bottom:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
box-sizing: border-box;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: left top;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-border-top {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-border-top:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
box-sizing: border-box;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: left top;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-card__thumbnailimage {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 150px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__thumbnailimage-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__thumbnailimage-image {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-card__thumbnailimage-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
flex-direction: row;
|
||||
padding: 8px 12px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.uni-card__thumbnailimage-title-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-card__title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-card__title-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__title-header {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.uni-card__title-header-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.uni-card__title-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
height: 40px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-card__title-content-title {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-card__title-content-extra {
|
||||
font-size: 12px;
|
||||
line-height: 27px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-card__header {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-card__header-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
margin-right: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-card__header-title-text {
|
||||
font-size: 16px;
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-card__header-extra-img {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.uni-card__header-extra-text {
|
||||
flex: 1;
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-card__content {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-card__content--pd {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.uni-card__content-extra {
|
||||
font-size: 14px;
|
||||
padding-bottom: 10px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.uni-card__footer {
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.uni-card__footer-text {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-card--shadow {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-shadow: 0px 0px 5px 1px rgba(0, 0, 0, 0.1);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-card--full {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-card--full:after {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-ellipsis {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
2966
components/uni-col/uni-col.vue
Normal file
2966
components/uni-col/uni-col.vue
Normal file
File diff suppressed because it is too large
Load Diff
216
components/uni-collapse-item/uni-collapse-item.vue
Normal file
216
components/uni-collapse-item/uni-collapse-item.vue
Normal file
@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<view :class="{ 'uni-collapse-cell--disabled': disabled,'uni-collapse-cell--notdisabled': !disabled, 'uni-collapse-cell--open': isOpen,'uni-collapse-cell--hide':!isOpen }" class="uni-collapse-cell">
|
||||
<view :class="{ 'uni-collapse-cell--disabled': disabled}" class="uni-collapse-cell__title" @click="onClick">
|
||||
<image v-if="thumb" :src="thumb" class="uni-collapse-cell__title-img" />
|
||||
<text class="uni-collapse-cell__title-text">{{ title }}</text>
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<view :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__title-arrow">
|
||||
<uni-icons color="#bbb" size="20" type="arrowdown" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<uni-icons :class="{ 'uni-collapse-cell__title-arrow-active': isOpen, 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__title-arrow" color="#bbb" size="20" type="arrowdown" />
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view :class="{'uni-collapse-cell__content--hide':!isOpen}" class="uni-collapse-cell__content">
|
||||
<view :class="{ 'uni-collapse-cell--animation': showAnimation === true }" class="uni-collapse-cell__wrapper" :style="{'transform':isOpen?'translateY(0)':'translateY(-50%)','-webkit-transform':isOpen?'translateY(0)':'translateY(-50%)'}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
/**
|
||||
* CollapseItem 折叠面板子组件
|
||||
* @description 折叠面板子组件
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} thumb 标题左侧缩略图
|
||||
* @property {Boolean} disabled = [true|false] 是否展开面板
|
||||
* @property {Boolean} showAnimation = [true|false] 开启动画
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCollapseItem',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
// 列表标题
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
name: {
|
||||
// 唯一标识符
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
// 是否禁用
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showAnimation: {
|
||||
// 是否显示动画
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
open: {
|
||||
// 是否展开
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
thumb: {
|
||||
// 缩略图
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpen: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
open(val) {
|
||||
this.isOpen = val
|
||||
}
|
||||
},
|
||||
inject: ['collapse'],
|
||||
created() {
|
||||
this.isOpen = this.open
|
||||
this.nameSync = this.name ? this.name : this.collapse.childrens.length
|
||||
this.collapse.childrens.push(this)
|
||||
if (String(this.collapse.accordion) === 'true') {
|
||||
if (this.isOpen) {
|
||||
let lastEl = this.collapse.childrens[this.collapse.childrens.length - 2]
|
||||
if (lastEl) {
|
||||
this.collapse.childrens[this.collapse.childrens.length - 2].isOpen = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
if (String(this.collapse.accordion) === 'true') {
|
||||
this.collapse.childrens.forEach(vm => {
|
||||
if (vm === this) {
|
||||
return
|
||||
}
|
||||
vm.isOpen = false
|
||||
})
|
||||
}
|
||||
this.isOpen = !this.isOpen
|
||||
this.collapse.onChange && this.collapse.onChange()
|
||||
this.$forceUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-collapse-cell {
|
||||
flex-direction: column;
|
||||
border-color: #e5e5e5;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.uni-collapse-cell--hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-collapse-cell--open {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-collapse-cell--disabled {
|
||||
background-color: #f1f1f1;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed !important;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-collapse-cell--hide {
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.uni-collapse-cell--animation {
|
||||
transition-property: transform;
|
||||
transition-duration: 0.3s;
|
||||
transition-timing-function: ease;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title {
|
||||
padding: 12px 12px;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
height: 48px;
|
||||
line-height: 24px;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title:active {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title-img {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title-arrow {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(0deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title-arrow-active {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.uni-collapse-cell__title-text {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
color: inherit;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-collapse-cell__content--hide {
|
||||
height: 0px;
|
||||
line-height: 0px;
|
||||
}
|
||||
</style>
|
||||
59
components/uni-collapse/uni-collapse.vue
Normal file
59
components/uni-collapse/uni-collapse.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<view class="uni-collapse">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Collapse 折叠面板
|
||||
* @description 展示可以折叠 / 展开的内容区域
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=23
|
||||
* @property {Boolean} accordion = [true|false] 是否开启手风琴效果是否开启手风琴效果
|
||||
* @event {Function} change 切换面板时触发,activeNames(Array):展开状态的uniCollapseItem的 name 值
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCollapse',
|
||||
props: {
|
||||
accordion: {
|
||||
// 是否开启手风琴效果
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
collapse: this
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.childrens = []
|
||||
},
|
||||
methods: {
|
||||
onChange() {
|
||||
let activeItem = []
|
||||
this.childrens.forEach((vm, index) => {
|
||||
if (vm.isOpen) {
|
||||
activeItem.push(vm.nameSync)
|
||||
}
|
||||
})
|
||||
this.$emit('change', activeItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-collapse {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
212
components/uni-combox/uni-combox.vue
Normal file
212
components/uni-combox/uni-combox.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<view class="uni-combox">
|
||||
<view v-if="label" class="uni-combox__label" :style="labelStyle">
|
||||
<text>{{label}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__input-box">
|
||||
<input class="uni-combox__input" type="text" :placeholder="placeholder" v-model="inputVal" @input="onInput" @focus="onFocus" @blur="onBlur" />
|
||||
<uni-icons class="uni-combox__input-arrow" type="arrowdown" size="14" @click="toggleSelector"></uni-icons>
|
||||
<view class="uni-combox__selector" v-if="showSelector">
|
||||
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
|
||||
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
|
||||
<text>{{emptyTips}}</text>
|
||||
</view>
|
||||
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" @click="onSelectorClick(index)">
|
||||
<text>{{item}}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
/**
|
||||
* Combox 组合输入框
|
||||
* @description 组合输入框一般用于既可以输入也可以选择的场景
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=1261
|
||||
* @property {String} label 左侧文字
|
||||
* @property {String} labelWidth 左侧内容宽度
|
||||
* @property {String} placeholder 输入框占位符
|
||||
* @property {Array} candidates 候选项列表
|
||||
* @property {String} emptyTips 筛选结果为空时显示的文字
|
||||
* @property {String} value 组合框的值
|
||||
*/
|
||||
export default {
|
||||
name: 'uniCombox',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
labelWidth: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
candidates: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
emptyTips: {
|
||||
type: String,
|
||||
default: '无匹配项'
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSelector: false,
|
||||
inputVal: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
labelStyle() {
|
||||
if (this.labelWidth === 'auto') {
|
||||
return {}
|
||||
}
|
||||
return {
|
||||
width: this.labelWidth
|
||||
}
|
||||
},
|
||||
filterCandidates() {
|
||||
return this.candidates.filter((item) => {
|
||||
return item.toString().indexOf(this.inputVal) > -1
|
||||
})
|
||||
},
|
||||
filterCandidatesLength() {
|
||||
return this.filterCandidates.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newVal) {
|
||||
this.inputVal = newVal
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleSelector() {
|
||||
this.showSelector = !this.showSelector
|
||||
},
|
||||
onFocus() {
|
||||
this.showSelector = true
|
||||
},
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
this.showSelector = false
|
||||
}, 153)
|
||||
},
|
||||
onSelectorClick(index) {
|
||||
this.inputVal = this.filterCandidates[index]
|
||||
this.showSelector = false
|
||||
this.$emit('input', this.inputVal)
|
||||
},
|
||||
onInput() {
|
||||
setTimeout(() => {
|
||||
this.$emit('input', this.inputVal)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-combox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
height: 40px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-combox__label {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
padding-right: 10px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.uni-combox__input-box {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-combox__input {
|
||||
flex: 1;
|
||||
font-size: 16px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.uni-combox__input-arrow {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-combox__selector {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background-color: #FFFFFF;
|
||||
border-radius: 6px;
|
||||
box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.uni-combox__selector-scroll {
|
||||
max-height: 200px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.uni-combox__selector::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-bottom: solid 6px #FFFFFF;
|
||||
border-right: solid 6px transparent;
|
||||
border-left: solid 6px transparent;
|
||||
left: 50%;
|
||||
top: -6px;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty,
|
||||
.uni-combox__selector-item {
|
||||
/* #ifdef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
border-bottom: solid 1px #DDDDDD;
|
||||
margin: 0px 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.uni-combox__selector-empty:last-child,
|
||||
.uni-combox__selector-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
</style>
|
||||
208
components/uni-countdown/uni-countdown.vue
Normal file
208
components/uni-countdown/uni-countdown.vue
Normal file
@ -0,0 +1,208 @@
|
||||
<template>
|
||||
<view class="uni-countdown">
|
||||
<text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ d }}</text>
|
||||
<text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text>
|
||||
<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ h }}</text>
|
||||
<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text>
|
||||
<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ i }}</text>
|
||||
<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text>
|
||||
<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ s }}</text>
|
||||
<text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* Countdown 倒计时
|
||||
* @description 倒计时组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=25
|
||||
* @property {String} backgroundColor 背景色
|
||||
* @property {String} color 文字颜色
|
||||
* @property {Number} day 天数
|
||||
* @property {Number} hour 小时
|
||||
* @property {Number} minute 分钟
|
||||
* @property {Number} second 秒
|
||||
* @property {Number} timestamp 时间戳
|
||||
* @property {Boolean} showDay = [true|false] 是否显示天数
|
||||
* @property {Boolean} showColon = [true|false] 是否以冒号为分隔符
|
||||
* @property {String} splitorColor 分割符号颜色
|
||||
* @event {Function} timeup 倒计时时间到触发事件
|
||||
* @example <uni-countdown :day="1" :hour="1" :minute="12" :second="40"></uni-countdown>
|
||||
*/
|
||||
export default {
|
||||
name: 'UniCountdown',
|
||||
props: {
|
||||
showDay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showColon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#FFFFFF'
|
||||
},
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
splitorColor: {
|
||||
type: String,
|
||||
default: '#000000'
|
||||
},
|
||||
day: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
hour: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
minute: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
second: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
timestamp: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
timer: null,
|
||||
syncFlag: false,
|
||||
d: '00',
|
||||
h: '00',
|
||||
i: '00',
|
||||
s: '00',
|
||||
leftTime: 0,
|
||||
seconds: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
day(val) {
|
||||
this.changeFlag()
|
||||
},
|
||||
hour(val) {
|
||||
this.changeFlag()
|
||||
},
|
||||
minute(val) {
|
||||
this.changeFlag()
|
||||
},
|
||||
second(val) {
|
||||
this.changeFlag()
|
||||
}
|
||||
},
|
||||
created: function(e) {
|
||||
this.startData();
|
||||
},
|
||||
beforeDestroy() {
|
||||
clearInterval(this.timer)
|
||||
},
|
||||
methods: {
|
||||
toSeconds(timestamp, day, hours, minutes, seconds) {
|
||||
if (timestamp) {
|
||||
return timestamp - parseInt(new Date().getTime() / 1000, 10)
|
||||
}
|
||||
return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
|
||||
},
|
||||
timeUp() {
|
||||
clearInterval(this.timer)
|
||||
this.$emit('timeup')
|
||||
},
|
||||
countDown() {
|
||||
let seconds = this.seconds
|
||||
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||||
if (seconds > 0) {
|
||||
day = Math.floor(seconds / (60 * 60 * 24))
|
||||
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||||
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
|
||||
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
|
||||
} else {
|
||||
this.timeUp()
|
||||
}
|
||||
if (day < 10) {
|
||||
day = '0' + day
|
||||
}
|
||||
if (hour < 10) {
|
||||
hour = '0' + hour
|
||||
}
|
||||
if (minute < 10) {
|
||||
minute = '0' + minute
|
||||
}
|
||||
if (second < 10) {
|
||||
second = '0' + second
|
||||
}
|
||||
this.d = day
|
||||
this.h = hour
|
||||
this.i = minute
|
||||
this.s = second
|
||||
},
|
||||
startData() {
|
||||
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||
if (this.seconds <= 0) {
|
||||
return
|
||||
}
|
||||
this.countDown()
|
||||
this.timer = setInterval(() => {
|
||||
this.seconds--
|
||||
if (this.seconds < 0) {
|
||||
this.timeUp()
|
||||
return
|
||||
}
|
||||
this.countDown()
|
||||
}, 1000)
|
||||
},
|
||||
changeFlag() {
|
||||
if (!this.syncFlag) {
|
||||
this.seconds = this.toSeconds(this.timestamp, this.day, this.hour, this.minute, this.second)
|
||||
this.startData();
|
||||
this.syncFlag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-countdown {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
padding: 2rpx 0;
|
||||
}
|
||||
|
||||
.uni-countdown__splitor {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
line-height: 48rpx;
|
||||
padding: 5rpx;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-countdown__number {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 52rpx;
|
||||
height: 48rpx;
|
||||
line-height: 48rpx;
|
||||
margin: 5rpx;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
316
components/uni-data-checkbox/clientdb.js
Normal file
316
components/uni-data-checkbox/clientdb.js
Normal file
@ -0,0 +1,316 @@
|
||||
|
||||
const events = {
|
||||
load: 'load',
|
||||
error: 'error'
|
||||
}
|
||||
const pageMode = {
|
||||
add: 'add',
|
||||
replace: 'replace'
|
||||
}
|
||||
|
||||
const attrs = [
|
||||
'pageCurrent',
|
||||
'pageSize',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'getcount',
|
||||
'orderby',
|
||||
'where'
|
||||
]
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listData: this.getone ? {} : [],
|
||||
paginationInternal: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
},
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let db = null;
|
||||
let dbCmd = null;
|
||||
|
||||
if(this.collection){
|
||||
this.db = uniCloud.database();
|
||||
this.dbCmd = this.db.command;
|
||||
}
|
||||
|
||||
this._isEnded = false
|
||||
|
||||
this.$watch(() => {
|
||||
var al = []
|
||||
attrs.forEach(key => {
|
||||
al.push(this[key])
|
||||
})
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
this.paginationInternal.pageSize = this.pageSize
|
||||
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needReset) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.paginationInternal.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this._execLoadData()
|
||||
})
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList = []
|
||||
if (!window.unidev) {
|
||||
window.unidev = {
|
||||
clientDB: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
unidev.clientDB.data.push(this._debugDataList)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
let changeName
|
||||
let events = this.$scope.dataset.eventOpts
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
let event = events[i]
|
||||
if (event[0].includes('^load')) {
|
||||
changeName = event[1][0][0]
|
||||
}
|
||||
}
|
||||
if (changeName) {
|
||||
let parent = this.$parent
|
||||
let maxDepth = 16
|
||||
this._changeDataFunction = null
|
||||
while (parent && maxDepth > 0) {
|
||||
let fun = parent[changeName]
|
||||
if (fun && typeof fun === 'function') {
|
||||
this._changeDataFunction = fun
|
||||
maxDepth = 0
|
||||
break
|
||||
}
|
||||
parent = parent.$parent
|
||||
maxDepth--;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// if (!this.manual) {
|
||||
// this.loadData()
|
||||
// }
|
||||
},
|
||||
// #ifdef H5
|
||||
beforeDestroy() {
|
||||
if (process.env.NODE_ENV === 'development' && window.unidev) {
|
||||
var cd = this._debugDataList
|
||||
var dl = unidev.clientDB.data
|
||||
for (var i = dl.length - 1; i >= 0; i--) {
|
||||
if (dl[i] === cd) {
|
||||
dl.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
loadData(args1, args2) {
|
||||
let callback = null
|
||||
if (typeof args1 === 'object') {
|
||||
if (args1.clear) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (args1.current !== undefined) {
|
||||
this.paginationInternal.current = args1.current
|
||||
}
|
||||
if (typeof args2 === 'function') {
|
||||
callback = args2
|
||||
}
|
||||
} else if (typeof args1 === 'function') {
|
||||
callback = args1
|
||||
}
|
||||
|
||||
this._execLoadData(callback)
|
||||
},
|
||||
loadMore() {
|
||||
if (this._isEnded) {
|
||||
return
|
||||
}
|
||||
this._execLoadData()
|
||||
},
|
||||
refresh() {
|
||||
this.clear()
|
||||
this._execLoadData()
|
||||
},
|
||||
clear() {
|
||||
this._isEnded = false
|
||||
this.listData = []
|
||||
},
|
||||
reset() {
|
||||
this.paginationInternal.current = 1
|
||||
},
|
||||
remove(id, {
|
||||
action,
|
||||
callback,
|
||||
confirmTitle,
|
||||
confirmContent
|
||||
} = {}) {
|
||||
if (!id || !id.length) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: confirmTitle || '提示',
|
||||
content: confirmContent || '是否删除该数据',
|
||||
showCancel: true,
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
this._execRemove(id, action, callback)
|
||||
}
|
||||
})
|
||||
},
|
||||
_execLoadData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
this._getExec().then((res) => {
|
||||
this.loading = false
|
||||
const {
|
||||
data,
|
||||
count
|
||||
} = res.result
|
||||
this._isEnded = data.length < this.pageSize
|
||||
|
||||
callback && callback(data, this._isEnded)
|
||||
this._dispatchEvent(events.load, data)
|
||||
|
||||
if (this.getone) {
|
||||
this.listData = data.length ? data[0] : undefined
|
||||
} else if (this.pageData === pageMode.add) {
|
||||
this.listData.push(...data)
|
||||
if (this.listData.length) {
|
||||
this.paginationInternal.current++
|
||||
}
|
||||
} else if (this.pageData === pageMode.replace) {
|
||||
this.listData = data
|
||||
this.paginationInternal.count = count
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList.length = 0
|
||||
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
|
||||
}
|
||||
// #endif
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
callback && callback()
|
||||
this.$emit(events.error, err)
|
||||
})
|
||||
},
|
||||
_getExec() {
|
||||
let exec = this.db
|
||||
if (this.action) {
|
||||
exec = exec.action(this.action)
|
||||
}
|
||||
|
||||
exec = exec.collection(this.collection)
|
||||
|
||||
if (!(!this.where || !Object.keys(this.where).length)) {
|
||||
exec = exec.where(this.where)
|
||||
}
|
||||
if (this.field) {
|
||||
exec = exec.field(this.field)
|
||||
}
|
||||
if (this.orderby) {
|
||||
exec = exec.orderBy(this.orderby)
|
||||
}
|
||||
|
||||
const {
|
||||
current,
|
||||
size
|
||||
} = this.paginationInternal
|
||||
exec = exec.skip(size * (current - 1)).limit(size).get({
|
||||
getCount: this.getcount
|
||||
})
|
||||
|
||||
return exec
|
||||
},
|
||||
_execRemove(id, action, callback) {
|
||||
if (!this.collection || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Array.isArray(id) ? id : [id]
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
mask: true
|
||||
})
|
||||
|
||||
let exec = this.db
|
||||
if (action) {
|
||||
exec = exec.action(action)
|
||||
}
|
||||
|
||||
exec.collection(this.collection).where({
|
||||
_id: dbCmd.in(ids)
|
||||
}).remove().then((res) => {
|
||||
callback && callback(res.result)
|
||||
if (this.pageData === pageMode.replace) {
|
||||
this.refresh()
|
||||
} else {
|
||||
this.removeData(ids)
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showModal({
|
||||
content: err.message,
|
||||
showCancel: false
|
||||
})
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
removeData(ids) {
|
||||
let il = ids.slice(0)
|
||||
let dl = this.listData
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
let index = il.indexOf(dl[i]._id)
|
||||
if (index >= 0) {
|
||||
dl.splice(i, 1)
|
||||
il.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
_dispatchEvent(type, data) {
|
||||
if (this._changeDataFunction) {
|
||||
this._changeDataFunction(data, this._isEnded)
|
||||
} else {
|
||||
this.$emit(type, data, this._isEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
821
components/uni-data-checkbox/uni-data-checkbox.vue
Executable file
821
components/uni-data-checkbox/uni-data-checkbox.vue
Executable file
@ -0,0 +1,821 @@
|
||||
<template>
|
||||
<view class="uni-data-checklist">
|
||||
<template v-if="loading">
|
||||
<view class="uni-data-loading">
|
||||
<uni-load-more status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
|
||||
<label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList" :key="index">
|
||||
<checkbox class="hidden" hidden :disabled="!!item.disabled" :value="item.value+''" :checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner" :class="item.checkboxBgClass" :style="[item.styleIcon]">
|
||||
<view class="checkbox__inner-icon" :class="item.checkboxClass"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleBackgroud]"></view>
|
||||
</view>
|
||||
</label>
|
||||
</checkbox-group>
|
||||
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
|
||||
<label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList" :key="index">
|
||||
<radio hidden :disabled="item.disabled" :value="item.value+''" :checked="item.selected" />
|
||||
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner" :class="item.checkboxBgClass" :style="[item.styleBackgroud]">
|
||||
<view class="radio__inner-icon" :class="item.checkboxClass" :style="[item.styleIcon]"></view>
|
||||
</view>
|
||||
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
|
||||
<text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text>
|
||||
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleRightIcon]"></view>
|
||||
</view>
|
||||
</label>
|
||||
</radio-group>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* DataChecklist 数据选择器
|
||||
* @description 通过数据渲染 checkbox 和 radio
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
|
||||
* @property {String} mode = [default| list | button | tag] 显示模式
|
||||
* @value default 默认横排模式
|
||||
* @value list 列表模式
|
||||
* @value button 按钮模式
|
||||
* @value tag 标签模式
|
||||
* @property {Boolean} multiple = [true|false] 是否多选
|
||||
* @property {Array|String|Number} value 默认值
|
||||
* @property {Array} localdata 本地数据 ,格式 [{text:'',value:''}]
|
||||
* @property {Number|String} min 最小选择个数 ,multiple为true时生效
|
||||
* @property {Number|String} max 最大选择个数 ,multiple为true时生效
|
||||
* @property {Boolean} wrap 是否换行显示
|
||||
* @property {String} icon = [left|right] list 列表模式下icon显示位置
|
||||
* @property {Boolean} selectedColor 选中颜色
|
||||
* @property {Boolean} selectedTextColor 选中文本颜色,如不填写则自动显示
|
||||
* @value left 左侧显示
|
||||
* @value right 右侧显示
|
||||
* @event {Function} change 选中发生变化触发
|
||||
*/
|
||||
|
||||
import clientdb from './clientdb.js'
|
||||
export default {
|
||||
name: 'uniDataChecklist',
|
||||
mixins: [clientdb],
|
||||
props: {
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
min: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
max: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
wrap: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
selectedColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
selectedTextColor: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// clientDB 相关
|
||||
options: {
|
||||
type: [Object, Array],
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
getcount: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
getone: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
localdata: {
|
||||
handler(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
|
||||
listData(newVal) {
|
||||
this.range = newVal
|
||||
this.dataList = this.getDataList(this.getSelectedValue(newVal))
|
||||
},
|
||||
value(newVal) {
|
||||
this.dataList = this.getDataList(newVal)
|
||||
this.formItem && this.formItem.setValue(newVal)
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataList: [],
|
||||
range: [],
|
||||
contentText: {
|
||||
contentdown: '查看更多',
|
||||
contentrefresh: '加载中',
|
||||
contentnomore: '没有更多'
|
||||
},
|
||||
styles: {
|
||||
selectedColor: '#007aff',
|
||||
selectedTextColor: '#333',
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm('uniForms')
|
||||
this.formItem = this.getForm('uniFormsItem')
|
||||
this.formItem && this.formItem.setValue(this.value)
|
||||
this.styles = {
|
||||
selectedColor: this.selectedColor,
|
||||
selectedTextColor: this.selectedTextColor
|
||||
}
|
||||
|
||||
if (this.formItem) {
|
||||
if (this.formItem.name) {
|
||||
this.rename = this.formItem.name
|
||||
this.form.inputChildrens.push(this)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.localdata && this.localdata.length !== 0) {
|
||||
this.range = this.localdata
|
||||
this.dataList = this.getDataList(this.getSelectedValue(this.range))
|
||||
} else {
|
||||
if (this.collection) {
|
||||
this.loadData()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(range) {},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
chagne(e) {
|
||||
const values = e.detail.value
|
||||
|
||||
let detail = {
|
||||
value: [],
|
||||
data: []
|
||||
}
|
||||
|
||||
if (this.multiple) {
|
||||
this.range.forEach(item => {
|
||||
if (values.includes(item.value + '')) {
|
||||
detail.value.push(item.value)
|
||||
detail.data.push(item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
const range = this.range.find(item => (item.value + '') === values)
|
||||
if (range) {
|
||||
detail = {
|
||||
value: range.value,
|
||||
data: range
|
||||
}
|
||||
}
|
||||
}
|
||||
this.formItem && this.formItem.setValue(detail.value)
|
||||
this.$emit('input', detail.value)
|
||||
this.$emit('change', {
|
||||
detail
|
||||
})
|
||||
if (this.multiple) {
|
||||
// 如果 v-model 没有绑定 ,则走内部逻辑
|
||||
// if (this.value.length === 0) {
|
||||
this.dataList = this.getDataList(detail.value, true)
|
||||
// }
|
||||
} else {
|
||||
this.dataList = this.getDataList(detail.value)
|
||||
}
|
||||
},
|
||||
getLabelClass(item, index) {
|
||||
let classes = []
|
||||
switch (this.mode) {
|
||||
case 'default':
|
||||
item.disabled && classes.push('disabled-cursor')
|
||||
break
|
||||
case 'button':
|
||||
classes.push(...['is-button', ...this.getClasses(item, 'button')])
|
||||
break
|
||||
case 'list':
|
||||
if (this.multiple) {
|
||||
classes.push('is-list-multiple-box')
|
||||
} else {
|
||||
classes.push('is-list-box')
|
||||
}
|
||||
item.disabled && classes.push('is-list-disabled')
|
||||
index !== 0 && classes.push('is-list-border')
|
||||
break
|
||||
case 'tag':
|
||||
classes.push(...['is-tag', ...this.getClasses(item, 'tag')])
|
||||
break
|
||||
}
|
||||
classes = classes.join(' ')
|
||||
return classes
|
||||
},
|
||||
getCheckboxClass(item, type = '') {
|
||||
let classes = []
|
||||
if (this.multiple) {
|
||||
classes.push(...this.getClasses(item, 'default-multiple', type))
|
||||
} else {
|
||||
classes.push(...this.getClasses(item, 'default', type))
|
||||
}
|
||||
classes = classes.join(' ')
|
||||
return classes
|
||||
},
|
||||
getTextClass(item) {
|
||||
let classes = []
|
||||
switch (this.mode) {
|
||||
case 'default':
|
||||
classes.push(...this.getClasses(item, 'list'))
|
||||
break
|
||||
case 'button':
|
||||
classes.push(...this.getClasses(item, 'list'))
|
||||
break
|
||||
case 'list':
|
||||
classes.push(...this.getClasses(item, 'list'))
|
||||
break
|
||||
case 'tag':
|
||||
classes.push(...['is-tag-text', ...this.getClasses(item, 'tag-text')])
|
||||
break
|
||||
}
|
||||
classes = classes.join(' ')
|
||||
return classes
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取渲染的新数组
|
||||
* @param {Object} value 选中内容
|
||||
*/
|
||||
getDataList(value) {
|
||||
// 解除引用关系,破坏原引用关系,避免污染源数据
|
||||
let dataList = JSON.parse(JSON.stringify(this.range))
|
||||
let list = []
|
||||
if (this.multiple) {
|
||||
if (!Array.isArray(value)) {
|
||||
value = []
|
||||
// console.error('props 类型错误');
|
||||
}
|
||||
}
|
||||
dataList.forEach((item, index) => {
|
||||
item.disabled = item.disable || item.disabled || false
|
||||
if (this.multiple) {
|
||||
if (value.length > 0) {
|
||||
let have = value.find(val => val === item.value)
|
||||
item.selected = have !== undefined
|
||||
} else {
|
||||
item.selected = false
|
||||
}
|
||||
} else {
|
||||
item.selected = value === item.value
|
||||
}
|
||||
|
||||
list.push(item)
|
||||
})
|
||||
return this.setRange(list)
|
||||
},
|
||||
/**
|
||||
* 处理最大最小值
|
||||
* @param {Object} list
|
||||
*/
|
||||
setRange(list) {
|
||||
let selectList = list.filter(item => item.selected)
|
||||
let min = Number(this.min) || 0
|
||||
let max = Number(this.max) || ''
|
||||
list.forEach((item, index) => {
|
||||
if (this.multiple) {
|
||||
if (selectList.length <= min) {
|
||||
let have = selectList.find(val => val.value === item.value)
|
||||
if (have !== undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
|
||||
if (selectList.length >= max && max !== '') {
|
||||
let have = selectList.find(val => val.value === item.value)
|
||||
if (have === undefined) {
|
||||
item.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setClass(item, index)
|
||||
list[index] = item
|
||||
})
|
||||
return list
|
||||
},
|
||||
/**
|
||||
* 设置 class
|
||||
* @param {Object} item
|
||||
* @param {Object} index
|
||||
*/
|
||||
setClass(item, index) {
|
||||
// 设置 label 的 class
|
||||
item.labelClass = this.getLabelClass(item, index)
|
||||
// 设置 checkbox外层样式
|
||||
item.checkboxBgClass = this.getCheckboxClass(item, '-bg')
|
||||
// 设置 checkbox 内层样式
|
||||
item.checkboxClass = this.getCheckboxClass(item)
|
||||
// 设置文本样式
|
||||
item.textClass = this.getTextClass(item)
|
||||
// 设置 list 对勾右侧样式
|
||||
item.listClass = this.getCheckboxClass(item, '-list')
|
||||
|
||||
// 设置自定义样式
|
||||
item.styleBackgroud = this.setStyleBackgroud(item)
|
||||
item.styleIcon = this.setStyleIcon(item)
|
||||
item.styleIconText = this.setStyleIconText(item)
|
||||
item.styleRightIcon = this.setStyleRightIcon(item)
|
||||
},
|
||||
/**
|
||||
* 获取 class
|
||||
*/
|
||||
getClasses(item, name, type = '') {
|
||||
let classes = []
|
||||
item.disabled && classes.push('is-' + name + '-disabled' + type)
|
||||
item.selected && classes.push('is-' + name + '-checked' + type)
|
||||
|
||||
if (this.mode !== 'button' || name === 'button') {
|
||||
item.selected && item.disabled && classes.push('is-' + name + '-disabled-checked' + type)
|
||||
}
|
||||
|
||||
return classes
|
||||
},
|
||||
/**
|
||||
* 获取选中值
|
||||
* @param {Object} range
|
||||
*/
|
||||
getSelectedValue(range) {
|
||||
if (!this.multiple) return this.value
|
||||
let selectedArr = []
|
||||
range.forEach((item) => {
|
||||
if (item.selected) {
|
||||
selectedArr.push(item.value)
|
||||
}
|
||||
})
|
||||
return this.value.length > 0 ? this.value : selectedArr
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置背景样式
|
||||
*/
|
||||
setStyleBackgroud(item) {
|
||||
let styles = {}
|
||||
|
||||
if (item.selected) {
|
||||
if (this.mode !== 'list') {
|
||||
styles.borderColor = this.styles.selectedColor
|
||||
}
|
||||
if (this.mode === 'tag') {
|
||||
styles.backgroundColor = this.styles.selectedColor
|
||||
}
|
||||
}
|
||||
return styles
|
||||
},
|
||||
setStyleIcon(item) {
|
||||
let styles = {}
|
||||
|
||||
if (item.selected) {
|
||||
styles.backgroundColor = this.styles.selectedColor
|
||||
styles.borderColor = this.styles.selectedColor
|
||||
}
|
||||
return styles
|
||||
},
|
||||
setStyleIconText(item) {
|
||||
let styles = {}
|
||||
if (item.selected) {
|
||||
if (this.styles.selectedTextColor) {
|
||||
styles.color = this.styles.selectedTextColor
|
||||
} else {
|
||||
if (this.mode === 'tag') {
|
||||
styles.color = '#fff'
|
||||
} else {
|
||||
styles.color = this.styles.selectedColor
|
||||
}
|
||||
}
|
||||
}
|
||||
return styles
|
||||
},
|
||||
setStyleRightIcon(item) {
|
||||
let styles = {}
|
||||
if (item.selected) {
|
||||
if (this.mode === 'list') {
|
||||
styles.borderColor = this.styles.selectedColor
|
||||
}
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@charset "UTF-8";
|
||||
|
||||
.uni-data-checklist {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
/* min-height: 36px; */
|
||||
}
|
||||
|
||||
.uni-data-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* justify-content: center; */
|
||||
height: 36px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.checklist-group {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.checklist-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 5px 0;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.checklist-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
margin-left: 5px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.is-button {
|
||||
margin-right: 10px;
|
||||
padding: 3px 15px;
|
||||
border: 1px #DCDFE6 solid;
|
||||
border-radius: 3px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.is-list {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.is-list-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding: 10px 15px;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.checklist-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list-content {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.is-list-multiple-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding: 10px 15px;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.is-list-border {
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
.is-tag {
|
||||
margin-right: 10px;
|
||||
padding: 3px 10px;
|
||||
border: 1px #eee solid;
|
||||
border-radius: 3px;
|
||||
background-color: #f5f5f5;
|
||||
/* transition: border-color 0.1s; */
|
||||
}
|
||||
|
||||
.is-tag-text {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.checkbox__inner {
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
transition: border-color 0.1s;
|
||||
}
|
||||
|
||||
.checkbox__inner-icon {
|
||||
border: 1px solid #fff;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 8px;
|
||||
left: 5px;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
width: 3px;
|
||||
opacity: 0;
|
||||
transition: transform .2s;
|
||||
transform-origin: center;
|
||||
transform: rotate(40deg) scaleY(0.4);
|
||||
}
|
||||
|
||||
.radio__inner {
|
||||
flex-shrink: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 16px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
transition: border-color .3s;
|
||||
}
|
||||
|
||||
.radio__inner-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 10px;
|
||||
opacity: 0;
|
||||
transition: transform .3s;
|
||||
}
|
||||
|
||||
.checkobx__list {
|
||||
border: 1px solid #fff;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
transform-origin: center;
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
/* 禁用样式 */
|
||||
.is-default-disabled-bg {
|
||||
background-color: #F2F6FC;
|
||||
border-color: #DCDFE6;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.is-default-multiple-disabled-bg {
|
||||
background-color: #F2F6FC;
|
||||
border-color: #DCDFE6;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.is-default-disabled {
|
||||
border-color: #F2F6FC;
|
||||
}
|
||||
|
||||
.is-default-multiple-disabled {
|
||||
border-color: #F2F6FC;
|
||||
}
|
||||
|
||||
.is-list-disabled {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.is-list-disabled-checked {
|
||||
color: #a1dcc1;
|
||||
}
|
||||
|
||||
.is-button-disabled {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
border-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.is-button-text-disabled {
|
||||
color: #C0C4CC;
|
||||
}
|
||||
|
||||
.is-button-disabled-checked {
|
||||
border-color: #a1dcc1;
|
||||
}
|
||||
|
||||
.is-tag-disabled {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
border-color: #e9e9eb;
|
||||
background-color: #f4f4f5;
|
||||
}
|
||||
|
||||
.is-tag-text-disabled {
|
||||
color: #bcbec2;
|
||||
}
|
||||
|
||||
/* 选中样式 */
|
||||
.is-default-checked-bg {
|
||||
border-color: #007aff;
|
||||
}
|
||||
|
||||
.is-default-multiple-checked-bg {
|
||||
border-color: #007aff;
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.is-default-checked {
|
||||
opacity: 1;
|
||||
background-color: #007aff;
|
||||
transform: rotate(45deg) scaleY(1);
|
||||
}
|
||||
|
||||
.is-default-multiple-checked {
|
||||
opacity: 1;
|
||||
transform: rotate(45deg) scaleY(1);
|
||||
}
|
||||
|
||||
.is-default-disabled-checked-bg {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-default-multiple-disabled-checked-bg {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-default-checked-list {
|
||||
border-color: #007aff;
|
||||
opacity: 1;
|
||||
transform: rotate(45deg) scaleY(1);
|
||||
}
|
||||
|
||||
.is-default-multiple-checked-list {
|
||||
border-color: #007aff;
|
||||
opacity: 1;
|
||||
transform: rotate(45deg) scaleY(1);
|
||||
}
|
||||
|
||||
.is-list-disabled-checked {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-default-disabled-checked-list {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-default-multiple-disabled-checked-list {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-button-checked {
|
||||
border-color: #007aff;
|
||||
}
|
||||
|
||||
.is-button-disabled-checked {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.is-list-checked {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.is-tag-checked {
|
||||
border-color: #007aff;
|
||||
background-color: #007aff;
|
||||
}
|
||||
|
||||
.is-tag-text-checked {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.is-tag-disabled-checked {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.disabled-cursor {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.is-wrap {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
/* #ifdef MP-ALIPAY */
|
||||
display: none;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
362
components/uni-data-indexed-list/clientdb.js
Normal file
362
components/uni-data-indexed-list/clientdb.js
Normal file
@ -0,0 +1,362 @@
|
||||
|
||||
const events = {
|
||||
load: 'load',
|
||||
error: 'error'
|
||||
}
|
||||
const pageMode = {
|
||||
add: 'add',
|
||||
replace: 'replace'
|
||||
}
|
||||
|
||||
const attrs = [
|
||||
'pageCurrent',
|
||||
'pageSize',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'getcount',
|
||||
'orderby',
|
||||
'where'
|
||||
]
|
||||
|
||||
export default {
|
||||
props: {
|
||||
collection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
getcount: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
getone: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
listData: this.getone ? {} : [],
|
||||
paginationInternal: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
},
|
||||
errorMessage: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
let db = null;
|
||||
let dbCmd = null;
|
||||
|
||||
if(this.collection){
|
||||
this.db = uniCloud.database();
|
||||
this.dbCmd = this.db.command;
|
||||
}
|
||||
|
||||
this._isEnded = false
|
||||
|
||||
this.$watch(() => {
|
||||
var al = []
|
||||
attrs.forEach(key => {
|
||||
al.push(this[key])
|
||||
})
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
this.paginationInternal.pageSize = this.pageSize
|
||||
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (needReset) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.paginationInternal.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this._execLoadData()
|
||||
})
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList = []
|
||||
if (!window.unidev) {
|
||||
window.unidev = {
|
||||
clientDB: {
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
unidev.clientDB.data.push(this._debugDataList)
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
let changeName
|
||||
let events = this.$scope.dataset.eventOpts
|
||||
for (var i = 0; i < events.length; i++) {
|
||||
let event = events[i]
|
||||
if (event[0].includes('^load')) {
|
||||
changeName = event[1][0][0]
|
||||
}
|
||||
}
|
||||
if (changeName) {
|
||||
let parent = this.$parent
|
||||
let maxDepth = 16
|
||||
this._changeDataFunction = null
|
||||
while (parent && maxDepth > 0) {
|
||||
let fun = parent[changeName]
|
||||
if (fun && typeof fun === 'function') {
|
||||
this._changeDataFunction = fun
|
||||
maxDepth = 0
|
||||
break
|
||||
}
|
||||
parent = parent.$parent
|
||||
maxDepth--;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// if (!this.manual) {
|
||||
// this.loadData()
|
||||
// }
|
||||
},
|
||||
// #ifdef H5
|
||||
beforeDestroy() {
|
||||
if (process.env.NODE_ENV === 'development' && window.unidev) {
|
||||
var cd = this._debugDataList
|
||||
var dl = unidev.clientDB.data
|
||||
for (var i = dl.length - 1; i >= 0; i--) {
|
||||
if (dl[i] === cd) {
|
||||
dl.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
loadData(args1, args2) {
|
||||
let callback = null
|
||||
if (typeof args1 === 'object') {
|
||||
if (args1.clear) {
|
||||
this.clear()
|
||||
this.reset()
|
||||
}
|
||||
if (args1.current !== undefined) {
|
||||
this.paginationInternal.current = args1.current
|
||||
}
|
||||
if (typeof args2 === 'function') {
|
||||
callback = args2
|
||||
}
|
||||
} else if (typeof args1 === 'function') {
|
||||
callback = args1
|
||||
}
|
||||
|
||||
this._execLoadData(callback)
|
||||
},
|
||||
loadMore() {
|
||||
if (this._isEnded) {
|
||||
return
|
||||
}
|
||||
this._execLoadData()
|
||||
},
|
||||
refresh() {
|
||||
this.clear()
|
||||
this._execLoadData()
|
||||
},
|
||||
clear() {
|
||||
this._isEnded = false
|
||||
this.listData = []
|
||||
},
|
||||
reset() {
|
||||
this.paginationInternal.current = 1
|
||||
},
|
||||
remove(id, {
|
||||
action,
|
||||
callback,
|
||||
confirmTitle,
|
||||
confirmContent
|
||||
} = {}) {
|
||||
if (!id || !id.length) {
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: confirmTitle || '提示',
|
||||
content: confirmContent || '是否删除该数据',
|
||||
showCancel: true,
|
||||
success: (res) => {
|
||||
if (!res.confirm) {
|
||||
return
|
||||
}
|
||||
this._execRemove(id, action, callback)
|
||||
}
|
||||
})
|
||||
},
|
||||
_execLoadData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.errorMessage = ''
|
||||
|
||||
this._getExec().then((res) => {
|
||||
this.loading = false
|
||||
const {
|
||||
data,
|
||||
count
|
||||
} = res.result
|
||||
this._isEnded = data.length < this.pageSize
|
||||
|
||||
callback && callback(data, this._isEnded)
|
||||
this._dispatchEvent(events.load, data)
|
||||
|
||||
if (this.getone) {
|
||||
this.listData = data.length ? data[0] : undefined
|
||||
} else if (this.pageData === pageMode.add) {
|
||||
this.listData.push(...data)
|
||||
if (this.listData.length) {
|
||||
this.paginationInternal.current++
|
||||
}
|
||||
} else if (this.pageData === pageMode.replace) {
|
||||
this.listData = data
|
||||
this.paginationInternal.count = count
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
this._debugDataList.length = 0
|
||||
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
|
||||
}
|
||||
// #endif
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
callback && callback()
|
||||
this.$emit(events.error, err)
|
||||
})
|
||||
},
|
||||
_getExec() {
|
||||
let exec = this.db
|
||||
if (this.action) {
|
||||
exec = exec.action(this.action)
|
||||
}
|
||||
|
||||
exec = exec.collection(this.collection)
|
||||
|
||||
if (!(!this.where || !Object.keys(this.where).length)) {
|
||||
exec = exec.where(this.where)
|
||||
}
|
||||
if (this.field) {
|
||||
exec = exec.field(this.field)
|
||||
}
|
||||
if (this.orderby) {
|
||||
exec = exec.orderBy(this.orderby)
|
||||
}
|
||||
|
||||
const {
|
||||
current,
|
||||
size
|
||||
} = this.paginationInternal
|
||||
exec = exec.skip(size * (current - 1)).limit(size).get({
|
||||
getCount: this.getcount
|
||||
})
|
||||
|
||||
return exec
|
||||
},
|
||||
_execRemove(id, action, callback) {
|
||||
if (!this.collection || !id) {
|
||||
return
|
||||
}
|
||||
|
||||
const ids = Array.isArray(id) ? id : [id]
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({
|
||||
mask: true
|
||||
})
|
||||
|
||||
let exec = this.db
|
||||
if (action) {
|
||||
exec = exec.action(action)
|
||||
}
|
||||
|
||||
exec.collection(this.collection).where({
|
||||
_id: dbCmd.in(ids)
|
||||
}).remove().then((res) => {
|
||||
callback && callback(res.result)
|
||||
if (this.pageData === pageMode.replace) {
|
||||
this.refresh()
|
||||
} else {
|
||||
this.removeData(ids)
|
||||
}
|
||||
}).catch((err) => {
|
||||
uni.showModal({
|
||||
content: err.message,
|
||||
showCancel: false
|
||||
})
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
},
|
||||
removeData(ids) {
|
||||
let il = ids.slice(0)
|
||||
let dl = this.listData
|
||||
for (let i = dl.length - 1; i >= 0; i--) {
|
||||
let index = il.indexOf(dl[i]._id)
|
||||
if (index >= 0) {
|
||||
dl.splice(i, 1)
|
||||
il.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
_dispatchEvent(type, data) {
|
||||
if (this._changeDataFunction) {
|
||||
this._changeDataFunction(data, this._isEnded)
|
||||
} else {
|
||||
this.$emit(type, data, this._isEnded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
142
components/uni-data-indexed-list/uni-data-indexed-list-item.vue
Normal file
142
components/uni-data-indexed-list/uni-data-indexed-list-item.vue
Normal file
@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="loaded || list.itemIndex < 15" class="uni-indexed-list__title-wrapper">
|
||||
<text v-if="list.items && list.items.length > 0" class="uni-indexed-list__title">{{ list.title }}</text>
|
||||
</view>
|
||||
<view v-if="(loaded || list.itemIndex < 15) && list.items && list.items.length > 0" class="uni-indexed-list__list">
|
||||
<view v-for="(item, index) in list.items" :key="index" class="uni-indexed-list__item" hover-class="uni-indexed-list__item--hover">
|
||||
<view class="uni-indexed-list__item-container" @click="onClick(idx, index)">
|
||||
<view class="uni-indexed-list__item-border" :class="{'uni-indexed-list__item-border--last':index===list.items.length-1}">
|
||||
<view v-if="showSelect" style="margin-right: 20rpx;">
|
||||
<uni-icons :type="item.checked ? 'checkbox-filled' : 'circle'" :color="item.checked ? '#007aff' : '#aaa'" size="24" />
|
||||
</view>
|
||||
<text class="uni-indexed-list__item-content">{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
export default {
|
||||
name: 'UniIndexedList',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
idx: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
showSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(idx, index) {
|
||||
this.$emit("itemClick", {
|
||||
idx,
|
||||
index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-indexed-list__list {
|
||||
background-color: #ffffff;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item {
|
||||
font-size: 16px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-container {
|
||||
padding-left: 15px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-border {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
padding: 15px;
|
||||
padding-left: 0;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-border--last {
|
||||
border-bottom-width: 0px;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-content {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-indexed-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-indexed-list__title-wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.uni-indexed-list__title {
|
||||
padding: 6px 12px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
364
components/uni-data-indexed-list/uni-data-indexed-list.vue
Normal file
364
components/uni-data-indexed-list/uni-data-indexed-list.vue
Normal file
@ -0,0 +1,364 @@
|
||||
<template>
|
||||
<view class="uni-indexed-list" ref="list" id="list">
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<list class="uni-indexed-list__scroll" scrollable="true" show-scrollbar="false">
|
||||
<cell v-for="(list, idx) in lists" :key="idx" :ref="'uni-indexed-list-' + idx">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<scroll-view :scroll-into-view="scrollViewId" class="uni-indexed-list__scroll" scroll-y>
|
||||
<view v-for="(list, idx) in lists" :key="idx" :id="'uni-indexed-list-' + idx">
|
||||
<!-- #endif -->
|
||||
<uni-indexed-list-item :list="list" :loaded="loaded" :idx="idx" :showSelect="showSelect" @itemClick="onClick"></uni-indexed-list-item>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
</cell>
|
||||
</list>
|
||||
<!-- #endif -->
|
||||
<view :class="touchmove ? 'uni-indexed-list__menu--active' : ''" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd" class="uni-indexed-list__menu">
|
||||
<view v-for="(list, key) in lists" :key="key" class="uni-indexed-list__menu-item">
|
||||
<text class="uni-indexed-list__menu-text" :class="touchmoveIndex == key ? 'uni-indexed-list__menu-text--active' : ''">{{ list.value }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="touchmove" class="uni-indexed-list__alert-wrapper">
|
||||
<text class="uni-indexed-list__alert">{{ lists[touchmoveIndex] }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
import uniIndexedListItem from './uni-data-indexed-list-item.vue'
|
||||
import clientdb from './clientdb.js'
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
function throttle(func, delay) {
|
||||
var prev = Date.now();
|
||||
return function() {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
var now = Date.now();
|
||||
if (now - prev >= delay) {
|
||||
func.apply(context, args);
|
||||
prev = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
let pageY = e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
if (this.touchmoveIndex === index) {
|
||||
return false
|
||||
}
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
// #ifndef APP-NVUE
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
|
||||
animated: false
|
||||
})
|
||||
this.touchmoveIndex = index
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
const throttleTouchMove = throttle(touchMove, 40)
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* IndexedList 索引列表
|
||||
* @description 用于展示索引列表
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=375
|
||||
* @property {Boolean} showSelect = [true|false] 展示模式
|
||||
* @value true 展示模式
|
||||
* @value false 选择模式
|
||||
* @property {Object} options 索引列表需要的数据对象
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|ClientDBActionString} action 云端执行数据库查询的前或后,触发某个action函数操作,进行预处理或后处理
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
* @event {Function} click 点击列表事件 ,返回当前选择项的事件对象
|
||||
* @example <uni-indexed-list options="" showSelect="false" @click=""></uni-indexed-list>
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataIndexedList',
|
||||
mixins: [clientdb],
|
||||
components: {
|
||||
uniIcons,
|
||||
uniIndexedListItem
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
showSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
winHeight: 0,
|
||||
itemHeight: 0,
|
||||
winOffsetY: 0,
|
||||
touchmove: false,
|
||||
touchmoveIndex: -1,
|
||||
scrollViewId: '',
|
||||
touchmoveTimeout: '',
|
||||
loaded: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
handler: function() {
|
||||
this.setList()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.localdata.length || this.options.length) {
|
||||
setTimeout(() => {
|
||||
this.setList()
|
||||
}, 50)
|
||||
setTimeout(() => {
|
||||
this.loaded = true
|
||||
}, 300);
|
||||
} else if (this.collection) {
|
||||
if (!this.manual) {
|
||||
this._execLoadData((data) => {
|
||||
this.lists = this.groupData(data);
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
groupData(data) {
|
||||
let groups = {};
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
let item = data[i];
|
||||
let group = item.group;
|
||||
if (!groups[group]) {
|
||||
groups[group] = {
|
||||
"title": group,
|
||||
"value": group,
|
||||
"itemIndex": i,
|
||||
"items": []
|
||||
}
|
||||
}
|
||||
groups[group].items.push(item);
|
||||
}
|
||||
let result = []
|
||||
for (let g in groups) {
|
||||
let group = groups[g];
|
||||
let items = group.items;
|
||||
for (let j = 0; j < items.length; j++) {
|
||||
items[j].itemIndex = j;
|
||||
}
|
||||
result.push(group);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
setList(data) {
|
||||
let index = 0;
|
||||
this.lists = []
|
||||
this.options.forEach((value, index) => {
|
||||
if (value.data.length === 0) {
|
||||
return
|
||||
}
|
||||
let indexBefore = index
|
||||
let items = value.data.map(item => {
|
||||
let obj = {}
|
||||
obj['value'] = value.letter
|
||||
obj['text'] = item
|
||||
obj['itemIndex'] = index
|
||||
index++
|
||||
obj.checked = item.checked ? item.checked : false
|
||||
return obj
|
||||
})
|
||||
this.lists.push({
|
||||
title: value.letter,
|
||||
value: value.letter,
|
||||
items: items,
|
||||
itemIndex: indexBefore
|
||||
})
|
||||
})
|
||||
// #ifndef APP-NVUE
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('#list')
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.winOffsetY = ret[0].top
|
||||
this.winHeight = ret[0].height
|
||||
this.itemHeight = this.winHeight / this.lists.length
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['list'], (res) => {
|
||||
this.winOffsetY = res.size.top
|
||||
this.winHeight = res.size.height
|
||||
this.itemHeight = this.winHeight / this.lists.length
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
touchStart(e) {
|
||||
this.touchmove = true
|
||||
let pageY = e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
// #ifdef APP-NVUE
|
||||
dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
|
||||
animated: false
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
touchMove(e) {
|
||||
// #ifndef APP-PLUS
|
||||
let pageY = e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
if (this.touchmoveIndex === index) {
|
||||
return false
|
||||
}
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
throttleTouchMove.call(this, e)
|
||||
// #endif
|
||||
},
|
||||
touchEnd() {
|
||||
this.touchmove = false
|
||||
this.touchmoveIndex = -1
|
||||
},
|
||||
onClick(e) {
|
||||
let {
|
||||
idx,
|
||||
index
|
||||
} = e
|
||||
let obj = {}
|
||||
for (let key in this.lists[idx].items[index]) {
|
||||
obj[key] = this.lists[idx].items[index][key]
|
||||
}
|
||||
let select = []
|
||||
if (this.showSelect) {
|
||||
this.lists[idx].items[index].checked = !this.lists[idx].items[index].checked
|
||||
this.lists.forEach((value, idx) => {
|
||||
value.items.forEach((item, index) => {
|
||||
if (item.checked) {
|
||||
let obj = {}
|
||||
for (let key in this.lists[idx].items[index]) {
|
||||
obj[key] = this.lists[idx].items[index][key]
|
||||
}
|
||||
select.push(obj)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
this.$emit('click', {
|
||||
item: obj,
|
||||
select: select
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-indexed-list {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-indexed-list__scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu {
|
||||
width: 24px;
|
||||
background-color: lightgrey;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-text {
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu--active {
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-text--active {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-indexed-list__alert-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__alert {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 80px;
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
font-size: 35px;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
326
components/uni-data-picker/uni-data-picker.vue
Normal file
326
components/uni-data-picker/uni-data-picker.vue
Normal file
@ -0,0 +1,326 @@
|
||||
<template>
|
||||
<view class="uni-data-tree">
|
||||
<view class="uni-data-tree-input" @click="handleInput">
|
||||
<slot :options="options" :data="inputSelected" :error="errorMessage">
|
||||
<view class="input-value">
|
||||
<text v-if="errorMessage" class="error-text">{{errorMessage}}</text>
|
||||
<uni-load-more v-else-if="loading && !isOpened" class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
<view v-else-if="inputSelected.length" v-for="(item,index) in inputSelected" :key="index" class="input-value-item">
|
||||
<text>{{item.text}}</text><text v-if="index<inputSelected.length-1" class="input-split-line">/</text>
|
||||
</view>
|
||||
<text v-else class="placeholder">{{placeholder}}</text>
|
||||
<view class="input-arrow"></view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
|
||||
<view class="uni-data-tree-dialog" v-if="isOpened">
|
||||
<view class="dialog-caption">
|
||||
<view class="title-area">
|
||||
<text class="title">{{popupTitle}}</text>
|
||||
</view>
|
||||
<view class="dialog-close" @click="handleClose">
|
||||
<view class="dialog-close-plus" data-id="close"></view>
|
||||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<data-picker-view class="picker-view" ref="pickerView" v-model="value" :localdata="localdata" :preload="preload" :collection="collection" :field="field" :orderby="orderby" :where="where" :step-searh="stepSearh" :self-field="selfField" :parent-field="parentField" :managed-mode="true" @change="onchange" @datachange="ondatachange"></data-picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataPicker from "../uni-data-pickerview/uni-data-picker.js"
|
||||
import DataPickerView from "../uni-data-pickerview/uni-data-pickerview.vue"
|
||||
|
||||
/**
|
||||
* uni-data-picker
|
||||
* @description uni-data-picker
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-data-picker
|
||||
* @property {String} popup-title 弹出窗口标题
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} preload = [true|false] 是否预加载数据
|
||||
* @value true 开启预加载数据,点击弹出窗口后显示已加载数据
|
||||
* @value false 关闭预加载数据,点击弹出窗口后开始加载数据
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
* @event {Function} onpopupshow 弹出的选择窗口打开时触发此事件
|
||||
* @event {Function} onpopuphide 弹出的选择窗口关闭时触发此事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPicker',
|
||||
mixins: [dataPicker],
|
||||
components: {
|
||||
DataPickerView
|
||||
},
|
||||
props: {
|
||||
popupTitle: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
heightMobile: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
options: {
|
||||
type: [Object, Array],
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpened: false,
|
||||
inputSelected: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm('uniForms')
|
||||
this.formItem = this.getForm('uniFormsItem')
|
||||
if (this.formItem) {
|
||||
if (this.formItem.name) {
|
||||
this.rename = this.formItem.name
|
||||
this.form.inputChildrens.push(this)
|
||||
}
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.load()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = []
|
||||
this.selectedIndex = 0
|
||||
this.load()
|
||||
},
|
||||
load() {
|
||||
if (this.isLocaldata) {
|
||||
this.loadData()
|
||||
this.inputSelected = this.selected.slice(0)
|
||||
} else if (this.value.length) {
|
||||
this.getTreePath(() => {
|
||||
this.inputSelected = this.selected.slice(0)
|
||||
})
|
||||
}
|
||||
},
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false;
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
show() {
|
||||
this.isOpened = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.pickerView.updateData({
|
||||
treeData: this._treeData,
|
||||
selected: this.selected,
|
||||
selectedIndex: this.selectedIndex
|
||||
})
|
||||
})
|
||||
},
|
||||
hide() {
|
||||
this.isOpened = false
|
||||
},
|
||||
handleInput() {
|
||||
this.show()
|
||||
},
|
||||
handleClose(e) {
|
||||
this.hide()
|
||||
},
|
||||
ondatachange(e) {
|
||||
this._treeData = this.$refs.pickerView._treeData
|
||||
},
|
||||
onchange(e) {
|
||||
this.hide()
|
||||
this.inputSelected = e
|
||||
this._dispatchEvent(e)
|
||||
},
|
||||
_dispatchEvent(selected) {
|
||||
var value = new Array(selected.length)
|
||||
for (var i = 0; i < selected.length; i++) {
|
||||
value[i] = selected[i].value
|
||||
}
|
||||
|
||||
if (this.formItem) {
|
||||
const v = value[value.length - 1]
|
||||
this.formItem.setValue(v)
|
||||
}
|
||||
|
||||
this.$emit('change', value)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-data-tree {
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
.input-value {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
font-size: 14px;
|
||||
line-height: 38px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 5px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.input-value-item {
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.input-split-line {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.input-arrow {
|
||||
margin-left: auto;
|
||||
margin-right: 5px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-left: 1px solid #999;
|
||||
border-bottom: 1px solid #999;
|
||||
transform: rotate(-45deg);
|
||||
transform-origin: 2px;
|
||||
}
|
||||
|
||||
.uni-data-tree-cover {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: auto;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 80vh;
|
||||
background-color: #FFFFFF;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.title-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dialog-close-plus {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background-color: #666;
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dialog-close-rotate {
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-data-tree-cover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
max-height: 50vh;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
474
components/uni-data-pickerview/uni-data-picker.js
Normal file
474
components/uni-data-pickerview/uni-data-picker.js
Normal file
@ -0,0 +1,474 @@
|
||||
export default {
|
||||
props: {
|
||||
localdata: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
getcount: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
getone: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
gettree: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
stepSearh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selfField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
parentField: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
loadMore: {
|
||||
contentdown: '',
|
||||
contentrefresh: '',
|
||||
contentnomore: ''
|
||||
},
|
||||
dataList: [],
|
||||
selected: [],
|
||||
selectedIndex: 0,
|
||||
page: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLocaldata() {
|
||||
return this.localdata.length > 0
|
||||
},
|
||||
postField() {
|
||||
return `${this.field}, ${this.parentField} as parent_value`
|
||||
},
|
||||
postWhere() {
|
||||
let result = []
|
||||
let selected = this.selected
|
||||
result.push(`${this.parentField} == null`)
|
||||
if (selected.length) {
|
||||
for (var i = 0; i < selected.length - 1; i++) {
|
||||
result.push(`${this.parentField} == '${selected[i].value}'`)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
nodeWhere() {
|
||||
let result = []
|
||||
let selected = this.selected
|
||||
if (selected.length) {
|
||||
result.push(`${this.parentField} == '${selected[selected.length - 1].value}'`)
|
||||
}
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
formatValue(value) {
|
||||
var dl = new Array(value.length)
|
||||
for (let i = 0; i < dl.length; i++) {
|
||||
dl[i] = value[i].value
|
||||
}
|
||||
this._value = dl
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$watch(() => {
|
||||
var al = [];
|
||||
['pageCurrent',
|
||||
'pageSize',
|
||||
'value',
|
||||
'localdata',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'orderby',
|
||||
'where',
|
||||
'getont',
|
||||
'getcount',
|
||||
'gettree'
|
||||
].forEach(key => {
|
||||
al.push(this[key])
|
||||
});
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.page.current = this.pageCurrent
|
||||
}
|
||||
this.page.size = this.pageSize
|
||||
|
||||
this.onPropsChange()
|
||||
})
|
||||
this._treeData = []
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = []
|
||||
},
|
||||
getCommand(options = {}) {
|
||||
/* eslint-disable no-undef */
|
||||
let db = uniCloud.database()
|
||||
|
||||
const action = options.action || this.action
|
||||
if (action) {
|
||||
db = db.action(action)
|
||||
}
|
||||
|
||||
const collection = options.collection || this.collection
|
||||
db = db.collection(collection)
|
||||
|
||||
const where = options.where || this.where
|
||||
if (!(!where || !Object.keys(where).length)) {
|
||||
db = db.where(where)
|
||||
}
|
||||
|
||||
const field = options.field || this.field
|
||||
if (field) {
|
||||
db = db.field(field)
|
||||
}
|
||||
|
||||
const orderby = options.orderby || this.orderby
|
||||
if (orderby) {
|
||||
db = db.orderBy(orderby)
|
||||
}
|
||||
|
||||
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
|
||||
const size = options.pageSize !== undefined ? options.pageSize : this.page.size
|
||||
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
|
||||
const getTree = options.gettree !== undefined ? options.gettree : this.gettree
|
||||
|
||||
const getOptions = {
|
||||
getCount,
|
||||
getTree
|
||||
}
|
||||
if (options.getTreePath) {
|
||||
getOptions.getTreePath = options.getTreePath
|
||||
}
|
||||
|
||||
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
|
||||
|
||||
return db
|
||||
},
|
||||
getTreePath(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
getTreePath: {
|
||||
startWith: `${this.selfField}=='${this.value}'`
|
||||
}
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
let treePath = []
|
||||
this._extractTreePath(res.result.data, treePath)
|
||||
this.selected = treePath
|
||||
callback && callback()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
if (this.isLocaldata) {
|
||||
this._processLocalData()
|
||||
return
|
||||
}
|
||||
|
||||
if (this.value.length) {
|
||||
this._loadNodeData((data) => {
|
||||
this._treeData = data
|
||||
this._updateBindData()
|
||||
this._updateSelected()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.stepSearh) {
|
||||
this._loadNodeData((data) => {
|
||||
this._treeData = data
|
||||
this._updateBindData()
|
||||
})
|
||||
} else {
|
||||
this._loadAllData((data) => {
|
||||
this._treeData = []
|
||||
this._extractTree(data, this._treeData, null)
|
||||
this._updateBindData()
|
||||
})
|
||||
}
|
||||
},
|
||||
_loadAllData(callback) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
gettree: true,
|
||||
startwith: `${this.selfField}=='${this.value}'`
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
callback(res.result.data)
|
||||
this.onDataChange()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
_loadNodeData(callback, pw) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.getCommand({
|
||||
field: this.postField,
|
||||
where: pw || this.postWhere,
|
||||
pageSize: 500
|
||||
}).then((res) => {
|
||||
this.loading = false
|
||||
callback(res.result.data)
|
||||
this.onDataChange()
|
||||
}).catch((err) => {
|
||||
this.loading = false
|
||||
this.errorMessage = err
|
||||
})
|
||||
},
|
||||
_updateSelected() {
|
||||
var dl = this.dataList
|
||||
var sl = this.selected
|
||||
for (var i = 0; i < sl.length; i++) {
|
||||
var value = sl[i].value
|
||||
var dl2 = dl[i]
|
||||
for (var j = 0; j < dl2.length; j++) {
|
||||
var item2 = dl2[j]
|
||||
if (item2.value === value) {
|
||||
sl[i].text = item2.text
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_updateBindData(node) {
|
||||
const {
|
||||
dataList,
|
||||
hasNodes
|
||||
} = this._filterData(this._treeData, this.selected)
|
||||
|
||||
let isLeaf = this._stepSearh === false && !hasNodes
|
||||
|
||||
if (node) {
|
||||
node.isLeaf = isLeaf
|
||||
}
|
||||
|
||||
this.dataList = dataList
|
||||
this.selectedIndex = dataList.length - 1
|
||||
|
||||
if (!isLeaf && this.selected.length < dataList.length) {
|
||||
this.selected.push({
|
||||
value: null,
|
||||
text: "请选择"
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isLeaf,
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
_filterData(data, paths) {
|
||||
let dataList = []
|
||||
|
||||
let hasNodes = true
|
||||
|
||||
dataList.push(data.filter((item) => {
|
||||
return item.parent_value === undefined
|
||||
}))
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
var value = paths[i].value
|
||||
var nodes = data.filter((item) => {
|
||||
return item.parent_value === value
|
||||
})
|
||||
|
||||
if (nodes.length) {
|
||||
dataList.push(nodes)
|
||||
} else {
|
||||
hasNodes = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dataList,
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
_extractTree(nodes, result, parent_value) {
|
||||
let list = result || []
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
|
||||
let child = {}
|
||||
for (let key in node) {
|
||||
if (key !== 'children') {
|
||||
child[key] = node[key]
|
||||
}
|
||||
}
|
||||
if (parent_value !== null) {
|
||||
child.parent_value = parent_value
|
||||
}
|
||||
result.push(child)
|
||||
|
||||
let children = node.children
|
||||
if (children) {
|
||||
this._extractTree(children, result, node.value)
|
||||
}
|
||||
}
|
||||
},
|
||||
_extractTreePath(nodes, result) {
|
||||
let list = result || []
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
|
||||
let child = {}
|
||||
for (let key in node) {
|
||||
if (key !== 'children') {
|
||||
child[key] = node[key]
|
||||
}
|
||||
}
|
||||
result.push(child)
|
||||
|
||||
let children = node.children
|
||||
if (children) {
|
||||
this._extractTreePath(children, result)
|
||||
}
|
||||
}
|
||||
},
|
||||
_findNodePath(key, nodes, path) {
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let {
|
||||
value,
|
||||
text,
|
||||
children
|
||||
} = nodes[i]
|
||||
|
||||
path.push({
|
||||
value,
|
||||
text
|
||||
})
|
||||
|
||||
if (value === key) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (children) {
|
||||
this._findNodePath(key, children, path)
|
||||
} else {
|
||||
path.pop()
|
||||
}
|
||||
}
|
||||
},
|
||||
_processLocalData() {
|
||||
this._treeData = []
|
||||
this._extractTree(this.localdata, this._treeData)
|
||||
|
||||
var inputValue = this.value
|
||||
if (!inputValue || !inputValue.length) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof inputValue === "string") {
|
||||
let nodePath = []
|
||||
this._findNodePath(inputValue, this.localdata, nodePath)
|
||||
this.selected = nodePath
|
||||
} else {
|
||||
let selected = new Array(inputValue.length)
|
||||
for (var i = 0; i < inputValue.length; i++) {
|
||||
selected[i] = {
|
||||
value: inputValue[i]
|
||||
}
|
||||
}
|
||||
this.selected = selected
|
||||
}
|
||||
|
||||
//this._updateBindData()
|
||||
}
|
||||
}
|
||||
}
|
||||
254
components/uni-data-pickerview/uni-data-pickerview.vue
Normal file
254
components/uni-data-pickerview/uni-data-pickerview.vue
Normal file
@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<view class="uni-data-pickerview">
|
||||
<scroll-view class="selected-area" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<view class="selected-item" :class="{'selected-item-active':index==selectedIndex}" v-for="(item,index) in selected" :key="index" v-if="item.text" @click="handleSelect(index)">
|
||||
<text class="">{{item.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tab-c">
|
||||
<scroll-view class="list" v-for="(child, i) in dataList" :key="i" v-if="i==selectedIndex" scroll-y="false">
|
||||
<view class="item" v-for="(item, j) in child" :key="j" @click="handleNodeClick(i, j)">
|
||||
<text class="item-text">{{item.text}}</text>
|
||||
<view class="check" v-if="selected.length > i && item.value == selected[i].value"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
</view>
|
||||
<view class="error-message" v-if="errorMessage">
|
||||
<text class="error-text">{{errorMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataPicker from "./uni-data-picker.js"
|
||||
|
||||
/**
|
||||
* uni-data-pickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://uniapp.dcloud.net.cn/uniCloud/uni-data-picker
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
managedMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
if (this.managedMode) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.load()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = []
|
||||
this.selectedIndex = 0
|
||||
this.load()
|
||||
},
|
||||
load() {
|
||||
if (this.isLocaldata) {
|
||||
this.loadData()
|
||||
} else if (this.value.length) {
|
||||
this.getTreePath((res) => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSelect(index) {
|
||||
this.selectedIndex = index
|
||||
},
|
||||
handleNodeClick(i, j) {
|
||||
const node = this.dataList[i][j]
|
||||
const {
|
||||
value,
|
||||
text
|
||||
} = node
|
||||
|
||||
if (i < this.selected.length - 1) {
|
||||
this.selected.splice(i, this.selected.length - i)
|
||||
this.selected.push(node)
|
||||
} else if (i === this.selected.length - 1) {
|
||||
this.selected[i] = node
|
||||
}
|
||||
|
||||
if (node.isLeaf) {
|
||||
this.onSelectedChange(node, node.isLeaf)
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
isLeaf,
|
||||
hasNodes
|
||||
} = this._updateBindData()
|
||||
|
||||
if (this.isLocaldata && (!hasNodes || isLeaf)) {
|
||||
this.onSelectedChange(node, true)
|
||||
return
|
||||
}
|
||||
|
||||
if (!isLeaf && !hasNodes) {
|
||||
this._loadNodeData((data) => {
|
||||
if (!data.length) {
|
||||
node.isLeaf = true
|
||||
} else {
|
||||
this._treeData.push(...data)
|
||||
this._updateBindData(node)
|
||||
}
|
||||
this.onSelectedChange(node, node.isLeaf)
|
||||
}, this.nodeWhere)
|
||||
return
|
||||
}
|
||||
|
||||
this.onSelectedChange(node, false)
|
||||
},
|
||||
updateData(data) {
|
||||
this._treeData = data.treeData
|
||||
this.selected = data.selected
|
||||
if (!this._treeData.length) {
|
||||
this.loadData()
|
||||
} else {
|
||||
//this.selected = data.selected
|
||||
this._updateBindData()
|
||||
}
|
||||
},
|
||||
onDataChange() {
|
||||
this.$emit('datachange')
|
||||
},
|
||||
onSelectedChange(node, isLeaf) {
|
||||
if (isLeaf) {
|
||||
this._dispatchEvent()
|
||||
} else if (node) {
|
||||
this.$emit('nodeclick', node)
|
||||
}
|
||||
},
|
||||
_dispatchEvent() {
|
||||
this.$emit('change', this.selected.slice(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-data-pickerview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
opacity: .9;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.selected-item-active {
|
||||
border-bottom: 2px solid #007aff;
|
||||
}
|
||||
|
||||
.selected-item-text {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.tab-c {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
flex: 1;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
border: 2px solid #007aff;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
transform-origin: center;
|
||||
transition: all 0.3s;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
||||
191
components/uni-dateformat/date-format.js
Normal file
191
components/uni-dateformat/date-format.js
Normal file
@ -0,0 +1,191 @@
|
||||
// yyyy-MM-dd hh:mm:ss.SSS 所有支持的类型
|
||||
function pad(str, length = 2) {
|
||||
str += ''
|
||||
while (str.length < length) {
|
||||
str = '0' + str
|
||||
}
|
||||
return str.slice(-length)
|
||||
}
|
||||
|
||||
const parser = {
|
||||
yyyy: (dateObj) => {
|
||||
return pad(dateObj.year, 4)
|
||||
},
|
||||
yy: (dateObj) => {
|
||||
return pad(dateObj.year)
|
||||
},
|
||||
MM: (dateObj) => {
|
||||
return pad(dateObj.month)
|
||||
},
|
||||
M: (dateObj) => {
|
||||
return dateObj.month
|
||||
},
|
||||
dd: (dateObj) => {
|
||||
return pad(dateObj.day)
|
||||
},
|
||||
d: (dateObj) => {
|
||||
return dateObj.day
|
||||
},
|
||||
hh: (dateObj) => {
|
||||
return pad(dateObj.hour)
|
||||
},
|
||||
h: (dateObj) => {
|
||||
return dateObj.hour
|
||||
},
|
||||
mm: (dateObj) => {
|
||||
return pad(dateObj.minute)
|
||||
},
|
||||
m: (dateObj) => {
|
||||
return dateObj.minute
|
||||
},
|
||||
ss: (dateObj) => {
|
||||
return pad(dateObj.second)
|
||||
},
|
||||
s: (dateObj) => {
|
||||
return dateObj.second
|
||||
},
|
||||
SSS: (dateObj) => {
|
||||
return pad(dateObj.millisecond, 3)
|
||||
},
|
||||
S: (dateObj) => {
|
||||
return dateObj.millisecond
|
||||
},
|
||||
}
|
||||
|
||||
// 这都n年了iOS依然不认识2020-12-12,需要转换为2020/12/12
|
||||
function getDate(time) {
|
||||
if (time instanceof Date) {
|
||||
return time
|
||||
}
|
||||
switch (typeof time) {
|
||||
case 'string':
|
||||
return new Date(time.replace(/-/g, '/'))
|
||||
default:
|
||||
return new Date(time)
|
||||
}
|
||||
}
|
||||
|
||||
export function formatDate(date, format = 'yyyy/MM/dd hh:mm:ss') {
|
||||
if (!date && date !== 0) {
|
||||
return '-'
|
||||
}
|
||||
date = getDate(date)
|
||||
const dateObj = {
|
||||
year: date.getFullYear(),
|
||||
month: date.getMonth() + 1,
|
||||
day: date.getDate(),
|
||||
hour: date.getHours(),
|
||||
minute: date.getMinutes(),
|
||||
second: date.getSeconds(),
|
||||
millisecond: date.getMilliseconds()
|
||||
}
|
||||
const tokenRegExp = /yyyy|yy|MM|M|dd|d|hh|h|mm|m|ss|s|SSS|SS|S/
|
||||
let flag = true
|
||||
let result = format
|
||||
while (flag) {
|
||||
flag = false
|
||||
result = result.replace(tokenRegExp, function(matched) {
|
||||
flag = true
|
||||
return parser[matched](dateObj)
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function friendlyDate(time, {
|
||||
locale = 'zh',
|
||||
threshold = [60000, 3600000],
|
||||
format = 'yyyy/MM/dd hh:mm:ss'
|
||||
}) {
|
||||
if (!time && time !== 0) {
|
||||
return '-'
|
||||
}
|
||||
const localeText = {
|
||||
zh: {
|
||||
year: '年',
|
||||
month: '月',
|
||||
day: '天',
|
||||
hour: '小时',
|
||||
minute: '分钟',
|
||||
second: '秒',
|
||||
ago: '前',
|
||||
later: '后',
|
||||
justNow: '刚刚',
|
||||
soon: '马上',
|
||||
template: '{num}{unit}{suffix}'
|
||||
},
|
||||
en: {
|
||||
year: 'year',
|
||||
month: 'month',
|
||||
day: 'day',
|
||||
hour: 'hour',
|
||||
minute: 'minute',
|
||||
second: 'second',
|
||||
ago: 'ago',
|
||||
later: 'later',
|
||||
justNow: 'just now',
|
||||
soon: 'soon',
|
||||
template: '{num} {unit} {suffix}'
|
||||
}
|
||||
}
|
||||
const text = localeText[locale] || localeText.zh
|
||||
let date = getDate(time)
|
||||
let ms = date.getTime() - Date.now()
|
||||
let absMs = Math.abs(ms)
|
||||
if (absMs < threshold[0]) {
|
||||
return ms < 0 ? text.justNow : text.soon
|
||||
}
|
||||
if (absMs >= threshold[1]) {
|
||||
return formatDate(date, format)
|
||||
}
|
||||
let num
|
||||
let unit
|
||||
let suffix = text.later
|
||||
if (ms < 0) {
|
||||
suffix = text.ago
|
||||
ms = -ms
|
||||
}
|
||||
const seconds = Math.floor((ms) / 1000)
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const days = Math.floor(hours / 24)
|
||||
const months = Math.floor(days / 30)
|
||||
const years = Math.floor(months / 12)
|
||||
switch (true) {
|
||||
case years > 0:
|
||||
num = years
|
||||
unit = text.year
|
||||
break
|
||||
case months > 0:
|
||||
num = months
|
||||
unit = text.month
|
||||
break
|
||||
case days > 0:
|
||||
num = days
|
||||
unit = text.day
|
||||
break
|
||||
case hours > 0:
|
||||
num = hours
|
||||
unit = text.hour
|
||||
break
|
||||
case minutes > 0:
|
||||
num = minutes
|
||||
unit = text.minute
|
||||
break
|
||||
default:
|
||||
num = seconds
|
||||
unit = text.second
|
||||
break
|
||||
}
|
||||
|
||||
if (locale === 'en') {
|
||||
if (num === 1) {
|
||||
num = 'a'
|
||||
} else {
|
||||
unit += 's'
|
||||
}
|
||||
}
|
||||
|
||||
return text.template.replace(/{\s*num\s*}/g, num + '').replace(/{\s*unit\s*}/g, unit).replace(/{\s*suffix\s*}/g,
|
||||
suffix)
|
||||
}
|
||||
88
components/uni-dateformat/uni-dateformat.vue
Normal file
88
components/uni-dateformat/uni-dateformat.vue
Normal file
@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<text>{{dateShow}}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Dateformat 日期格式化
|
||||
* @description 日期格式化组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3279
|
||||
* @property {Object|String|Number} date 日期对象/日期字符串/时间戳
|
||||
* @property {String} locale 格式化使用的语言
|
||||
* @value zh 中文
|
||||
* @value en 英文
|
||||
* @property {Array} threshold 应用不同类型格式化的阈值
|
||||
* @property {String} format 输出日期字符串时的格式
|
||||
*/
|
||||
import {
|
||||
friendlyDate
|
||||
} from './date-format.js'
|
||||
export default {
|
||||
name: 'uniDateformat',
|
||||
props: {
|
||||
date: {
|
||||
type: [Object, String, Number],
|
||||
default () {
|
||||
return Date.now()
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
type: String,
|
||||
default: 'zh',
|
||||
},
|
||||
threshold: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [0, 0]
|
||||
}
|
||||
},
|
||||
format: {
|
||||
type: String,
|
||||
default: 'yyyy/MM/dd hh:mm:ss'
|
||||
},
|
||||
// refreshRate使用不当可能导致性能问题,谨慎使用
|
||||
refreshRate: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
refreshMark: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dateShow() {
|
||||
this.refreshMark
|
||||
return friendlyDate(this.date, {
|
||||
locale: this.locale,
|
||||
threshold: this.threshold,
|
||||
format: this.format
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
refreshRate: {
|
||||
handler() {
|
||||
this.setAutoRefresh()
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
this.refreshMark++
|
||||
},
|
||||
setAutoRefresh() {
|
||||
clearInterval(this.refreshInterval)
|
||||
if (this.refreshRate) {
|
||||
this.refreshInterval = setInterval(() => {
|
||||
this.refresh()
|
||||
}, parseInt(this.refreshRate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
358
components/uni-datetime-picker/uni-datetime-picker.vue
Normal file
358
components/uni-datetime-picker/uni-datetime-picker.vue
Normal file
@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<view class="uni-datetime-picker">
|
||||
<view @click="tiggerTimePicker">
|
||||
<slot>
|
||||
<view class="uni-datetime-picker-timebox uni-datetime-picker-flex">
|
||||
{{time}}
|
||||
<view v-if="!time" class="uni-datetime-picker-time">选择日期时间</view>
|
||||
<view class="uni-datetime-picker-down-arrow"></view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view v-if="visible" class="uni-datetime-picker-mask" @click="initTimePicker"></view>
|
||||
<view v-if="visible" class="uni-datetime-picker-popup">
|
||||
<view class="uni-title">
|
||||
设置日期和时间
|
||||
</view>
|
||||
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd" @change="bindDateChange">
|
||||
<picker-view-column class="uni-datetime-picker-hyphen">
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">{{item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column class="uni-datetime-picker-hyphen">
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">{{item < 10 ? '0' + item : item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">{{item < 10 ? '0' + item : item}}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
|
||||
<picker-view-column class="uni-datetime-picker-colon">
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">{{item < 10 ? '0' + item : item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column class="uni-datetime-picker-colon">
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">{{item < 10 ? '0' + item : item}}</view>
|
||||
</picker-view-column>
|
||||
<picker-view-column>
|
||||
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">{{item < 10 ? '0' + item : item}}</view>
|
||||
</picker-view-column>
|
||||
</picker-view>
|
||||
<view class="uni-datetime-picker-btn">
|
||||
<view class="" @click="clearTime">重置</view>
|
||||
<view class="uni-datetime-picker-btn-group">
|
||||
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">取消</view>
|
||||
<view class="" @click="setTime">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
time: '',
|
||||
years: [],
|
||||
months: [],
|
||||
days: [],
|
||||
hours: [],
|
||||
minutes: [],
|
||||
seconds: [],
|
||||
year: 1900,
|
||||
month: 0,
|
||||
day: 0,
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
indicatorStyle: `height: 50px;`,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'datetime-local'
|
||||
},
|
||||
timestamp: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
maxYear: {
|
||||
type: [Number, String],
|
||||
default: 2100
|
||||
},
|
||||
minYear: {
|
||||
type: [Number, String],
|
||||
default: 1900
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
ymd() {
|
||||
return [this.year - this.minYear, this.month - 1, this.day - 1]
|
||||
},
|
||||
hms() {
|
||||
return [this.hour, this.minute, this.second]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.parseValue(this.value)
|
||||
this.initTime()
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm('uniForms')
|
||||
this.formItem = this.getForm('uniFormsItem')
|
||||
|
||||
if (this.formItem) {
|
||||
if (this.formItem.name) {
|
||||
this.rename = this.formItem.name
|
||||
this.form.inputChildrens.push(this)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const date = new Date()
|
||||
for (let i = this.minYear; i <= this.maxYear; i++) {
|
||||
this.years.push(i)
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 12; i++) {
|
||||
this.months.push(i)
|
||||
}
|
||||
|
||||
for (let i = 1; i <= 31; i++) {
|
||||
this.days.push(i)
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 23; i++) {
|
||||
this.hours.push(i)
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 59; i++) {
|
||||
this.minutes.push(i)
|
||||
}
|
||||
|
||||
for (let i = 0; i <= 59; i++) {
|
||||
this.seconds.push(i)
|
||||
}
|
||||
this.parseValue(this.value)
|
||||
if (this.value) {
|
||||
this.initTime()
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
parseDateTime(datetime) {
|
||||
let defaultDate = null
|
||||
if (!datetime) {
|
||||
defaultDate = new Date()
|
||||
} else {
|
||||
defaultDate = new Date(datetime)
|
||||
}
|
||||
this.year = defaultDate.getFullYear()
|
||||
if (this.year < this.minYear || this.year > this.maxYear) {
|
||||
const now = Date.now()
|
||||
this.parseDateTime(now)
|
||||
return
|
||||
}
|
||||
this.month = defaultDate.getMonth() + 1
|
||||
this.day = defaultDate.getDate()
|
||||
this.hour = defaultDate.getHours()
|
||||
this.minute = defaultDate.getMinutes()
|
||||
this.second = defaultDate.getSeconds()
|
||||
},
|
||||
parseValue(defaultTime) {
|
||||
if (Number(defaultTime)) {
|
||||
defaultTime = parseInt(defaultTime)
|
||||
}
|
||||
this.parseDateTime(defaultTime)
|
||||
},
|
||||
bindDateChange(e) {
|
||||
const val = e.detail.value
|
||||
this.year = this.years[val[0]]
|
||||
this.month = this.months[val[1]]
|
||||
this.day = this.days[val[2]]
|
||||
},
|
||||
bindTimeChange(e) {
|
||||
const val = e.detail.value
|
||||
this.hour = this.hours[val[0]]
|
||||
this.minute = this.minutes[val[1]]
|
||||
this.second = this.seconds[val[2]]
|
||||
},
|
||||
initTimePicker() {
|
||||
// if (!this.time) {
|
||||
// this.parseValue()
|
||||
// }
|
||||
this.parseValue(this.value)
|
||||
this.visible = !this.visible
|
||||
},
|
||||
tiggerTimePicker() {
|
||||
this.visible = !this.visible
|
||||
},
|
||||
clearTime() {
|
||||
this.time = ''
|
||||
this.formItem && this.formItem.setValue(this.time)
|
||||
this.$emit('change', this.time)
|
||||
this.tiggerTimePicker()
|
||||
},
|
||||
initTime() {
|
||||
this.time = this.createDomSting()
|
||||
if (!this.timestamp) {
|
||||
this.formItem && this.formItem.setValue(this.time)
|
||||
this.$emit('change', this.time)
|
||||
} else {
|
||||
this.formItem && this.formItem.setValue(this.createTimeStamp(this.time))
|
||||
this.$emit('change', this.createTimeStamp(this.time))
|
||||
}
|
||||
},
|
||||
setTime() {
|
||||
this.initTime()
|
||||
this.tiggerTimePicker()
|
||||
},
|
||||
createTimeStamp(time) {
|
||||
return Date.parse(new Date(time))
|
||||
},
|
||||
createDomSting() {
|
||||
const yymmdd = this.year +
|
||||
'-' +
|
||||
(this.month < 10 ? '0' + this.month : this.month) +
|
||||
'-' +
|
||||
(this.day < 10 ? '0' + this.day : this.day) +
|
||||
' ' +
|
||||
(this.hour < 10 ? '0' + this.hour : this.hour) +
|
||||
':' +
|
||||
(this.minute < 10 ? '0' + this.minute : this.minute) +
|
||||
':' +
|
||||
(this.second < 10 ? '0' + this.second : this.second)
|
||||
|
||||
return yymmdd
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-datetime-picker-view {
|
||||
width: 100%;
|
||||
height: 130px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-item {
|
||||
line-height: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-btn {
|
||||
margin-top: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: blue;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-btn-group {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-cancel {
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-mask {
|
||||
position: fixed;
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
transition-duration: 0.3s;
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-popup {
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
width: 270px;
|
||||
background-color: #fff;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
transition-duration: 0.3s;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-time {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-colon::after {
|
||||
content: ':';
|
||||
position: absolute;
|
||||
top: 53px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-hyphen::after {
|
||||
content: '-';
|
||||
position: absolute;
|
||||
top: 53px;
|
||||
right: -2px;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-timebox {
|
||||
border: 1px solid #E5E5E5;
|
||||
border-radius: 5px;
|
||||
padding: 7px 10px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-down-arrow {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-down-arrow::after {
|
||||
display: inline-block;
|
||||
content: " ";
|
||||
height: 9px;
|
||||
width: 9px;
|
||||
border-width: 0 1px 1px 0;
|
||||
border-color: #E5E5E5;
|
||||
border-style: solid;
|
||||
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
|
||||
transform-origin: center;
|
||||
transition: transform .3s;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 5px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
|
||||
.uni-datetime-picker-flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
166
components/uni-drawer/uni-drawer.vue
Normal file
166
components/uni-drawer/uni-drawer.vue
Normal file
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<view v-if="visibleSync" :class="{ 'uni-drawer--visible': showDrawer }" class="uni-drawer" @touchmove.stop.prevent="clear">
|
||||
<view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close('mask')" />
|
||||
<view class="uni-drawer__content" :class="{'uni-drawer--right': rightMode,'uni-drawer--left': !rightMode, 'uni-drawer__content--visible': showDrawer}" :style="{width:drawerWidth+'px'}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Drawer 抽屉
|
||||
* @description 抽屉侧滑菜单
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=26
|
||||
* @property {Boolean} mask = [true | false] 是否显示遮罩
|
||||
* @property {Boolean} maskClick = [true | false] 点击遮罩是否关闭
|
||||
* @property {Boolean} mode = [left | right] Drawer 滑出位置
|
||||
* @value left 从左侧滑出
|
||||
* @value right 从右侧侧滑出
|
||||
* @property {Number} width 抽屉的宽度 ,仅 vue 页面生效
|
||||
* @event {Function} close 组件关闭时触发事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDrawer',
|
||||
props: {
|
||||
/**
|
||||
* 显示模式(左、右),只在初始化生效
|
||||
*/
|
||||
mode: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 蒙层显示状态
|
||||
*/
|
||||
mask: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 遮罩是否可点击关闭
|
||||
*/
|
||||
maskClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 抽屉宽度
|
||||
*/
|
||||
width: {
|
||||
type: Number,
|
||||
default: 220
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visibleSync: false,
|
||||
showDrawer: false,
|
||||
rightMode: false,
|
||||
watchTimer: null,
|
||||
drawerWidth: 220
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// #ifndef APP-NVUE
|
||||
this.drawerWidth = this.width
|
||||
// #endif
|
||||
this.rightMode = this.mode === 'right'
|
||||
},
|
||||
methods: {
|
||||
clear() {},
|
||||
close(type) {
|
||||
// fixed by mehaotian 抽屉尚未完全关闭或遮罩禁止点击时不触发以下逻辑
|
||||
if ((type === 'mask' && !this.maskClick) || !this.visibleSync) return
|
||||
this._change('showDrawer', 'visibleSync', false)
|
||||
},
|
||||
open() {
|
||||
// fixed by mehaotian 处理重复点击打开的事件
|
||||
if (this.visibleSync) return
|
||||
this._change('visibleSync', 'showDrawer', true)
|
||||
},
|
||||
_change(param1, param2, status) {
|
||||
this[param1] = status
|
||||
if (this.watchTimer) {
|
||||
clearTimeout(this.watchTimer)
|
||||
}
|
||||
this.watchTimer = setTimeout(() => {
|
||||
this[param2] = status
|
||||
this.$emit('change', status)
|
||||
}, status ? 50 : 300)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-drawer {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.uni-drawer__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 220px;
|
||||
bottom: 0;
|
||||
background-color: #ffffff;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.uni-drawer--left {
|
||||
left: 0;
|
||||
/* #ifdef APP-NVUE */
|
||||
transform: translateX(-220px);
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
transform: translateX(-100%);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-drawer--right {
|
||||
right: 0;
|
||||
/* #ifdef APP-NVUE */
|
||||
transform: translateX(220px);
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
transform: translateX(100%);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-drawer__content--visible {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
|
||||
.uni-drawer__mask {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.uni-drawer__mask--visible {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
56
components/uni-easyinput/common.js
Normal file
56
components/uni-easyinput/common.js
Normal file
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @desc 函数防抖
|
||||
* @param func 目标函数
|
||||
* @param wait 延迟执行毫秒数
|
||||
* @param immediate true - 立即执行, false - 延迟执行
|
||||
*/
|
||||
export const debounce = function(func, wait = 1000, immediate = true) {
|
||||
let timer;
|
||||
console.log(1);
|
||||
return function() {
|
||||
console.log(123);
|
||||
let context = this,
|
||||
args = arguments;
|
||||
if (timer) clearTimeout(timer);
|
||||
if (immediate) {
|
||||
let callNow = !timer;
|
||||
timer = setTimeout(() => {
|
||||
timer = null;
|
||||
}, wait);
|
||||
if (callNow) func.apply(context, args);
|
||||
} else {
|
||||
timer = setTimeout(() => {
|
||||
func.apply(context, args);
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @desc 函数节流
|
||||
* @param func 函数
|
||||
* @param wait 延迟执行毫秒数
|
||||
* @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
|
||||
*/
|
||||
export const throttle = (func, wait = 1000, type = 1) => {
|
||||
let previous = 0;
|
||||
let timeout;
|
||||
return function() {
|
||||
let context = this;
|
||||
let args = arguments;
|
||||
if (type === 1) {
|
||||
let now = Date.now();
|
||||
|
||||
if (now - previous > wait) {
|
||||
func.apply(context, args);
|
||||
previous = now;
|
||||
}
|
||||
} else if (type === 2) {
|
||||
if (!timeout) {
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null;
|
||||
func.apply(context, args)
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
399
components/uni-easyinput/uni-easyinput.vue
Normal file
399
components/uni-easyinput/uni-easyinput.vue
Normal file
@ -0,0 +1,399 @@
|
||||
<template>
|
||||
<view class="uni-easyinput" :class="{'uni-easyinput-error':msg}" :style="{color:inputBorder && msg?'#dd524d':styles.color}">
|
||||
<view class="uni-easyinput__content" :class="{'is-input-border':inputBorder ,'is-input-error-border':inputBorder && msg,'is-textarea':type==='textarea','is-disabled':disabled}" :style="{'border-color':inputBorder && msg?'#dd524d':styles.borderColor,'background-color':disabled?styles.disableColor:'#fff'}">
|
||||
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')"></uni-icons>
|
||||
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{'input-padding':inputBorder}" :name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"></textarea>
|
||||
<input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input" :style="{
|
||||
'padding-right':type === 'password' ||clearable || prefixIcon?'':'10px',
|
||||
'padding-left':prefixIcon?'':'10px'
|
||||
}" :name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" @focus="onFocus" @blur="onBlur" @input="onInput" @confirm="onConfirm" />
|
||||
<template v-if="type === 'password'">
|
||||
<uni-icons v-if="val != '' " class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" :type="showPassword?'eye-slash-filled':'eye-filled'" :size="18" color="#c0c4cc" @click="onEyes"></uni-icons>
|
||||
</template>
|
||||
<template v-else-if="suffixIcon">
|
||||
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')"></uni-icons>
|
||||
</template>
|
||||
<template v-else>
|
||||
<uni-icons class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize" v-if="clearable && focused && val " color="#c0c4cc" @click="onClear"></uni-icons>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Field 输入框
|
||||
* @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21001
|
||||
* @property {String| Number} value 输入内容
|
||||
* @property {String } type 输入框的类型(默认text) password/text/textarea/..
|
||||
* @value text 文本输入键盘
|
||||
* @value textarea 多行文本输入键盘
|
||||
* @value password 密码输入键盘
|
||||
* @value number 数字输入键盘,注意iOS上app-vue弹出的数字键盘并非9宫格方式
|
||||
* @value idcard 身份证输入键盘,信、支付宝、百度、QQ小程序
|
||||
* @value digit 带小数点的数字键盘 ,App的nvue页面、微信、支付宝、百度、头条、QQ小程序支持
|
||||
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
|
||||
* @property {Boolean} autoHeight 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @property {String } placeholder 输入框的提示文字
|
||||
* @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd"
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} disabled 是否不可输入(默认false)
|
||||
* @property {Number } maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
|
||||
* @property {String } confirmType 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
|
||||
* @property {Number } clearSize 清除图标的大小,单位px(默认15)
|
||||
* @property {String} prefixIcon 输入框头部图标
|
||||
* @property {String} suffixIcon 输入框尾部图标
|
||||
* @property {Boolean} trim 是否自动去除两端的空格
|
||||
* @value both 去除两端空格
|
||||
* @value left 去除左侧空格
|
||||
* @value right 去除右侧空格
|
||||
* @value start 去除左侧空格
|
||||
* @value end 去除右侧空格
|
||||
* @value all 去除全部空格
|
||||
* @value none 不去除空格
|
||||
* @property {Boolean} inputBorder 是否显示input输入框的边框(默认false)
|
||||
* @property {Object} styles 自定义颜色
|
||||
* @event {Function} input 输入框内容发生变化时触发
|
||||
* @event {Function} focus 输入框获得焦点时触发
|
||||
* @event {Function} blur 输入框失去焦点时触发
|
||||
* @event {Function} confirm 点击完成按钮时触发
|
||||
* @event {Function} iconClick 点击图标时触发
|
||||
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
|
||||
*/
|
||||
|
||||
import {
|
||||
debounce,
|
||||
throttle
|
||||
} from './common.js'
|
||||
|
||||
export default {
|
||||
name: 'uni-easyinput',
|
||||
props: {
|
||||
name: String,
|
||||
value: [Number, String],
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
placeholder: String,
|
||||
placeholderStyle: String,
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 140
|
||||
},
|
||||
confirmType: {
|
||||
type: String,
|
||||
default: 'done'
|
||||
},
|
||||
// 清除按钮的大小
|
||||
clearSize: {
|
||||
type: [Number, String],
|
||||
default: 15
|
||||
},
|
||||
// 是否显示 input 边框
|
||||
inputBorder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
prefixIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
suffixIcon: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
// 自定义样式
|
||||
styles: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
color: '#333',
|
||||
disableColor: '#eee',
|
||||
borderColor: '#e5e5e5'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
errMsg: '',
|
||||
val: '',
|
||||
showMsg: '',
|
||||
border: false,
|
||||
isFirstBorder: false,
|
||||
showClearIcon: false,
|
||||
showPassword: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
msg() {
|
||||
return this.errorMessage || this.errMsg;
|
||||
},
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
if (this.errMsg) this.errMsg = ''
|
||||
this.val = newVal
|
||||
if (this.form && this.formItem) {
|
||||
this.formItem.setValue(newVal)
|
||||
}
|
||||
},
|
||||
focus(newVal) {
|
||||
this.$nextTick(() => {
|
||||
this.focused = this.focus
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.val = this.value
|
||||
this.form = this.getForm('uniForms')
|
||||
this.formItem = this.getForm('uniFormsItem')
|
||||
if (this.form && this.formItem) {
|
||||
if (this.formItem.name) {
|
||||
this.rename = this.formItem.name
|
||||
this.form.inputChildrens.push(this)
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// this.onInput = throttle(this.input, 500)
|
||||
this.$nextTick(() => {
|
||||
this.focused = this.focus
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化变量值
|
||||
*/
|
||||
init() {
|
||||
|
||||
},
|
||||
onClickIcon(type) {
|
||||
this.$emit('iconClick', type)
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false;
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
onEyes() {
|
||||
this.showPassword = !this.showPassword
|
||||
},
|
||||
onInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if (this.trim) {
|
||||
if (typeof(this.trim) === 'boolean' && this.trim) {
|
||||
value = this.trimStr(value)
|
||||
}
|
||||
if (typeof(this.trim) === 'string') {
|
||||
value = this.trimStr(value, this.trim)
|
||||
}
|
||||
};
|
||||
if (this.errMsg) this.errMsg = ''
|
||||
this.val = value
|
||||
this.$emit('input', value);
|
||||
},
|
||||
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
let value = event.detail.value;
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100);
|
||||
this.$emit('blur', event);
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.val = '';
|
||||
this.$emit('input', '');
|
||||
},
|
||||
fieldClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
trimStr(str, pos = 'both') {
|
||||
if (pos === 'both') {
|
||||
return str.trim();
|
||||
} else if (pos === 'left') {
|
||||
return str.trimLeft();
|
||||
} else if (pos === 'right') {
|
||||
return str.trimRight();
|
||||
} else if (pos === 'start') {
|
||||
return str.trimStart()
|
||||
} else if (pos === 'end') {
|
||||
return str.trimEnd()
|
||||
} else if (pos === 'all') {
|
||||
return str.replace(/\s+/g, '');
|
||||
} else if (pos === 'none') {
|
||||
return str;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-easyinput {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-easyinput__content {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.uni-easyinput__content-input {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
line-height: 2;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.is-textarea {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.is-textarea-icon {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.uni-easyinput__content-textarea {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
width: auto;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
padding-top: 6px;
|
||||
padding-bottom: 10px;
|
||||
min-height: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.input-padding {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.content-clear-icon {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.label-icon {
|
||||
margin-right: 5px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.is-input-border {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.is-required {
|
||||
color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-error-message {
|
||||
position: absolute;
|
||||
bottom: -17px;
|
||||
left: 0;
|
||||
line-height: 12px;
|
||||
color: #dd524d;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.uni-error-msg--boeder {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.is-input-error-border {
|
||||
border-color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-easyinput--border {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
.uni-easyinput-error {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.is-first-border {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
background-color: #eee;
|
||||
}
|
||||
</style>
|
||||
442
components/uni-fab/uni-fab.vue
Normal file
442
components/uni-fab/uni-fab.vue
Normal file
@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<view class="uni-cursor-point">
|
||||
<view v-if="popMenu && (leftBottom||rightBottom||leftTop||rightTop) && content.length > 0" :class="{
|
||||
'uni-fab--leftBottom': leftBottom,
|
||||
'uni-fab--rightBottom': rightBottom,
|
||||
'uni-fab--leftTop': leftTop,
|
||||
'uni-fab--rightTop': rightTop
|
||||
}" class="uni-fab">
|
||||
<view :class="{
|
||||
'uni-fab__content--left': horizontal === 'left',
|
||||
'uni-fab__content--right': horizontal === 'right',
|
||||
'uni-fab__content--flexDirection': direction === 'vertical',
|
||||
'uni-fab__content--flexDirectionStart': flexDirectionStart,
|
||||
'uni-fab__content--flexDirectionEnd': flexDirectionEnd,
|
||||
'uni-fab__content--other-platform': !isAndroidNvue
|
||||
}" :style="{ width: boxWidth, height: boxHeight, backgroundColor: styles.backgroundColor }" class="uni-fab__content" elevation="5">
|
||||
<view v-if="flexDirectionStart || horizontalLeft" class="uni-fab__item uni-fab__item--first" />
|
||||
<view v-for="(item, index) in content" :key="index" :class="{ 'uni-fab__item--active': isShow }" class="uni-fab__item" @click="_onItemClick(index, item)">
|
||||
<image :src="item.active ? item.selectedIconPath : item.iconPath" class="uni-fab__item-image" mode="widthFix" />
|
||||
<text class="uni-fab__item-text" :style="{ color: item.active ? styles.selectedColor : styles.color }">{{ item.text }}</text>
|
||||
</view>
|
||||
<view v-if="flexDirectionEnd || horizontalRight" class="uni-fab__item uni-fab__item--first" />
|
||||
</view>
|
||||
</view>
|
||||
<view :class="{
|
||||
'uni-fab__circle--leftBottom': leftBottom,
|
||||
'uni-fab__circle--rightBottom': rightBottom,
|
||||
'uni-fab__circle--leftTop': leftTop,
|
||||
'uni-fab__circle--rightTop': rightTop,
|
||||
'uni-fab__content--other-platform': !isAndroidNvue
|
||||
}" class="uni-fab__circle uni-fab__plus" :style="{ 'background-color': styles.buttonColor }" @click="_onClick">
|
||||
<view class="fab-circle-v" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
|
||||
<view class="fab-circle-h" :class="{'uni-fab__plus--active': isShow && content.length > 0}"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let platform = 'other'
|
||||
// #ifdef APP-NVUE
|
||||
platform = uni.getSystemInfoSync().platform
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Fab 悬浮按钮
|
||||
* @description 点击可展开一个图形按钮菜单
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=144
|
||||
* @property {Object} pattern 可选样式配置项
|
||||
* @property {Object} horizontal = [left | right] 水平对齐方式
|
||||
* @value left 左对齐
|
||||
* @value right 右对齐
|
||||
* @property {Object} vertical = [bottom | top] 垂直对齐方式
|
||||
* @value bottom 下对齐
|
||||
* @value top 上对齐
|
||||
* @property {Object} direction = [horizontal | vertical] 展开菜单显示方式
|
||||
* @value horizontal 水平显示
|
||||
* @value vertical 垂直显示
|
||||
* @property {Array} content 展开菜单内容配置项
|
||||
* @property {Boolean} popMenu 是否使用弹出菜单
|
||||
* @event {Function} trigger 展开菜单点击事件,返回点击信息
|
||||
* @event {Function} fabClick 悬浮按钮点击事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniFab',
|
||||
props: {
|
||||
pattern: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
horizontal: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
vertical: {
|
||||
type: String,
|
||||
default: 'bottom'
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal'
|
||||
},
|
||||
content: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
popMenu: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fabShow: false,
|
||||
isShow: false,
|
||||
isAndroidNvue: platform === 'android',
|
||||
styles: {
|
||||
color: '#3c3e49',
|
||||
selectedColor: '#007AFF',
|
||||
backgroundColor: '#fff',
|
||||
buttonColor: '#3c3e49'
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
contentWidth(e) {
|
||||
return (this.content.length + 1) * 55 + 10 + 'px'
|
||||
},
|
||||
contentWidthMin() {
|
||||
return 55 + 'px'
|
||||
},
|
||||
// 动态计算宽度
|
||||
boxWidth() {
|
||||
return this.getPosition(3, 'horizontal')
|
||||
},
|
||||
// 动态计算高度
|
||||
boxHeight() {
|
||||
return this.getPosition(3, 'vertical')
|
||||
},
|
||||
// 计算左下位置
|
||||
leftBottom() {
|
||||
return this.getPosition(0, 'left', 'bottom')
|
||||
},
|
||||
// 计算右下位置
|
||||
rightBottom() {
|
||||
return this.getPosition(0, 'right', 'bottom')
|
||||
},
|
||||
// 计算左上位置
|
||||
leftTop() {
|
||||
return this.getPosition(0, 'left', 'top')
|
||||
},
|
||||
rightTop() {
|
||||
return this.getPosition(0, 'right', 'top')
|
||||
},
|
||||
flexDirectionStart() {
|
||||
return this.getPosition(1, 'vertical', 'top')
|
||||
},
|
||||
flexDirectionEnd() {
|
||||
return this.getPosition(1, 'vertical', 'bottom')
|
||||
},
|
||||
horizontalLeft() {
|
||||
return this.getPosition(2, 'horizontal', 'left')
|
||||
},
|
||||
horizontalRight() {
|
||||
return this.getPosition(2, 'horizontal', 'right')
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
pattern(newValue, oldValue) {
|
||||
//console.log(JSON.stringify(newValue))
|
||||
this.styles = Object.assign({}, this.styles, newValue)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.isShow = this.show
|
||||
if (this.top === 0) {
|
||||
this.fabShow = true
|
||||
}
|
||||
// 初始化样式
|
||||
this.styles = Object.assign({}, this.styles, this.pattern)
|
||||
},
|
||||
methods: {
|
||||
_onClick() {
|
||||
this.$emit('fabClick')
|
||||
if (!this.popMenu) {
|
||||
return
|
||||
}
|
||||
this.isShow = !this.isShow
|
||||
},
|
||||
open() {
|
||||
this.isShow = true
|
||||
},
|
||||
close() {
|
||||
this.isShow = false
|
||||
},
|
||||
/**
|
||||
* 按钮点击事件
|
||||
*/
|
||||
_onItemClick(index, item) {
|
||||
this.$emit('trigger', {
|
||||
index,
|
||||
item
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 获取 位置信息
|
||||
*/
|
||||
getPosition(types, paramA, paramB) {
|
||||
if (types === 0) {
|
||||
return this.horizontal === paramA && this.vertical === paramB
|
||||
} else if (types === 1) {
|
||||
return this.direction === paramA && this.vertical === paramB
|
||||
} else if (types === 2) {
|
||||
return this.direction === paramA && this.horizontal === paramB
|
||||
} else {
|
||||
return this.isShow && this.direction === paramA ? this.contentWidth : this.contentWidthMin
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-fab {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-fab--leftBottom {
|
||||
left: 5px;
|
||||
bottom: 20px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(5px + var(--window-left));
|
||||
bottom: calc(20px + var(--window-bottom));
|
||||
/* #endif */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--leftTop {
|
||||
left: 5px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(5px + var(--window-left));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--rightBottom {
|
||||
right: 5px;
|
||||
bottom: 20px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(5px + var(--window-right));
|
||||
bottom: calc(20px + var(--window-bottom));
|
||||
/* #endif */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab--rightTop {
|
||||
right: 5px;
|
||||
top: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(5px + var(--window-right));
|
||||
top: calc(30px + var(--window-top));
|
||||
/* #endif */
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.uni-fab__circle {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
background-color: #3c3e49;
|
||||
border-radius: 55px;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.uni-fab__circle--leftBottom {
|
||||
left: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--leftTop {
|
||||
left: 15px;
|
||||
top: 40px;
|
||||
/* #ifdef H5 */
|
||||
left: calc(15px + var(--window-left));
|
||||
top: calc(40px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--rightBottom {
|
||||
right: 15px;
|
||||
bottom: 30px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
bottom: calc(30px + var(--window-bottom));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--rightTop {
|
||||
right: 15px;
|
||||
top: 40px;
|
||||
/* #ifdef H5 */
|
||||
right: calc(15px + var(--window-right));
|
||||
top: calc(40px + var(--window-top));
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-fab__circle--left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.uni-fab__circle--bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-fab__plus {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.fab-circle-v {
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 31px;
|
||||
left: 26px;
|
||||
top: 12px;
|
||||
background-color: white;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.fab-circle-h {
|
||||
position: absolute;
|
||||
width: 31px;
|
||||
height: 3px;
|
||||
left: 12px;
|
||||
top: 26px;
|
||||
background-color: white;
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.uni-fab__plus--active {
|
||||
transform: rotate(135deg);
|
||||
}
|
||||
|
||||
.uni-fab__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-radius: 55px;
|
||||
overflow: hidden;
|
||||
transition-property: width, height;
|
||||
transition-duration: 0.2s;
|
||||
width: 55px;
|
||||
border-color: #DDDDDD;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.uni-fab__content--other-platform {
|
||||
border-width: 0px;
|
||||
box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.uni-fab__content--left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.uni-fab__content--right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirection {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirectionStart {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.uni-fab__content--flexDirectionEnd {
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-fab__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.uni-fab__item--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-fab__item-image {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.uni-fab__item-text {
|
||||
color: #FFFFFF;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-fab__item--first {
|
||||
width: 55px;
|
||||
}
|
||||
</style>
|
||||
136
components/uni-fav/uni-fav.vue
Normal file
136
components/uni-fav/uni-fav.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view :class="[circle === true || circle === 'true' ? 'uni-fav--circle' : '']" :style="[{ backgroundColor: checked ? bgColorChecked : bgColor }]" @click="onClick" class="uni-fav">
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<view class="uni-fav-star" v-if="!checked && (star === true || star === 'true')">
|
||||
<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" size="14" type="star-filled" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-star" size="14" type="star-filled" v-if="!checked && (star === true || star === 'true')" />
|
||||
<!-- #endif -->
|
||||
<text :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-text">{{ checked ? contentText.contentFav : contentText.contentDefault }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from "../uni-icons/uni-icons.vue";
|
||||
|
||||
/**
|
||||
* Fav 收藏按钮
|
||||
* @description 用于收藏功能,可点击切换选中、不选中的状态
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=864
|
||||
* @property {Boolean} star = [true|false] 按钮是否带星星
|
||||
* @property {String} bgColor 未收藏时的背景色
|
||||
* @property {String} bgColorChecked 已收藏时的背景色
|
||||
* @property {String} fgColor 未收藏时的文字颜色
|
||||
* @property {String} fgColorChecked 已收藏时的文字颜色
|
||||
* @property {Boolean} circle = [true|false] 是否为圆角
|
||||
* @property {Boolean} checked = [true|false] 是否为已收藏
|
||||
* @property {Object} contentText = [true|false] 收藏按钮文字
|
||||
* @event {Function} click 点击 fav按钮触发事件
|
||||
* @example <uni-fav :checked="true"/>
|
||||
*/
|
||||
export default {
|
||||
name: "UniFav",
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
star: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: "#eeeeee"
|
||||
},
|
||||
fgColor: {
|
||||
type: String,
|
||||
default: "#666666"
|
||||
},
|
||||
bgColorChecked: {
|
||||
type: String,
|
||||
default: "#007aff"
|
||||
},
|
||||
fgColorChecked: {
|
||||
type: String,
|
||||
default: "#FFFFFF"
|
||||
},
|
||||
circle: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
contentText: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
contentDefault: "收藏",
|
||||
contentFav: "已收藏"
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
checked() {
|
||||
if (uni.report) {
|
||||
if (this.checked) {
|
||||
uni.report("收藏", "收藏");
|
||||
} else {
|
||||
uni.report("取消收藏", "取消收藏");
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit("click");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-fav {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 60px;
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.uni-fav--circle {
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
.uni-fav-star {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
height: 25px;
|
||||
line-height: 24px;
|
||||
margin-right: 3px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-fav-text {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
height: 25px;
|
||||
line-height: 25px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
652
components/uni-field/uni-field.vue
Executable file
652
components/uni-field/uni-field.vue
Executable file
@ -0,0 +1,652 @@
|
||||
<template>
|
||||
<view class="uni-field" :class="{ 'uni-border-top': borderTop, 'uni-border-bottom': borderBottom }" :style="[fieldStyle]">
|
||||
<view class="uni-field-inner" :class="[type == 'textarea' ? 'uni-textarea-inner' : '', 'uni-label-postion-' + labelPos]">
|
||||
<view :class="errorTop ? 'uni-error-in-label' : ''">
|
||||
<view class="uni-field-label" :class="[required ? 'uni-required' : '']" :style="{
|
||||
justifyContent: justifyContent,
|
||||
width: labelWid + 'px',
|
||||
marginBottom: labelMarginBottom
|
||||
}">
|
||||
<view class="uni-icon-wrap" v-if="leftIcon">
|
||||
<uni-icons size="16" :type="leftIcon" :color="iconColor" />
|
||||
</view>
|
||||
<slot name="leftIcon"></slot>
|
||||
<text class="uni-label-text" :class="[leftIcon ? 'uni-label-left-gap' : '']">{{ label }}</text>
|
||||
</view>
|
||||
<view v-if="errorTop" class="uni-error-message" :style="{ paddingLeft: '4px' }">{{ msg }}</view>
|
||||
</view>
|
||||
<view class="fild-body" :class="[inputBorder ? 'uni-input-border' : '']" :style="[borderEixstTextareaStyle]">
|
||||
<view class="uni-flex-1 uni-flex" :style="[inputWrapStyle]">
|
||||
<textarea v-if="type == 'textarea'" class="uni-flex-1 uni-textarea-class" :name="name" :value="value" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focus" :autoHeight="autoHeight" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm" @tap="fieldClick" />
|
||||
<input
|
||||
v-else
|
||||
:type="type"
|
||||
class="uni-flex-1 uni-field__input-wrap"
|
||||
:name="name"
|
||||
: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"
|
||||
/>
|
||||
<uni-icons :size="clearSize" v-if="clearable && value != ''" type="clear" color="#c0c4cc" @click="onClear" class="uni-clear-icon" />
|
||||
</view>
|
||||
<view class="uni-button-wrap"><slot name="right" /></view>
|
||||
<uni-icons v-if="rightIcon" size="16" @click="rightIconClick" :type="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" />
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-if="errorBottom"
|
||||
class="uni-error-message"
|
||||
:style="{
|
||||
paddingLeft: Number(labelWid) + 4 + 'px'
|
||||
}"
|
||||
>
|
||||
{{ msg }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Field 输入框
|
||||
* @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21001
|
||||
* @property {String } type 输入框的类型(默认text)
|
||||
* @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false)
|
||||
* @property {String } leftIcon label左边的图标,限uni-ui的图标名称
|
||||
* @property {String } iconColor 左边通过icon配置的图标的颜色(默认#606266)
|
||||
* @property {Boolean} rightIcon 输入框右边的图标名称,限uni-ui的图标名称(默认false)
|
||||
* @property {String } label 输入框左边的文字提示
|
||||
* @property {Number } labelWidth label的宽度,单位px(默认65)
|
||||
* @property {String } labelAlign label的文字对齐方式(默认left)
|
||||
* @property {String } labelPosition label的文字的位置(默认left)
|
||||
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
|
||||
* @property {String } placeholder 输入框的提示文字
|
||||
* @property {String } placeholderStyle placeholder的样式(内联样式,字符串),如"color: #ddd"
|
||||
* @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
|
||||
* @property {Boolean} focus 是否自动获得焦点(默认false)
|
||||
* @property {Boolean} disabled 是否不可输入(默认false)
|
||||
* @property {Number } maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
|
||||
* @property {String } confirmType 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
|
||||
* @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
|
||||
* @property {Number } clearSize 清除图标的大小,单位px(默认15)
|
||||
* @property {Boolean} trim 是否自动去除两端的空格
|
||||
* @property {String } name 表单域的属性名,在使用校验规则时必填
|
||||
* @property {Array } rules 单行表单验证规则,接受一个数组
|
||||
* @property {Boolean} inputBorder 是否显示input输入框的边框(默认false)
|
||||
* @property {Boolean} border-bottom 是否显示field的下边框(默认true)
|
||||
* @property {Boolean} border-top 是否显示field的上边框(默认false)
|
||||
* @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
|
||||
* @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 <uni-field v-model="mobile" label="手机号" required :error-message="errorMessage"></uni-field>
|
||||
*/
|
||||
export default {
|
||||
name: 'uni-field',
|
||||
props: {
|
||||
// rules:{
|
||||
// type:Array,
|
||||
// default(){
|
||||
// return []
|
||||
// }
|
||||
// },
|
||||
trigger: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
leftIcon: String,
|
||||
rightIcon: String,
|
||||
required: Boolean,
|
||||
label: String,
|
||||
password: Boolean,
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 左边标题的宽度单位px
|
||||
labelWidth: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 对齐方式,left|center|right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
autoHeight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorMessage: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
placeholder: String,
|
||||
placeholderStyle: String,
|
||||
focus: Boolean,
|
||||
name: String,
|
||||
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: ''
|
||||
},
|
||||
// 清除按钮的大小
|
||||
clearSize: {
|
||||
type: [Number, String],
|
||||
default: 15
|
||||
},
|
||||
// 是否显示 input 边框
|
||||
inputBorder: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示上边框
|
||||
borderTop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示下边框
|
||||
borderBottom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否自动去除两端的空格
|
||||
trim: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
focused: false,
|
||||
itemIndex: 0,
|
||||
errorTop: false,
|
||||
errorBottom: false,
|
||||
labelMarginBottom: '',
|
||||
errorWidth: '',
|
||||
errMsg: '',
|
||||
errorBorderColor: false,
|
||||
val: '',
|
||||
labelPos: '',
|
||||
labelWid: '',
|
||||
labelAli: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
msg() {
|
||||
return this.errorMessage || this.errMsg;
|
||||
},
|
||||
fieldStyle() {
|
||||
let style = {};
|
||||
if (this.labelPos === 'top') {
|
||||
style.padding = '10px 14px';
|
||||
this.labelMarginBottom = '6px';
|
||||
}
|
||||
if (this.labelPos === 'left' && this.msg !== false && this.msg !== '') {
|
||||
style.paddingBottom = '0px';
|
||||
this.errorBottom = true;
|
||||
this.errorTop = false;
|
||||
} else if (this.labelPos === 'top' && this.msg !== false && this.msg !== '') {
|
||||
this.errorBottom = false;
|
||||
this.errorTop = true;
|
||||
} else {
|
||||
// style.paddingBottom = ''
|
||||
this.errorTop = false;
|
||||
this.errorBottom = false;
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
borderEixstTextareaStyle() {
|
||||
let style = {};
|
||||
if (this.inputBorder) {
|
||||
if (this.type === 'textarea') {
|
||||
style.minHeight = '60px';
|
||||
}
|
||||
if (this.msg !== false && this.msg != '') {
|
||||
style.borderColor = '#dd524d';
|
||||
}
|
||||
}
|
||||
return style;
|
||||
},
|
||||
|
||||
inputWrapStyle() {
|
||||
let style = {};
|
||||
// 判断lable的位置,如果是left的话,让input左边两边有间隙
|
||||
if (this.labelPos == 'left') {
|
||||
style.margin = `0 4px`;
|
||||
} else {
|
||||
// 如果lable是top的,input的左边就没必要有间隙了
|
||||
style.marginRight = `4px`;
|
||||
// this.fieldStyle.style.padding = '10px 14px'
|
||||
}
|
||||
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.labelAli == 'left') style.justifyContent = 'flext-start';
|
||||
if (this.labelAli == 'center') style.justifyContent = 'center';
|
||||
if (this.labelAli == 'right') style.justifyContent = 'flext-end';
|
||||
return style;
|
||||
},
|
||||
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
|
||||
justifyContent() {
|
||||
if (this.labelAli == 'left') return 'flex-start';
|
||||
if (this.labelAli == 'center') return 'center';
|
||||
if (this.labelAli == 'right') return 'flex-end';
|
||||
},
|
||||
// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
|
||||
inputMaxlength() {
|
||||
return Number(this.maxlength);
|
||||
},
|
||||
// label的位置
|
||||
fieldInnerStyle() {
|
||||
let style = {};
|
||||
if (this.labelPos == 'left') {
|
||||
style.flexDirection = 'row';
|
||||
} else {
|
||||
style.flexDirection = 'column';
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
trigger(trigger) {
|
||||
this.formTrigger = trigger;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm();
|
||||
this.formRules = [];
|
||||
this.formTrigger = this.trigger;
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化变量值
|
||||
*/
|
||||
init() {
|
||||
if (this.form) {
|
||||
this.form.childrens.push(this);
|
||||
this.labelPos = this.labelPosition ? this.labelPosition : this.form.labelPosition;
|
||||
this.labelWid = this.labelWidth ? this.labelWidth : this.form.labelWidth;
|
||||
this.labelAli = this.labelAlign ? this.labelAlign : this.form.labelAlign;
|
||||
|
||||
if (this.form.formRules) {
|
||||
this.formRules = this.form.formRules[this.name];
|
||||
}
|
||||
this.validator = this.form.validator;
|
||||
if(this.name){
|
||||
this.form.formData[this.name] = this.value || '';
|
||||
}
|
||||
} else {
|
||||
this.labelPos = this.labelPosition || 'left';
|
||||
this.labelWid = this.labelWidth || 65;
|
||||
this.labelAli = this.labelAlign || 'left';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm() {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== 'uniForms') {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false;
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除该表单项的校验结果
|
||||
*/
|
||||
clearValidate() {
|
||||
this.errMsg = '';
|
||||
},
|
||||
/**
|
||||
* 父组件处理函数
|
||||
* @param {Object} callback
|
||||
*/
|
||||
parentVal(callback) {
|
||||
if (this.type === 'number') {
|
||||
this.val = this.val === '' ? this.val : Number(this.val);
|
||||
}
|
||||
typeof callback === 'function' &&
|
||||
callback(
|
||||
{
|
||||
[this.name]: this.val
|
||||
},
|
||||
this.name
|
||||
);
|
||||
},
|
||||
/**
|
||||
* 触发校验
|
||||
* @param {Object} trigger
|
||||
* @param {Object} value
|
||||
*/
|
||||
triggerValidator(trigger, value) {
|
||||
let isValid = false;
|
||||
// 如果 name 不存在,则不开启校验
|
||||
this.formRules &&
|
||||
this.formRules.rules &&
|
||||
this.formRules.rules.forEach(item => {
|
||||
item.trigger = this.isTrigger(this.form.formTrigger, this.formTrigger, item.trigger);
|
||||
if (item.trigger !== trigger || item.trigger === 'submit') return;
|
||||
isValid = true;
|
||||
});
|
||||
|
||||
isValid && this.triggerCheck(value);
|
||||
},
|
||||
/**
|
||||
* 校验规则
|
||||
* @param {Object} value
|
||||
*/
|
||||
triggerCheck(value, item) {
|
||||
// 输入值为 number
|
||||
if (this.type === 'number') {
|
||||
value = value === '' ? value : Number(value);
|
||||
}
|
||||
const result = this.validator.validateUpdate({
|
||||
[this.name]: value
|
||||
});
|
||||
this.errMsg = !result ? '' : result.errorMessage;
|
||||
this.form.validateCheck(result);
|
||||
},
|
||||
/**
|
||||
* 触发时机
|
||||
* @param {Object} event
|
||||
*/
|
||||
isTrigger(parentRule, itemRlue, rule) {
|
||||
let rl = 'none';
|
||||
if (rule) {
|
||||
rl = rule;
|
||||
} else if (itemRlue) {
|
||||
rl = itemRlue;
|
||||
} else if (parentRule) {
|
||||
rl = parentRule;
|
||||
} else {
|
||||
rl = 'blur';
|
||||
}
|
||||
return rl;
|
||||
},
|
||||
|
||||
onInput(event) {
|
||||
let value = event.detail.value;
|
||||
// 判断是否去除空格
|
||||
if (this.trim) value = this.trimStr(value);
|
||||
this.form.formData[this.name] = value || '';
|
||||
this.val = value;
|
||||
this.$emit('input', value);
|
||||
// 校验输入
|
||||
this.triggerValidator('change', value);
|
||||
},
|
||||
|
||||
onFocus(event) {
|
||||
this.focused = true;
|
||||
this.$emit('focus', event);
|
||||
},
|
||||
onBlur(event) {
|
||||
let value = event.detail.value;
|
||||
// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
|
||||
// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
|
||||
setTimeout(() => {
|
||||
this.focused = false;
|
||||
}, 100);
|
||||
this.$emit('blur', event);
|
||||
|
||||
// 校验输入
|
||||
this.triggerValidator('blur', value);
|
||||
},
|
||||
onConfirm(e) {
|
||||
this.$emit('confirm', e.detail.value);
|
||||
},
|
||||
onClear(event) {
|
||||
this.val = '';
|
||||
this.$emit('input', '');
|
||||
this.clearValidate();
|
||||
},
|
||||
rightIconClick() {
|
||||
this.$emit('right-icon-click');
|
||||
this.$emit('click');
|
||||
},
|
||||
fieldClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
trimStr(str, pos = 'both') {
|
||||
if (pos == 'both') {
|
||||
return str.replace(/^\s+|\s+$/g, '');
|
||||
} else if (pos == 'left') {
|
||||
return str.replace(/^\s*/, '');
|
||||
} else if (pos == 'right') {
|
||||
return str.replace(/(\s*$)/g, '');
|
||||
} else if (pos == 'all') {
|
||||
return str.replace(/\s+/g, '');
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>@charset "UTF-8";
|
||||
.uni-field {
|
||||
padding: 16px 14px;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
background-color: #fff; }
|
||||
|
||||
.uni-field-inner {
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
|
||||
.uni-textarea-inner {
|
||||
align-items: flex-start; }
|
||||
|
||||
.uni-textarea-class {
|
||||
min-height: 48px;
|
||||
width: auto;
|
||||
font-size: 14px; }
|
||||
|
||||
.fild-body {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center; }
|
||||
|
||||
.uni-arror-right {
|
||||
margin-left: 4px; }
|
||||
|
||||
.uni-label-text {
|
||||
display: inline-block; }
|
||||
|
||||
.uni-label-left-gap {
|
||||
margin-left: 3px; }
|
||||
|
||||
.uni-label-postion-top {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex: 1; }
|
||||
|
||||
.uni-field-label {
|
||||
width: 65px;
|
||||
flex: 1 1 65px;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
|
||||
.uni-required::before {
|
||||
content: '*';
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
font-size: 14px;
|
||||
color: #dd524d;
|
||||
height: 9px;
|
||||
line-height: 1; }
|
||||
|
||||
.uni-field__input-wrap {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-size: 14px;
|
||||
height: 24px;
|
||||
flex: 1;
|
||||
width: auto; }
|
||||
|
||||
.uni-clear-icon {
|
||||
display: flex;
|
||||
align-items: center; }
|
||||
|
||||
.uni-error-message {
|
||||
line-height: 12px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
color: #dd524d;
|
||||
font-size: 12px;
|
||||
text-align: left; }
|
||||
|
||||
.uni-input-error-border {
|
||||
border-color: #dd524d; }
|
||||
|
||||
.placeholder-style {
|
||||
color: #969799; }
|
||||
|
||||
.uni-input-class {
|
||||
font-size: 14px; }
|
||||
|
||||
.uni-button-wrap {
|
||||
margin-left: 4px; }
|
||||
|
||||
/* start--Retina 屏幕下的 1px 边框--start */
|
||||
.uni-border,
|
||||
.uni-border-bottom,
|
||||
.uni-border-left,
|
||||
.uni-border-right,
|
||||
.uni-border-top,
|
||||
.uni-border-top-bottom {
|
||||
position: relative; }
|
||||
|
||||
.uni-border-bottom:after,
|
||||
.uni-border-left:after,
|
||||
.uni-border-right:after,
|
||||
.uni-border-top-bottom:after,
|
||||
.uni-border-top:after,
|
||||
.uni-border:after {
|
||||
/* #ifndef APP-NVUE */
|
||||
content: ' ';
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
pointer-events: none;
|
||||
box-sizing: border-box;
|
||||
-webkit-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
width: 199.8%;
|
||||
height: 199.7%;
|
||||
transform: scale(0.5, 0.5);
|
||||
border: 0 solid #e5e5e5;
|
||||
z-index: 2; }
|
||||
|
||||
.uni-input-border {
|
||||
min-height: 34px;
|
||||
padding-left: 4px;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box; }
|
||||
|
||||
.uni-border-top:after {
|
||||
border-top-width: 1px; }
|
||||
|
||||
.uni-border-left:after {
|
||||
border-left-width: 1px; }
|
||||
|
||||
.uni-border-right:after {
|
||||
border-right-width: 1px; }
|
||||
|
||||
.uni-border-bottom:after {
|
||||
border-bottom-width: 1px; }
|
||||
|
||||
.uni-border-top-bottom:after {
|
||||
border-width: 1px 0; }
|
||||
|
||||
.uni-border:after {
|
||||
border-width: 1px; }
|
||||
|
||||
/* end--Retina 屏幕下的 1px 边框--end */
|
||||
.uni-icon-wrap {
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; }
|
||||
|
||||
.uni-button-wrap {
|
||||
display: flex;
|
||||
align-items: right;
|
||||
justify-content: center; }
|
||||
|
||||
.uni-clear-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 4px; }
|
||||
|
||||
.uni-flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center; }
|
||||
|
||||
.uni-flex-1 {
|
||||
flex: 1; }
|
||||
|
||||
.uni-error-in-label {
|
||||
display: flex;
|
||||
flex-direction: row; }
|
||||
</style>
|
||||
426
components/uni-forms-item/uni-forms-item.vue
Executable file
426
components/uni-forms-item/uni-forms-item.vue
Executable file
@ -0,0 +1,426 @@
|
||||
<template>
|
||||
<view class="uni-forms-item" :class="{'uni-forms-item--border':border,'is-first-border':border&&isFirstBorder,'uni-forms-item-error':msg}">
|
||||
<view class="uni-forms-item__inner" :class="['is-direction-'+labelPos,]">
|
||||
<view v-if="label" class="uni-forms-item__label" :style="{width:labelWid+'px',justifyContent: justifyContent}">
|
||||
<slot name="left">
|
||||
<uni-icons v-if="leftIcon" class="label-icon" size="16" :type="leftIcon" :color="iconColor" />
|
||||
<text>{{label}}</text>
|
||||
<text v-if="required" class="is-required">*</text>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="uni-forms-item__content" :class="{'is-input-error-border': msg}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-error-message" :class="{'uni-error-msg--boeder':border}" :style="{
|
||||
paddingLeft: (labelPos === 'left'? Number(labelWid)+5:5) + 'px'
|
||||
}">{{ showMsg === 'undertext' ? msg:'' }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Field 输入框
|
||||
* @description 此组件可以实现表单的输入与校验,包括 "text" 和 "textarea" 类型。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21001
|
||||
* @property {Boolean} required 是否必填,左边显示红色"*"号(默认false)
|
||||
* @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
|
||||
* @value bind 发生变化时触发
|
||||
* @value submit 提交时触发
|
||||
* @property {String } leftIcon label左边的图标,限 uni-ui 的图标名称
|
||||
* @property {String } iconColor 左边通过icon配置的图标的颜色(默认#606266)
|
||||
* @property {String } label 输入框左边的文字提示
|
||||
* @property {Number } labelWidth label的宽度,单位px(默认65)
|
||||
* @property {String } labelAlign = [left|center|right] label的文字对齐方式(默认left)
|
||||
* @value left label 左侧显示
|
||||
* @value center label 居中
|
||||
* @value right label 右侧对齐
|
||||
* @property {String } labelPosition = [top|left] label的文字的位置(默认left)
|
||||
* @value top 顶部显示 label
|
||||
* @value left 左侧显示 label
|
||||
* @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
|
||||
* @property {String } name 表单域的属性名,在使用校验规则时必填
|
||||
*/
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
name: "uniFormsItem",
|
||||
props: {
|
||||
// 自定义内容
|
||||
custom: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示报错信息
|
||||
showMessage: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
name: String,
|
||||
required: Boolean,
|
||||
validateTrigger: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
leftIcon: String,
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
label: String,
|
||||
// 左边标题的宽度单位px
|
||||
labelWidth: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 对齐方式,left|center|right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// lable的位置,可选为 left-左边,top-上边
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
errorMessage: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errorTop: false,
|
||||
errorBottom: false,
|
||||
labelMarginBottom: '',
|
||||
errorWidth: '',
|
||||
errMsg: '',
|
||||
val: '',
|
||||
labelPos: '',
|
||||
labelWid: '',
|
||||
labelAli: '',
|
||||
showMsg: 'undertext',
|
||||
border: false,
|
||||
isFirstBorder: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
msg() {
|
||||
return this.errorMessage || this.errMsg;
|
||||
},
|
||||
fieldStyle() {
|
||||
let style = {}
|
||||
if (this.labelPos == 'top') {
|
||||
style.padding = '0 0'
|
||||
this.labelMarginBottom = '6px'
|
||||
}
|
||||
if (this.labelPos == 'left' && this.msg !== false && this.msg != '') {
|
||||
style.paddingBottom = '0px'
|
||||
this.errorBottom = true
|
||||
this.errorTop = false
|
||||
} else if (this.labelPos == 'top' && this.msg !== false && this.msg != '') {
|
||||
this.errorBottom = false
|
||||
this.errorTop = true
|
||||
} else {
|
||||
// style.paddingBottom = ''
|
||||
this.errorTop = false
|
||||
this.errorBottom = false
|
||||
}
|
||||
return style
|
||||
},
|
||||
|
||||
// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
|
||||
justifyContent() {
|
||||
if (this.labelAli === 'left') return 'flex-start';
|
||||
if (this.labelAli === 'center') return 'center';
|
||||
if (this.labelAli === 'right') return 'flex-end';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
validateTrigger(trigger) {
|
||||
this.formTrigger = trigger
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm()
|
||||
this.group = this.getForm('uniGroup')
|
||||
this.formRules = []
|
||||
this.formTrigger = this.validateTrigger
|
||||
if (this.form) {
|
||||
this.form.childrens.push(this)
|
||||
}
|
||||
this.init()
|
||||
},
|
||||
destroyed() {
|
||||
if (this.form) {
|
||||
this.form.childrens.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.form.childrens.splice(index, 1)
|
||||
delete this.form.formData[item.name]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (this.form) {
|
||||
let {
|
||||
formRules,
|
||||
validator,
|
||||
formData,
|
||||
value,
|
||||
labelPosition,
|
||||
labelWidth,
|
||||
labelAlign,
|
||||
errShowType
|
||||
} = this.form
|
||||
|
||||
this.labelPos = this.labelPosition ? this.labelPosition : labelPosition
|
||||
this.labelWid = this.label ? (this.labelWidth ? this.labelWidth : labelWidth) : 0
|
||||
this.labelAli = this.labelAlign ? this.labelAlign : labelAlign
|
||||
|
||||
// 判断第一个 item
|
||||
if (!this.form.isFirstBorder) {
|
||||
this.form.isFirstBorder = true
|
||||
this.isFirstBorder = true
|
||||
}
|
||||
|
||||
// 判断 group 里的第一个 item
|
||||
if (this.group) {
|
||||
if (!this.group.isFirstBorder) {
|
||||
this.group.isFirstBorder = true
|
||||
this.isFirstBorder = true
|
||||
}
|
||||
}
|
||||
|
||||
this.border = this.form.border
|
||||
this.showMsg = errShowType
|
||||
|
||||
if (formRules) {
|
||||
this.formRules = formRules[this.name] || {}
|
||||
}
|
||||
|
||||
this.validator = validator
|
||||
} else {
|
||||
this.labelPos = this.labelPosition || 'left'
|
||||
this.labelWid = this.labelWidth || 65
|
||||
this.labelAli = this.labelAlign || 'left'
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniForms') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除该表单项的校验结果
|
||||
*/
|
||||
clearValidate() {
|
||||
this.errMsg = ''
|
||||
},
|
||||
|
||||
setValue(value) {
|
||||
if (this.name) {
|
||||
if (this.errMsg) this.errMsg = ''
|
||||
this.form.formData[this.name] = this.form._getValue(this.name, value)
|
||||
if (!this.formRules || (typeof(this.formRules) && JSON.stringify(this.formRules) === '{}')) return
|
||||
this.triggerCheck(this.form._getValue(this.name, value))
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 校验规则
|
||||
* @param {Object} value
|
||||
*/
|
||||
async triggerCheck(value, callback) {
|
||||
let promise = null;
|
||||
this.errMsg = ''
|
||||
// if no callback, return promise
|
||||
// if (callback && typeof callback !== 'function' && Promise) {
|
||||
// promise = new Promise((resolve, reject) => {
|
||||
// callback = function(valid) {
|
||||
// !valid ? resolve(valid) : reject(valid)
|
||||
// };
|
||||
// });
|
||||
// }
|
||||
// if (!this.validator) {
|
||||
// typeof callback === 'function' && callback(null);
|
||||
// if (promise) return promise
|
||||
// }
|
||||
if (!this.validator) return
|
||||
const isNoField = this.isRequired(this.formRules.rules || [])
|
||||
let isTrigger = this.isTrigger(this.formRules.validateTrigger, this.validateTrigger, this.form.validateTrigger)
|
||||
let result = null
|
||||
if (!(!isTrigger)) {
|
||||
result = await this.validator.validateUpdate({
|
||||
[this.name]: value
|
||||
}, this.form.formData)
|
||||
}
|
||||
// 判断是否必填
|
||||
if (!isNoField && !value) {
|
||||
result = null
|
||||
}
|
||||
if (isTrigger && result && result.errorMessage) {
|
||||
const inputComp = this.form.inputChildrens.find(child => child.rename === this.name)
|
||||
if (inputComp) {
|
||||
inputComp.errMsg = result.errorMessage
|
||||
}
|
||||
if (this.form.errShowType === 'toast') {
|
||||
uni.showToast({
|
||||
title: result.errorMessage || '校验错误',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
if (this.form.errShowType === 'modal') {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: result.errorMessage || '校验错误'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.errMsg = !result ? '' : result.errorMessage
|
||||
// 触发validate事件
|
||||
this.form.validateCheck(result ? result : null)
|
||||
// typeof callback === 'function' && callback(result ? result : null);
|
||||
// if (promise) return promise
|
||||
|
||||
},
|
||||
/**
|
||||
* 触发时机
|
||||
* @param {Object} event
|
||||
*/
|
||||
isTrigger(rule, itemRlue, parentRule) {
|
||||
let rl = true;
|
||||
// bind submit
|
||||
if (rule === 'submit' || !rule) {
|
||||
if (rule === undefined) {
|
||||
if (itemRlue !== 'bind') {
|
||||
if (!itemRlue) {
|
||||
return parentRule === 'bind' ? true : false
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 是否有必填字段
|
||||
isRequired(rules) {
|
||||
let isNoField = false
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const ruleData = rules[i]
|
||||
if (ruleData.required) {
|
||||
isNoField = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return isNoField
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-forms-item {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin-bottom: 22px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-forms-item__inner {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.is-direction-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.is-direction-top {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-forms-item__label {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
width: 65px;
|
||||
padding: 5px 0;
|
||||
box-sizing: border-box;
|
||||
height: 36px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-forms-item__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.label-icon {
|
||||
margin-right: 5px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.is-required {
|
||||
color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-error-message {
|
||||
position: absolute;
|
||||
bottom: -17px;
|
||||
left: 0;
|
||||
line-height: 12px;
|
||||
color: #dd524d;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.uni-error-msg--boeder {
|
||||
position: relative;
|
||||
bottom: 0;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.is-input-error-border {
|
||||
border-color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-forms-item--border {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 15px;
|
||||
border-top: 1px #eee solid;
|
||||
}
|
||||
|
||||
.uni-forms-item-error {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.is-first-border {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
364
components/uni-forms/schema-validator.js
Executable file
364
components/uni-forms/schema-validator.js
Executable file
@ -0,0 +1,364 @@
|
||||
|
||||
var pattern = {
|
||||
email: /^\S+?@\S+?\.\S+?$/,
|
||||
url: new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", 'i')
|
||||
};
|
||||
|
||||
const FORMAT_MAPPING = {
|
||||
"int": 'number',
|
||||
"bool": 'boolean',
|
||||
"double": 'number',
|
||||
"long": 'number'
|
||||
}
|
||||
|
||||
function formatMessage(args, resources) {
|
||||
var defaultMessage = ['label']
|
||||
defaultMessage.forEach((item) => {
|
||||
if (args[item] === undefined) {
|
||||
args[item] = ''
|
||||
}
|
||||
})
|
||||
|
||||
let str = resources
|
||||
for (let key in args) {
|
||||
let reg = new RegExp('{' + key + '}')
|
||||
str = str.replace(reg, args[key])
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function isEmptyValue(value, type) {
|
||||
if (value === undefined || value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && !value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === 'array' && Array.isArray(value) && !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class RuleValidator {
|
||||
|
||||
constructor(message) {
|
||||
this._message = message
|
||||
}
|
||||
|
||||
validateRule(key, value, data) {
|
||||
var result = null
|
||||
|
||||
let rules = key.rules
|
||||
|
||||
let hasRequired = rules.findIndex((item) => { return item.required })
|
||||
if (value === undefined && hasRequired < 0) {
|
||||
return result
|
||||
}
|
||||
|
||||
var message = this._message
|
||||
|
||||
if (rules === undefined) {
|
||||
return message['default']
|
||||
}
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
let rule = rules[i]
|
||||
let vt = this._getValidateType(rule)
|
||||
|
||||
if (key.label !== undefined) {
|
||||
Object.assign(rule, { label: key.label })
|
||||
}
|
||||
|
||||
if (RuleValidatorHelper[vt]) {
|
||||
var v = RuleValidatorHelper[vt](rule, value, message)
|
||||
if (v != null) {
|
||||
result = v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.validator) {
|
||||
var res = rule.validator(rule, value, data)
|
||||
if (!res) {
|
||||
result = formatMessage(rule, rule.errorMessage || message[vt] || message['default'])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
_getValidateType(rule) {
|
||||
// TODO
|
||||
var result = ''
|
||||
if (rule.required) {
|
||||
result = 'required'
|
||||
} else if (rule.enum || rule.maximum || rule.minimum || rule.maxLength || rule.minLength) {
|
||||
result = 'range'
|
||||
} else if (rule.format) {
|
||||
result = 'format'
|
||||
} else if (rule.pattern) {
|
||||
result = 'pattern'
|
||||
} else if (rule.validate) {
|
||||
result = 'validate'
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
const RuleValidatorHelper = {
|
||||
required(rule, value, message) {
|
||||
if (rule.required && isEmptyValue(value, rule.format)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.required);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
range(rule, value, message) {
|
||||
var type = (rule.maximum || rule.minimum) ? 1 : 0
|
||||
var min = type ? rule.minimum : rule.minLength;
|
||||
var max = type ? rule.maximum : rule.maxLength;
|
||||
|
||||
var key = ['string', 'number'][type];
|
||||
var val = type ? value : value.length;
|
||||
var enumValue = rule.enum;
|
||||
|
||||
if (enumValue) {
|
||||
if (enumValue.indexOf(value) < 0) {
|
||||
return formatMessage(rule, message[key].len);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else if (min && !max && val < min) {
|
||||
return formatMessage(rule, rule.errorMessage || message[key].min)
|
||||
} else if (max && !min && val > max) {
|
||||
return formatMessage(rule, rule.errorMessage || message[key].max)
|
||||
} else if (min && max && (val < min || val > max)) {
|
||||
return formatMessage(rule, rule.errorMessage || message[key].range)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
pattern(rule, value, message) {
|
||||
if (!types['pattern'](rule.pattern, value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
format(rule, value, message) {
|
||||
var customTypes = Object.keys(types);
|
||||
var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : rule.format;
|
||||
|
||||
if (customTypes.indexOf(format) > -1) {
|
||||
if (!types[format](value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.types[format]);
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const types = {
|
||||
integer(value) {
|
||||
return types.number(value) && parseInt(value, 10) === value;
|
||||
},
|
||||
string(value) {
|
||||
return typeof value === 'string';
|
||||
},
|
||||
number(value) {
|
||||
if (isNaN(value)) {
|
||||
return false;
|
||||
}
|
||||
return typeof value === 'number';
|
||||
},
|
||||
"boolean": function (value) {
|
||||
return typeof value === 'boolean';
|
||||
},
|
||||
"float": function (value) {
|
||||
return types.number(value) && !types.integer(value);
|
||||
},
|
||||
array(value) {
|
||||
return Array.isArray(value);
|
||||
},
|
||||
object(value) {
|
||||
return typeof value === 'object' && !types.array(value);
|
||||
},
|
||||
date(value) {
|
||||
var v
|
||||
if (value instanceof Date) {
|
||||
v = value;
|
||||
} else {
|
||||
v = new Date(value);
|
||||
}
|
||||
return typeof v.getTime === 'function' && typeof v.getMonth === 'function' && typeof v.getYear === 'function' && !isNaN(v.getTime());
|
||||
},
|
||||
timestamp(value) {
|
||||
if (!this.integer(value) || Math.abs(value).toString().length > 16) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.date(value);
|
||||
},
|
||||
email(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
|
||||
},
|
||||
url(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.url);
|
||||
},
|
||||
pattern(reg, value) {
|
||||
try {
|
||||
return new RegExp(reg).test(value);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
method(value) {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaValidator extends RuleValidator {
|
||||
|
||||
constructor(schema, options) {
|
||||
super(SchemaValidator.message);
|
||||
|
||||
this._schema = schema
|
||||
this._options = options || null
|
||||
}
|
||||
|
||||
updateSchema(schema) {
|
||||
this._schema = schema
|
||||
}
|
||||
|
||||
validate(data) {
|
||||
var checkResult = this._checkField(data)
|
||||
if (checkResult) {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
var result = this.invokeValidate(data, false)
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
validateAll(data) {
|
||||
var checkResult = this._checkField(data)
|
||||
if (checkResult) {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
return this.invokeValidate(data, true)
|
||||
}
|
||||
|
||||
validateUpdate(data) {
|
||||
var checkResult = this._checkField(data)
|
||||
if (checkResult) {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
var result = this.invokeValidateUpdate(data, false)
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
invokeValidate(data, all) {
|
||||
let result = []
|
||||
let schema = this._schema
|
||||
for (let key in schema) {
|
||||
let value = schema[key]
|
||||
|
||||
let vir = this.validateRule(value, data[key], data)
|
||||
if (vir != null) {
|
||||
result.push({
|
||||
key: key,
|
||||
errorMessage: vir
|
||||
})
|
||||
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
invokeValidateUpdate(data, all) {
|
||||
let result = []
|
||||
|
||||
for (let key in data) {
|
||||
let vir = this.validateRule(this._schema[key], data[key], data)
|
||||
if (vir != null) {
|
||||
result.push({
|
||||
key: key,
|
||||
errorMessage: vir
|
||||
})
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
_checkField(data) {
|
||||
var keys = Object.keys(data)
|
||||
var keys2 = Object.keys(this._schema)
|
||||
if (new Set(keys.concat(keys2)).size === keys2.length) {
|
||||
return ''
|
||||
}
|
||||
return [{ key: 'invalid', errorMessage: SchemaValidator.message['defaultInvalid'] }]
|
||||
}
|
||||
}
|
||||
|
||||
function Message() {
|
||||
return {
|
||||
default: '验证错误',
|
||||
defaultInvalid: '非法字段',
|
||||
required: '{label}必填',
|
||||
'enum': '{label}不合法',
|
||||
whitespace: '{label}不能为空',
|
||||
date: {
|
||||
format: '{label}日期{value}格式无效',
|
||||
parse: '{label}日期无法解析,{value}无效',
|
||||
invalid: '{label}日期{value}无效'
|
||||
},
|
||||
types: {
|
||||
string: '{label}类型无效',
|
||||
array: '{label}类型无效',
|
||||
object: '{label}类型无效',
|
||||
number: '{label}类型无效',
|
||||
date: '{label}类型无效',
|
||||
boolean: '{label}类型无效',
|
||||
integer: '{label}类型无效',
|
||||
float: '{label}类型无效',
|
||||
regexp: '{label}无效',
|
||||
email: '{label}类型无效',
|
||||
url: '{label}类型无效'
|
||||
},
|
||||
string: {
|
||||
len: '{label}必须为{length}个字符',
|
||||
min: '{label}不能少于 {minLength}个字符',
|
||||
max: '{label}不能超过 {maxLength}个字符',
|
||||
range: '{label}必须介于 {minLength}和{maxLength}个字符之间'
|
||||
},
|
||||
number: {
|
||||
len: '{label}必须等于{length}',
|
||||
min: '{label}不能小于{minimum}',
|
||||
max: '{label}不能大于{maximum}',
|
||||
range: '{label}必须介于{minimum}and{maximum}之间'
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '{label} 格式不匹配'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SchemaValidator.message = new Message();
|
||||
|
||||
export default SchemaValidator
|
||||
436
components/uni-forms/uni-forms.vue
Executable file
436
components/uni-forms/uni-forms.vue
Executable file
@ -0,0 +1,436 @@
|
||||
<template>
|
||||
<view class="uni-forms" :class="{'uni-forms--top':!border}">
|
||||
<form @submit.stop="submitForm" @reset="resetForm">
|
||||
<slot></slot>
|
||||
</form>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Forms 表单
|
||||
* @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773
|
||||
* @property {Object} rules 表单校验规则
|
||||
* @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
|
||||
* @value bind 发生变化时触发
|
||||
* @value submit 提交时触发
|
||||
* @property {String} labelPosition = [top|left] label 位置 默认 left 可选
|
||||
* @value top 顶部显示 label
|
||||
* @value left 左侧显示 label
|
||||
* @property {String} labelWidth label 宽度,默认 65px
|
||||
* @property {String} labelAlign = [left|center|right] label 居中方式 默认 left 可选
|
||||
* @value left label 左侧显示
|
||||
* @value center label 居中
|
||||
* @value right label 右侧对齐
|
||||
* @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式
|
||||
* @value undertext 错误信息在底部显示
|
||||
* @value toast 错误信息toast显示
|
||||
* @value modal 错误信息modal显示
|
||||
* @event {Function} submit 提交时触发
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
Vue.prototype.binddata = function(name, value, formName) {
|
||||
if (formName) {
|
||||
this.$refs[formName].setValue(name, value)
|
||||
} else {
|
||||
let formVm
|
||||
for (let i in this.$refs) {
|
||||
const vm = this.$refs[i]
|
||||
if (vm && vm.$options && vm.$options.name === 'uniForms') {
|
||||
formVm = vm
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性')
|
||||
formVm.setValue(name, value)
|
||||
}
|
||||
}
|
||||
|
||||
import Validator from './validate.js'
|
||||
|
||||
export default {
|
||||
name: 'uniForms',
|
||||
props: {
|
||||
value: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 校验触发器方式,默认 关闭
|
||||
validateTrigger: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label 位置,可选值 top/left
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label 宽度,单位 px
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: 65
|
||||
},
|
||||
// label 居中方式,可选值 left/center/right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
errShowType: {
|
||||
type: String,
|
||||
default: 'undertext'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
rules(newVal) {
|
||||
this.init(newVal)
|
||||
},
|
||||
trigger(trigger) {
|
||||
this.formTrigger = trigger
|
||||
},
|
||||
},
|
||||
created() {
|
||||
let _this = this
|
||||
this.childrens = []
|
||||
this.inputChildrens = []
|
||||
this.checkboxChildrens = []
|
||||
this.formRules = []
|
||||
// this.init(this.rules)
|
||||
},
|
||||
mounted() {
|
||||
this.init(this.rules)
|
||||
},
|
||||
methods: {
|
||||
init(formRules) {
|
||||
// 判断是否有规则
|
||||
if (Object.keys(formRules).length > 0) {
|
||||
this.formTrigger = this.trigger
|
||||
this.formRules = formRules
|
||||
if (!this.validator) {
|
||||
this.validator = new Validator(formRules)
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
// 判断表单存在那些实例
|
||||
for (let i in this.value) {
|
||||
const itemData = this.childrens.find(v => v.name === i)
|
||||
if (itemData) {
|
||||
this.formData[i] = this.value[i]
|
||||
itemData.init()
|
||||
}
|
||||
}
|
||||
|
||||
// watch 每个属性 ,需要知道具体那个属性发变化
|
||||
Object.keys(this.value).forEach((key) => {
|
||||
this.$watch('value.' + key, (newVal) => {
|
||||
const itemData = this.childrens.find(v => v.name === key)
|
||||
if (itemData) {
|
||||
this.formData[key] = this._getValue(key, newVal)
|
||||
itemData.init()
|
||||
} else {
|
||||
this.formData[key] = this.value[key] || null
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 设置校验规则
|
||||
* @param {Object} formRules
|
||||
*/
|
||||
setRules(formRules) {
|
||||
this.init(formRules)
|
||||
},
|
||||
/**
|
||||
* 公开给用户使用
|
||||
* 设置自定义表单组件 value 值
|
||||
* @param {String} name 字段名称
|
||||
* @param {String} value 字段值
|
||||
*/
|
||||
setValue(name, value, callback) {
|
||||
let example = this.childrens.find(child => child.name === name)
|
||||
if (!example) return null
|
||||
value = this._getValue(example.name, value)
|
||||
this.formData[name] = value
|
||||
example.val = value
|
||||
this.$emit('input', Object.assign({}, this.value, this.formData))
|
||||
return example.triggerCheck(value, callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO 表单提交, 小程序暂不支持这种用法
|
||||
* @param {Object} event
|
||||
*/
|
||||
submitForm(event) {
|
||||
const value = event.detail.value
|
||||
return this.validateAll(value || this.formData, 'submit')
|
||||
},
|
||||
|
||||
/**
|
||||
* 表单重置
|
||||
* @param {Object} event
|
||||
*/
|
||||
resetForm(event) {
|
||||
this.childrens.forEach(item => {
|
||||
item.errMsg = ''
|
||||
const inputComp = this.inputChildrens.find(child => child.rename === item.name)
|
||||
if (inputComp) {
|
||||
inputComp.errMsg = ''
|
||||
inputComp.$emit('input', inputComp.multiple ? [] : '')
|
||||
}
|
||||
})
|
||||
|
||||
this.childrens.forEach((item) => {
|
||||
if (item.name) {
|
||||
this.formData[item.name] = this._getValue(item.name, '')
|
||||
}
|
||||
})
|
||||
|
||||
this.$emit('input', this.formData)
|
||||
this.$emit('reset', event)
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发表单校验,通过 @validate 获取
|
||||
* @param {Object} validate
|
||||
*/
|
||||
validateCheck(validate) {
|
||||
if (validate === null) validate = null
|
||||
this.$emit('validate', validate)
|
||||
},
|
||||
/**
|
||||
* 校验所有或者部分表单
|
||||
*/
|
||||
async validateAll(invalidFields, type, callback) {
|
||||
this.childrens.forEach(item => {
|
||||
item.errMsg = ''
|
||||
})
|
||||
|
||||
let promise;
|
||||
if (!callback && typeof callback !== 'function' && Promise) {
|
||||
promise = new Promise((resolve, reject) => {
|
||||
callback = function(valid, invalidFields) {
|
||||
!valid ? resolve(invalidFields) : reject(valid);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let fieldsValue = {}
|
||||
let tempInvalidFields = Object.assign({}, invalidFields)
|
||||
|
||||
Object.keys(this.formRules).forEach(item => {
|
||||
const values = this.formRules[item]
|
||||
const rules = (values && values.rules) || []
|
||||
let isNoField = false
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const rule = rules[i]
|
||||
if (rule.required) {
|
||||
isNoField = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果存在 required 才会将内容插入校验对象
|
||||
if (!isNoField && (!tempInvalidFields[item] && tempInvalidFields[item] !== false)) {
|
||||
delete tempInvalidFields[item]
|
||||
}
|
||||
})
|
||||
|
||||
// 循环字段是否存在于校验规则中
|
||||
for (let i in this.formRules) {
|
||||
for (let j in tempInvalidFields) {
|
||||
if (i === j) {
|
||||
fieldsValue[i] = tempInvalidFields[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = []
|
||||
let example = null
|
||||
|
||||
let newFormData = {}
|
||||
this.childrens.forEach(v => {
|
||||
newFormData[v.name] = invalidFields[v.name] || this._getValue(v.name, '')
|
||||
})
|
||||
if (this.validator) {
|
||||
for (let i in fieldsValue) {
|
||||
// 循环校验,目的是异步校验
|
||||
const resultData = await this.validator.validateUpdate({
|
||||
[i]: fieldsValue[i]
|
||||
}, this.formData)
|
||||
|
||||
// 未通过
|
||||
if (resultData) {
|
||||
// 获取当前未通过子组件实例
|
||||
example = this.childrens.find(child => child.name === resultData.key)
|
||||
// 获取easyInput 组件实例
|
||||
const inputComp = this.inputChildrens.find(child => child.rename === (example && example.name))
|
||||
if (inputComp) {
|
||||
inputComp.errMsg = resultData.errorMessage
|
||||
}
|
||||
result.push(resultData)
|
||||
// 区分触发类型
|
||||
if (this.errShowType === 'undertext') {
|
||||
if (example) example.errMsg = resultData.errorMessage
|
||||
} else {
|
||||
if (this.errShowType === 'toast') {
|
||||
uni.showToast({
|
||||
title: resultData.errorMessage || '校验错误',
|
||||
icon: 'none'
|
||||
})
|
||||
break
|
||||
} else if (this.errShowType === 'modal') {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: resultData.errorMessage || '校验错误'
|
||||
})
|
||||
break
|
||||
} else {
|
||||
if (example) example.errMsg = resultData.errorMessage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(result)) {
|
||||
if (result.length === 0) result = null
|
||||
}
|
||||
|
||||
if (type === 'submit') {
|
||||
this.$emit('submit', {
|
||||
detail: {
|
||||
value: newFormData,
|
||||
errors: result
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$emit('validate', result)
|
||||
}
|
||||
|
||||
callback && typeof callback === 'function' && callback(result, newFormData)
|
||||
|
||||
if (promise && callback) {
|
||||
return promise
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 手动提交校验表单
|
||||
* 对整个表单进行校验的方法,参数为一个回调函数。
|
||||
*/
|
||||
submit(callback) {
|
||||
// Object.assign(this.formData,formData)
|
||||
return this.validateAll(this.formData, 'submit', callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 校验表单
|
||||
* 对整个表单进行校验的方法,参数为一个回调函数。
|
||||
*/
|
||||
validate(callback) {
|
||||
return this.validateAll(this.formData, '', callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* 部分表单校验
|
||||
* @param {Object} props
|
||||
* @param {Object} cb
|
||||
*/
|
||||
validateField(props, callback) {
|
||||
props = [].concat(props);
|
||||
let invalidFields = {}
|
||||
this.childrens.forEach(item => {
|
||||
if (props.indexOf(item.name) !== -1) {
|
||||
invalidFields = Object.assign({}, invalidFields, {
|
||||
[item.name]: this.formData[item.name]
|
||||
})
|
||||
}
|
||||
})
|
||||
return this.validateAll(invalidFields, '', callback)
|
||||
},
|
||||
|
||||
/**
|
||||
* 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果
|
||||
*/
|
||||
resetFields() {
|
||||
this.resetForm()
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
|
||||
*/
|
||||
clearValidate(props) {
|
||||
props = [].concat(props);
|
||||
this.childrens.forEach(item => {
|
||||
const inputComp = this.inputChildrens.find(child => child.rename === item.name)
|
||||
if (props.length === 0) {
|
||||
item.errMsg = ''
|
||||
if (inputComp) {
|
||||
inputComp.errMsg = ''
|
||||
}
|
||||
} else {
|
||||
if (props.indexOf(item.name) !== -1) {
|
||||
item.errMsg = ''
|
||||
if (inputComp) {
|
||||
inputComp.errMsg = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 把 value 转换成指定的类型
|
||||
* @param {Object} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
_getValue(key, value) {
|
||||
const rules = (this.formRules[key] && this.formRules[key].rules) || []
|
||||
const isRuleNum = rules.find(val => val.format && this.type_filter(val.format))
|
||||
const isRuleBool = rules.find(val => val.format && val.format === 'boolean' || val.format === 'bool')
|
||||
// 输入值为 number
|
||||
if (isRuleNum) {
|
||||
value = isNaN(value) ? value : (value === '' || value === null ? null : Number(value))
|
||||
}
|
||||
// 简单判断真假值
|
||||
if (isRuleBool) {
|
||||
value = !value ? false : true
|
||||
}
|
||||
return value
|
||||
},
|
||||
/**
|
||||
* 过滤数字类型
|
||||
* @param {Object} format
|
||||
*/
|
||||
type_filter(format) {
|
||||
return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-forms--top {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
</style>
|
||||
442
components/uni-forms/validate.js
Executable file
442
components/uni-forms/validate.js
Executable file
@ -0,0 +1,442 @@
|
||||
|
||||
var pattern = {
|
||||
email: /^\S+?@\S+?\.\S+?$/,
|
||||
url: new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", 'i')
|
||||
};
|
||||
|
||||
const FORMAT_MAPPING = {
|
||||
"int": 'number',
|
||||
"bool": 'boolean',
|
||||
"double": 'number',
|
||||
"long": 'number',
|
||||
"password": 'string'
|
||||
}
|
||||
|
||||
function formatMessage(args, resources) {
|
||||
var defaultMessage = ['label']
|
||||
defaultMessage.forEach((item) => {
|
||||
if (args[item] === undefined) {
|
||||
args[item] = ''
|
||||
}
|
||||
})
|
||||
|
||||
let str = resources
|
||||
for (let key in args) {
|
||||
let reg = new RegExp('{' + key + '}')
|
||||
str = str.replace(reg, args[key])
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function isEmptyValue(value, type) {
|
||||
if (value === undefined || value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && !value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(value) && !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === 'object' && !Object.keys(value).length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const types = {
|
||||
integer(value) {
|
||||
return types.number(value) && parseInt(value, 10) === value;
|
||||
},
|
||||
string(value) {
|
||||
return typeof value === 'string';
|
||||
},
|
||||
number(value) {
|
||||
if (isNaN(value)) {
|
||||
return false;
|
||||
}
|
||||
return typeof value === 'number';
|
||||
},
|
||||
"boolean": function (value) {
|
||||
return typeof value === 'boolean';
|
||||
},
|
||||
"float": function (value) {
|
||||
return types.number(value) && !types.integer(value);
|
||||
},
|
||||
array(value) {
|
||||
return Array.isArray(value);
|
||||
},
|
||||
object(value) {
|
||||
return typeof value === 'object' && !types.array(value);
|
||||
},
|
||||
date(value) {
|
||||
var v
|
||||
if (value instanceof Date) {
|
||||
v = value;
|
||||
} else {
|
||||
v = new Date(value);
|
||||
}
|
||||
return typeof v.getTime === 'function' && typeof v.getMonth === 'function' && typeof v.getYear === 'function' && !isNaN(v.getTime());
|
||||
},
|
||||
timestamp(value) {
|
||||
if (!this.integer(value) || Math.abs(value).toString().length > 16) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.date(value);
|
||||
},
|
||||
email(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
|
||||
},
|
||||
url(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.url);
|
||||
},
|
||||
pattern(reg, value) {
|
||||
try {
|
||||
return new RegExp(reg).test(value);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
method(value) {
|
||||
return typeof value === 'function';
|
||||
}
|
||||
}
|
||||
|
||||
class RuleValidator {
|
||||
|
||||
constructor(message) {
|
||||
this._message = message
|
||||
}
|
||||
|
||||
async validateRule(key, value, data, allData) {
|
||||
var result = null
|
||||
|
||||
let rules = key.rules
|
||||
|
||||
let hasRequired = rules.findIndex((item) => {
|
||||
return item.required
|
||||
})
|
||||
if (hasRequired < 0) {
|
||||
if (value === null || value === undefined) {
|
||||
return result
|
||||
}
|
||||
if (typeof value === 'string' && !value.length) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
var message = this._message
|
||||
|
||||
if (rules === undefined) {
|
||||
return message['default']
|
||||
}
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
let rule = rules[i]
|
||||
let vt = this._getValidateType(rule)
|
||||
|
||||
if (key.label !== undefined) {
|
||||
Object.assign(rule, {
|
||||
label: key.label
|
||||
})
|
||||
}
|
||||
|
||||
if (RuleValidatorHelper[vt]) {
|
||||
result = RuleValidatorHelper[vt](rule, value, message)
|
||||
if (result != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.validateExpr) {
|
||||
let now = Date.now()
|
||||
let resultExpr = rule.validateExpr(value, allData, now)
|
||||
if (resultExpr === false) {
|
||||
result = this._getMessage(rule, rule.errorMessage || this._message['default'])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.validateFunction) {
|
||||
result = await this.validateFunction(rule, value, data, allData, vt)
|
||||
if (result !== null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async validateFunction(rule, value, data, allData, vt) {
|
||||
let result = null
|
||||
try {
|
||||
let callbackMessage = null
|
||||
const res = await rule.validateFunction(rule, value, allData || data, (message) => {
|
||||
callbackMessage = message
|
||||
})
|
||||
if (callbackMessage || (typeof res === 'string' && res) || res === false) {
|
||||
result = this._getMessage(rule, callbackMessage || res, vt)
|
||||
}
|
||||
} catch (e) {
|
||||
result = this._getMessage(rule, e.message, vt)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
_getMessage(rule, message, vt) {
|
||||
return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
|
||||
}
|
||||
|
||||
_getValidateType(rule) {
|
||||
// TODO
|
||||
var result = ''
|
||||
if (rule.required) {
|
||||
result = 'required'
|
||||
} else if (rule.format) {
|
||||
result = 'format'
|
||||
} else if (rule.range) {
|
||||
result = 'range'
|
||||
} else if (rule.maximum || rule.minimum) {
|
||||
result = 'rangeNumber'
|
||||
} else if (rule.maxLength || rule.minLength) {
|
||||
result = 'rangeLength'
|
||||
} else if (rule.pattern) {
|
||||
result = 'pattern'
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
const RuleValidatorHelper = {
|
||||
required(rule, value, message) {
|
||||
if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.required);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
range(rule, value, message) {
|
||||
const { range, errorMessage } = rule;
|
||||
|
||||
let list = new Array(range.length);
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const item = range[i];
|
||||
if (types.object(item) && item.value !== undefined) {
|
||||
list[i] = item.value;
|
||||
} else {
|
||||
list[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
let result = false
|
||||
if (Array.isArray(value)) {
|
||||
result = (new Set(value.concat(list)).size === list.length);
|
||||
} else {
|
||||
if (list.indexOf(value) > -1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return formatMessage(rule, errorMessage || message['enum']);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
rangeNumber(rule, value, message) {
|
||||
if (!types.number(value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
let { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = rule;
|
||||
let min = exclusiveMinimum ? value <= minimum : value < minimum;
|
||||
let max = exclusiveMaximum ? value >= maximum : value > maximum;
|
||||
|
||||
if (minimum !== undefined && min) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'].min)
|
||||
} else if (maximum !== undefined && max) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'].max)
|
||||
} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'].range)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
rangeLength(rule, value, message) {
|
||||
if (!types.string(value) && !types.array(value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
let min = rule.minLength;
|
||||
let max = rule.maxLength;
|
||||
let val = value.length;
|
||||
|
||||
if (min !== undefined && val < min) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].min)
|
||||
} else if (max !== undefined && val > max) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].max)
|
||||
} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].range)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
pattern(rule, value, message) {
|
||||
if (!types['pattern'](rule.pattern, value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
format(rule, value, message) {
|
||||
var customTypes = Object.keys(types);
|
||||
var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : rule.format;
|
||||
|
||||
if (customTypes.indexOf(format) > -1) {
|
||||
if (!types[format](value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.types[format]);
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaValidator extends RuleValidator {
|
||||
|
||||
constructor(schema, options) {
|
||||
super(SchemaValidator.message);
|
||||
|
||||
this._schema = schema
|
||||
this._options = options || null
|
||||
}
|
||||
|
||||
updateSchema(schema) {
|
||||
this._schema = schema
|
||||
}
|
||||
|
||||
async validate(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidate(data, false, allData)
|
||||
}
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
async validateAll(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidate(data, true, allData)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async validateUpdate(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidateUpdate(data, false, allData)
|
||||
}
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
async invokeValidate(data, all, allData) {
|
||||
let result = []
|
||||
let schema = this._schema
|
||||
for (let key in schema) {
|
||||
let value = schema[key]
|
||||
let errorMessage = await this.validateRule(value, data[key], data, allData)
|
||||
if (errorMessage != null) {
|
||||
result.push({
|
||||
key,
|
||||
errorMessage
|
||||
})
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async invokeValidateUpdate(data, all, allData) {
|
||||
let result = []
|
||||
for (let key in data) {
|
||||
let errorMessage = await this.validateRule(this._schema[key], data[key], data, allData)
|
||||
if (errorMessage != null) {
|
||||
result.push({
|
||||
key,
|
||||
errorMessage
|
||||
})
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
_checkFieldInSchema(data) {
|
||||
var keys = Object.keys(data)
|
||||
var keys2 = Object.keys(this._schema)
|
||||
if (new Set(keys.concat(keys2)).size === keys2.length) {
|
||||
return ''
|
||||
}
|
||||
return [{
|
||||
key: 'invalid',
|
||||
errorMessage: SchemaValidator.message['defaultInvalid']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
function Message() {
|
||||
return {
|
||||
default: '验证错误',
|
||||
defaultInvalid: '字段超出范围',
|
||||
required: '{label}必填',
|
||||
'enum': '{label}超出范围',
|
||||
whitespace: '{label}不能为空',
|
||||
date: {
|
||||
format: '{label}日期{value}格式无效',
|
||||
parse: '{label}日期无法解析,{value}无效',
|
||||
invalid: '{label}日期{value}无效'
|
||||
},
|
||||
types: {
|
||||
string: '{label}类型无效',
|
||||
array: '{label}类型无效',
|
||||
object: '{label}类型无效',
|
||||
number: '{label}类型无效',
|
||||
date: '{label}类型无效',
|
||||
boolean: '{label}类型无效',
|
||||
integer: '{label}类型无效',
|
||||
float: '{label}类型无效',
|
||||
regexp: '{label}无效',
|
||||
email: '{label}类型无效',
|
||||
url: '{label}类型无效'
|
||||
},
|
||||
length: {
|
||||
min: '{label}长度不能少于{minLength}',
|
||||
max: '{label}长度不能超过{maxLength}',
|
||||
range: '{label}必须介于{minLength}和{maxLength}之间'
|
||||
},
|
||||
number: {
|
||||
min: '{label}不能小于{minimum}',
|
||||
max: '{label}不能大于{maximum}',
|
||||
range: '{label}必须介于{minimum}and{maximum}之间'
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '{label}格式不匹配'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
SchemaValidator.message = new Message();
|
||||
|
||||
export default SchemaValidator
|
||||
224
components/uni-goods-nav/uni-goods-nav.vue
Normal file
224
components/uni-goods-nav/uni-goods-nav.vue
Normal file
@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<view class="uni-goods-nav">
|
||||
<!-- 底部占位 -->
|
||||
<view class="uni-tab__seat" />
|
||||
<view class="uni-tab__cart-box flex">
|
||||
<view class="flex uni-tab__cart-sub-left">
|
||||
<view v-for="(item,index) in options" :key="index" class="flex uni-tab__cart-button-left uni-tab__shop-cart" @click="onClick(index,item)">
|
||||
<view class="uni-tab__icon">
|
||||
<uni-icons :type="item.icon" size="20" color="#646566"></uni-icons>
|
||||
<!-- <image class="image" :src="item.icon" mode="widthFix" /> -->
|
||||
</view>
|
||||
<text class="uni-tab__text">{{ item.text }}</text>
|
||||
<view class="flex uni-tab__dot-box">
|
||||
<text v-if="item.info" :class="{ 'uni-tab__dots': item.info > 9 }" class="uni-tab__dot " :style="{'backgroundColor':item.infoBackgroundColor?item.infoBackgroundColor:'#ff0000',
|
||||
color:item.infoColor?item.infoColor:'#fff'
|
||||
}">{{ item.info }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view :class="{'uni-tab__right':fill}" class="flex uni-tab__cart-sub-right ">
|
||||
<view v-for="(item,index) in buttonGroup" :key="index" :style="{backgroundColor:item.backgroundColor,color:item.color}" class="flex uni-tab__cart-button-right" @click="buttonClick(index,item)"><text :style="{color:item.color}" class="uni-tab__cart-button-right-text">{{ item.text }}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
/**
|
||||
* GoodsNav 商品导航
|
||||
* @description 商品加入购物车、立即购买等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=865
|
||||
* @property {Array} options 组件参数
|
||||
* @property {Array} buttonGroup 组件按钮组参数
|
||||
* @property {Boolean} fill = [true | false] 组件按钮组参数
|
||||
* @event {Function} click 左侧点击事件
|
||||
* @event {Function} buttonClick 右侧按钮组点击事件
|
||||
* @example <uni-goods-nav :fill="true" options="" buttonGroup="buttonGroup" @click="" @buttonClick="" />
|
||||
*/
|
||||
export default {
|
||||
name: 'UniGoodsNav',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [{
|
||||
icon: 'shop',
|
||||
text: '店铺',
|
||||
}, {
|
||||
icon: 'cart',
|
||||
text: '购物车'
|
||||
}]
|
||||
}
|
||||
},
|
||||
buttonGroup: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [{
|
||||
text: '加入购物车',
|
||||
backgroundColor: '#ffa200',
|
||||
color: '#fff'
|
||||
},
|
||||
{
|
||||
text: '立即购买',
|
||||
backgroundColor: '#ff0000',
|
||||
color: '#fff'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
fill: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(index, item) {
|
||||
this.$emit('click', {
|
||||
index,
|
||||
content: item,
|
||||
|
||||
})
|
||||
},
|
||||
buttonClick(index, item) {
|
||||
if (uni.report) {
|
||||
uni.report(item.text, item.text)
|
||||
}
|
||||
this.$emit('buttonClick', {
|
||||
index,
|
||||
content: item
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-goods-nav {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-tab__cart-box {
|
||||
flex: 1;
|
||||
height: 50px;
|
||||
background-color: #fff;
|
||||
z-index: 900;
|
||||
}
|
||||
|
||||
.uni-tab__cart-sub-left {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
.uni-tab__cart-sub-right {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-tab__right {
|
||||
margin: 5px 0;
|
||||
margin-right: 10px;
|
||||
border-radius: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-tab__cart-button-left {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin: 0 10px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-tab__icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.uni-tab__text {
|
||||
margin-top: 3px;
|
||||
font-size: 12px;
|
||||
color: #646566;
|
||||
}
|
||||
|
||||
.uni-tab__cart-button-right {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-tab__cart-button-right-text {
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.uni-tab__cart-button-right:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.uni-tab__dot-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
top: 2px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-tab__dot {
|
||||
padding: 0 4px;
|
||||
line-height: 15px;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
background-color: #ff0000;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.uni-tab__dots {
|
||||
padding: 0 4px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.uni-tab__color-y {
|
||||
background-color: #ffa200;
|
||||
}
|
||||
|
||||
.uni-tab__color-r {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
</style>
|
||||
162
components/uni-grid-item/uni-grid-item copy.vue
Normal file
162
components/uni-grid-item/uni-grid-item copy.vue
Normal file
@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<view v-if="width" :style="'width:'+width+';'+(square?'height:'+width:'')" class="uni-grid-item">
|
||||
<view :class="{ 'uni-grid-item--border': showBorder, 'uni-grid-item--border-top': showBorder && index < column, 'uni-highlight': highlight }" :style="{'border-right-color': borderColor ,'border-bottom-color': borderColor ,'border-top-color': borderColor }" class="uni-grid-item__box" @click="_onClick">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* GridItem 宫格
|
||||
* @description 宫格组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=27
|
||||
* @property {Number} index 子组件的唯一标识 ,点击gird会返回当前的标识
|
||||
*/
|
||||
export default {
|
||||
name: 'UniGridItem',
|
||||
inject: ['grid'],
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
column: 0,
|
||||
showBorder: true,
|
||||
square: true,
|
||||
highlight: true,
|
||||
left: 0,
|
||||
top: 0,
|
||||
openNum: 2,
|
||||
width: 0,
|
||||
borderColor: '#e5e5e5'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.column = this.grid.column
|
||||
this.showBorder = this.grid.showBorder
|
||||
this.square = this.grid.square
|
||||
this.highlight = this.grid.highlight
|
||||
this.top = this.hor === 0 ? this.grid.hor : this.hor
|
||||
this.left = this.ver === 0 ? this.grid.ver : this.ver
|
||||
this.borderColor = this.grid.borderColor
|
||||
this.grid.children.push(this)
|
||||
// this.grid.init()
|
||||
this.width = this.grid.width
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.grid.children.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.grid.children.splice(index, 1)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
_onClick() {
|
||||
this.grid.change({
|
||||
detail: {
|
||||
index: this.index
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-grid-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-grid-item__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-grid-item--border {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-bottom-color: #e5e5e5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0.5px;
|
||||
border-right-color: #e5e5e5;
|
||||
border-right-style: solid;
|
||||
border-right-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-grid-item--border:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: inherit;
|
||||
border-right-style: solid;
|
||||
border-right-width: 1px;
|
||||
border-right-color: inherit;
|
||||
box-sizing: border-box;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: left top;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-grid-item--border-top {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-grid-item--border-top:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: inherit;
|
||||
box-sizing: border-box;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
transform: scale(0.5);
|
||||
transform-origin: left top;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-highlight:active {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
</style>
|
||||
123
components/uni-grid-item/uni-grid-item.vue
Normal file
123
components/uni-grid-item/uni-grid-item.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view v-if="width" :style="'width:'+width+';'+(square?'height:'+width:'')" class="uni-grid-item">
|
||||
<view :class="{ 'uni-grid-item--border': showBorder, 'uni-grid-item--border-top': showBorder && index < column, 'uni-highlight': highlight }" :style="{'border-right-color': borderColor ,'border-bottom-color': borderColor ,'border-top-color': borderColor }" class="uni-grid-item__box" @click="_onClick">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* GridItem 宫格
|
||||
* @description 宫格组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=27
|
||||
* @property {Number} index 子组件的唯一标识 ,点击gird会返回当前的标识
|
||||
*/
|
||||
export default {
|
||||
name: 'UniGridItem',
|
||||
inject: ['grid'],
|
||||
props: {
|
||||
index: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
column: 0,
|
||||
showBorder: true,
|
||||
square: true,
|
||||
highlight: true,
|
||||
left: 0,
|
||||
top: 0,
|
||||
openNum: 2,
|
||||
width: 0,
|
||||
borderColor: '#e5e5e5'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.column = this.grid.column
|
||||
this.showBorder = this.grid.showBorder
|
||||
this.square = this.grid.square
|
||||
this.highlight = this.grid.highlight
|
||||
this.top = this.hor === 0 ? this.grid.hor : this.hor
|
||||
this.left = this.ver === 0 ? this.grid.ver : this.ver
|
||||
this.borderColor = this.grid.borderColor
|
||||
this.grid.children.push(this)
|
||||
// this.grid.init()
|
||||
this.width = this.grid.width
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.grid.children.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.grid.children.splice(index, 1)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
_onClick() {
|
||||
this.grid.change({
|
||||
detail: {
|
||||
index: this.index
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-grid-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-grid-item__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-grid-item--border {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-bottom-color: #e5e5e5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0.5px;
|
||||
border-right-color: #e5e5e5;
|
||||
border-right-style: solid;
|
||||
border-right-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 0;
|
||||
border-bottom: 1px #e5e5e5 solid;
|
||||
border-right: 1px #e5e5e5 solid;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-grid-item--border-top {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
border-top: 1px #e5e5e5 solid;
|
||||
z-index: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-highlight:active {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
</style>
|
||||
140
components/uni-grid/uni-grid.vue
Normal file
140
components/uni-grid/uni-grid.vue
Normal file
@ -0,0 +1,140 @@
|
||||
<template>
|
||||
<view class="uni-grid-wrap">
|
||||
<view :id="elId" ref="uni-grid" class="uni-grid" :class="{ 'uni-grid--border': showBorder }" :style="{ 'border-left-color':borderColor}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const dom = uni.requireNativePlugin('dom');
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* Grid 宫格
|
||||
* @description 宫格组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=27
|
||||
* @property {Number} column 每列显示个数
|
||||
* @property {String} borderColor 边框颜色
|
||||
* @property {Boolean} showBorder 是否显示边框
|
||||
* @property {Boolean} square 是否方形显示
|
||||
* @property {Boolean} Boolean 点击背景是否高亮
|
||||
* @event {Function} change 点击 grid 触发,e={detail:{index:0}},index 为当前点击 gird 下标
|
||||
*/
|
||||
export default {
|
||||
name: 'UniGrid',
|
||||
props: {
|
||||
// 每列显示个数
|
||||
column: {
|
||||
type: Number,
|
||||
default: 3
|
||||
},
|
||||
// 是否显示边框
|
||||
showBorder: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String,
|
||||
default: '#e5e5e5'
|
||||
},
|
||||
// 是否正方形显示,默认为 true
|
||||
square: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
highlight: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
grid: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
return {
|
||||
elId,
|
||||
width: 0
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.init()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
setTimeout(() => {
|
||||
this._getSize((width) => {
|
||||
this.children.forEach((item, index) => {
|
||||
item.width = width
|
||||
})
|
||||
})
|
||||
}, 50)
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('change', e)
|
||||
},
|
||||
_getSize(fn) {
|
||||
// #ifndef APP-NVUE
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.elId}`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.width = parseInt((ret[0].width - 1) / this.column) + 'px'
|
||||
fn(this.width)
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['uni-grid'], (ret) => {
|
||||
this.width = parseInt((ret.size.width - 1) / this.column) + 'px'
|
||||
fn(this.width)
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-grid-wrap {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
/* #ifdef H5 */
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-grid {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.uni-grid--border {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-left-color: #e5e5e5;
|
||||
border-left-style: solid;
|
||||
border-left-width: 0.5px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 1;
|
||||
border-left: 1px #e5e5e5 solid;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
125
components/uni-group/uni-group.vue
Normal file
125
components/uni-group/uni-group.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<view class="uni-group" :class="['uni-group--'+mode ,margin?'group-margin':'']" :style="{marginTop: `${top}px` }">
|
||||
<slot name="title">
|
||||
<view v-if="title" class="uni-group__title" :style="{'padding-left':border?'30px':'15px'}">
|
||||
<text class="uni-group__title-text">{{ title }}</text>
|
||||
</view>
|
||||
</slot>
|
||||
<view class="uni-group__content" :class="{'group-conent-padding':border}">
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Group 分组
|
||||
* @description 表单字段分组
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=21002
|
||||
* @property {String} title 主标题
|
||||
* @property {Number} top 分组间隔
|
||||
*/
|
||||
export default {
|
||||
name: 'uniGroup',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
top: {
|
||||
type: [Number, String],
|
||||
default: 10
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
margin: false,
|
||||
border: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
title(newVal) {
|
||||
if (uni.report && newVal !== '') {
|
||||
uni.report('title', newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.form = this.getForm()
|
||||
if (this.form) {
|
||||
this.margin = true
|
||||
this.border = this.form.border
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm() {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== 'uniForms') {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-group {
|
||||
background: #fff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.group-margin {
|
||||
margin: 0 -15px;
|
||||
}
|
||||
|
||||
.uni-group__title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
padding-left: 15px;
|
||||
height: 40px;
|
||||
background-color: #f8f8f8;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-group__content {
|
||||
padding: 15px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.group-conent-padding {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.uni-group__title-text {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.distraction {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-group--card {
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 5px 1px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
</style>
|
||||
132
components/uni-icons/icons.js
Normal file
132
components/uni-icons/icons.js
Normal file
@ -0,0 +1,132 @@
|
||||
export default {
|
||||
"pulldown": "\ue588",
|
||||
"refreshempty": "\ue461",
|
||||
"back": "\ue471",
|
||||
"forward": "\ue470",
|
||||
"more": "\ue507",
|
||||
"more-filled": "\ue537",
|
||||
"scan": "\ue612",
|
||||
"qq": "\ue264",
|
||||
"weibo": "\ue260",
|
||||
"weixin": "\ue261",
|
||||
"pengyouquan": "\ue262",
|
||||
"loop": "\ue565",
|
||||
"refresh": "\ue407",
|
||||
"refresh-filled": "\ue437",
|
||||
"arrowthindown": "\ue585",
|
||||
"arrowthinleft": "\ue586",
|
||||
"arrowthinright": "\ue587",
|
||||
"arrowthinup": "\ue584",
|
||||
"undo-filled": "\ue7d6",
|
||||
"undo": "\ue406",
|
||||
"redo": "\ue405",
|
||||
"redo-filled": "\ue7d9",
|
||||
"bars": "\ue563",
|
||||
"chatboxes": "\ue203",
|
||||
"camera": "\ue301",
|
||||
"chatboxes-filled": "\ue233",
|
||||
"camera-filled": "\ue7ef",
|
||||
"cart-filled": "\ue7f4",
|
||||
"cart": "\ue7f5",
|
||||
"checkbox-filled": "\ue442",
|
||||
"checkbox": "\ue7fa",
|
||||
"arrowleft": "\ue582",
|
||||
"arrowdown": "\ue581",
|
||||
"arrowright": "\ue583",
|
||||
"smallcircle-filled": "\ue801",
|
||||
"arrowup": "\ue580",
|
||||
"circle": "\ue411",
|
||||
"eye-filled": "\ue568",
|
||||
"eye-slash-filled": "\ue822",
|
||||
"eye-slash": "\ue823",
|
||||
"eye": "\ue824",
|
||||
"flag-filled": "\ue825",
|
||||
"flag": "\ue508",
|
||||
"gear-filled": "\ue532",
|
||||
"reload": "\ue462",
|
||||
"gear": "\ue502",
|
||||
"hand-thumbsdown-filled": "\ue83b",
|
||||
"hand-thumbsdown": "\ue83c",
|
||||
"hand-thumbsup-filled": "\ue83d",
|
||||
"heart-filled": "\ue83e",
|
||||
"hand-thumbsup": "\ue83f",
|
||||
"heart": "\ue840",
|
||||
"home": "\ue500",
|
||||
"info": "\ue504",
|
||||
"home-filled": "\ue530",
|
||||
"info-filled": "\ue534",
|
||||
"circle-filled": "\ue441",
|
||||
"chat-filled": "\ue847",
|
||||
"chat": "\ue263",
|
||||
"mail-open-filled": "\ue84d",
|
||||
"email-filled": "\ue231",
|
||||
"mail-open": "\ue84e",
|
||||
"email": "\ue201",
|
||||
"checkmarkempty": "\ue472",
|
||||
"list": "\ue562",
|
||||
"locked-filled": "\ue856",
|
||||
"locked": "\ue506",
|
||||
"map-filled": "\ue85c",
|
||||
"map-pin": "\ue85e",
|
||||
"map-pin-ellipse": "\ue864",
|
||||
"map": "\ue364",
|
||||
"minus-filled": "\ue440",
|
||||
"mic-filled": "\ue332",
|
||||
"minus": "\ue410",
|
||||
"micoff": "\ue360",
|
||||
"mic": "\ue302",
|
||||
"clear": "\ue434",
|
||||
"smallcircle": "\ue868",
|
||||
"close": "\ue404",
|
||||
"closeempty": "\ue460",
|
||||
"paperclip": "\ue567",
|
||||
"paperplane": "\ue503",
|
||||
"paperplane-filled": "\ue86e",
|
||||
"person-filled": "\ue131",
|
||||
"contact-filled": "\ue130",
|
||||
"person": "\ue101",
|
||||
"contact": "\ue100",
|
||||
"images-filled": "\ue87a",
|
||||
"phone": "\ue200",
|
||||
"images": "\ue87b",
|
||||
"image": "\ue363",
|
||||
"image-filled": "\ue877",
|
||||
"location-filled": "\ue333",
|
||||
"location": "\ue303",
|
||||
"plus-filled": "\ue439",
|
||||
"plus": "\ue409",
|
||||
"plusempty": "\ue468",
|
||||
"help-filled": "\ue535",
|
||||
"help": "\ue505",
|
||||
"navigate-filled": "\ue884",
|
||||
"navigate": "\ue501",
|
||||
"mic-slash-filled": "\ue892",
|
||||
"search": "\ue466",
|
||||
"settings": "\ue560",
|
||||
"sound": "\ue590",
|
||||
"sound-filled": "\ue8a1",
|
||||
"spinner-cycle": "\ue465",
|
||||
"download-filled": "\ue8a4",
|
||||
"personadd-filled": "\ue132",
|
||||
"videocam-filled": "\ue8af",
|
||||
"personadd": "\ue102",
|
||||
"upload": "\ue402",
|
||||
"upload-filled": "\ue8b1",
|
||||
"starhalf": "\ue463",
|
||||
"star-filled": "\ue438",
|
||||
"star": "\ue408",
|
||||
"trash": "\ue401",
|
||||
"phone-filled": "\ue230",
|
||||
"compose": "\ue400",
|
||||
"videocam": "\ue300",
|
||||
"trash-filled": "\ue8dc",
|
||||
"download": "\ue403",
|
||||
"chatbubble-filled": "\ue232",
|
||||
"chatbubble": "\ue202",
|
||||
"cloud-download": "\ue8e4",
|
||||
"cloud-upload-filled": "\ue8e5",
|
||||
"cloud-upload": "\ue8e6",
|
||||
"cloud-download-filled": "\ue8e9",
|
||||
"headphones":"\ue8bf",
|
||||
"shop":"\ue609"
|
||||
}
|
||||
70
components/uni-icons/uni-icons.vue
Normal file
70
components/uni-icons/uni-icons.vue
Normal file
File diff suppressed because one or more lines are too long
BIN
components/uni-icons/uni.ttf
Normal file
BIN
components/uni-icons/uni.ttf
Normal file
Binary file not shown.
145
components/uni-indexed-list/uni-indexed-list-item.vue
Normal file
145
components/uni-indexed-list/uni-indexed-list-item.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="loaded || list.itemIndex < 15" class="uni-indexed-list__title-wrapper">
|
||||
<text v-if="list.items && list.items.length > 0" class="uni-indexed-list__title">{{ list.key }}</text>
|
||||
</view>
|
||||
<view v-if="(loaded || list.itemIndex < 15) && list.items && list.items.length > 0" class="uni-indexed-list__list">
|
||||
<view v-for="(item, index) in list.items" :key="index" class="uni-indexed-list__item" hover-class="uni-indexed-list__item--hover">
|
||||
<view class="uni-indexed-list__item-container" @click="onClick(idx, index)">
|
||||
<view class="uni-indexed-list__item-border" :class="{'uni-indexed-list__item-border--last':index===list.items.length-1}">
|
||||
<view v-if="showSelect" style="margin-right: 20rpx;">
|
||||
<uni-icons :type="item.checked ? 'checkbox-filled' : 'circle'" :color="item.checked ? '#007aff' : '#aaa'" size="24" />
|
||||
</view>
|
||||
<text class="uni-indexed-list__item-content">{{ item.name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
export default {
|
||||
name: 'UniIndexedList',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
loaded: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
idx: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
list: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
showSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick(idx, index) {
|
||||
this.$emit("itemClick", {
|
||||
idx,
|
||||
index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-indexed-list__list {
|
||||
background-color: #ffffff;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-top-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item {
|
||||
font-size: 16px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-container {
|
||||
padding-left: 15px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-border {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
padding: 15px;
|
||||
padding-left: 0;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-border--last {
|
||||
border-bottom-width: 0px;
|
||||
}
|
||||
|
||||
.uni-indexed-list__item-content {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-indexed-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-indexed-list__title-wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
/* #endif */
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.uni-indexed-list__title {
|
||||
padding: 6px 12px;
|
||||
line-height: 24px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
358
components/uni-indexed-list/uni-indexed-list.vue
Normal file
358
components/uni-indexed-list/uni-indexed-list.vue
Normal file
@ -0,0 +1,358 @@
|
||||
<template>
|
||||
<view class="uni-indexed-list" ref="list" id="list">
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<list class="uni-indexed-list__scroll" scrollable="true" show-scrollbar="false">
|
||||
<cell v-for="(list, idx) in lists" :key="idx" :ref="'uni-indexed-list-' + idx">
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<scroll-view :scroll-into-view="scrollViewId" class="uni-indexed-list__scroll" scroll-y>
|
||||
<view v-for="(list, idx) in lists" :key="idx" :id="'uni-indexed-list-' + idx">
|
||||
<!-- #endif -->
|
||||
<indexed-list-item :list="list" :loaded="loaded" :idx="idx" :showSelect="showSelect" @itemClick="onClick"></indexed-list-item>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
</cell>
|
||||
</list>
|
||||
<!-- #endif -->
|
||||
<view class="uni-indexed-list__menu" :class="touchmove ? 'uni-indexed-list__menu--active' : ''" @touchstart="touchStart" @touchmove.stop.prevent="touchMove" @touchend="touchEnd" @mousedown.stop="mousedown" @mousemove.stop.prevent="mousemove" @mouseleave.stop="mouseleave">
|
||||
<view v-for="(list, key) in lists" :key="key" class="uni-indexed-list__menu-item">
|
||||
<text class="uni-indexed-list__menu-text" :class="touchmoveIndex == key ? 'uni-indexed-list__menu-text--active' : ''">{{ list.key }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="touchmove" class="uni-indexed-list__alert-wrapper">
|
||||
<text class="uni-indexed-list__alert">{{ lists[touchmoveIndex].key }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
import indexedListItem from './uni-indexed-list-item.vue'
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom');
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
function throttle(func, delay) {
|
||||
var prev = Date.now();
|
||||
return function() {
|
||||
var context = this;
|
||||
var args = arguments;
|
||||
var now = Date.now();
|
||||
if (now - prev >= delay) {
|
||||
func.apply(context, args);
|
||||
prev = Date.now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
let pageY = e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
if (this.touchmoveIndex === index) {
|
||||
return false
|
||||
}
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
// #ifndef APP-NVUE
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
|
||||
animated: false
|
||||
})
|
||||
this.touchmoveIndex = index
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
const throttleTouchMove = throttle(touchMove, 40)
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* IndexedList 索引列表
|
||||
* @description 用于展示索引列表
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=375
|
||||
* @property {Boolean} showSelect = [true|false] 展示模式
|
||||
* @value true 展示模式
|
||||
* @value false 选择模式
|
||||
* @property {Object} options 索引列表需要的数据对象
|
||||
* @event {Function} click 点击列表事件 ,返回当前选择项的事件对象
|
||||
* @example <uni-indexed-list options="" showSelect="false" @click=""></uni-indexed-list>
|
||||
*/
|
||||
export default {
|
||||
name: 'UniIndexedList',
|
||||
components: {
|
||||
uniIcons,
|
||||
indexedListItem
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
showSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lists: [],
|
||||
winHeight: 0,
|
||||
itemHeight: 0,
|
||||
winOffsetY: 0,
|
||||
touchmove: false,
|
||||
touchmoveIndex: -1,
|
||||
scrollViewId: '',
|
||||
touchmovable: true,
|
||||
loaded: false,
|
||||
isPC: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
options: {
|
||||
handler: function() {
|
||||
this.setList()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
this.isPC = this.IsPC()
|
||||
// #endif
|
||||
setTimeout(() => {
|
||||
this.setList()
|
||||
}, 50)
|
||||
setTimeout(() => {
|
||||
this.loaded = true
|
||||
}, 300);
|
||||
},
|
||||
methods: {
|
||||
setList() {
|
||||
let index = 0;
|
||||
this.lists = []
|
||||
this.options.forEach((value, index) => {
|
||||
if (value.data.length === 0) {
|
||||
return
|
||||
}
|
||||
let indexBefore = index
|
||||
let items = value.data.map(item => {
|
||||
let obj = {}
|
||||
obj['key'] = value.letter
|
||||
obj['name'] = item
|
||||
obj['itemIndex'] = index
|
||||
index++
|
||||
obj.checked = item.checked ? item.checked : false
|
||||
return obj
|
||||
})
|
||||
this.lists.push({
|
||||
title: value.letter,
|
||||
key: value.letter,
|
||||
items: items,
|
||||
itemIndex: indexBefore
|
||||
})
|
||||
})
|
||||
// #ifndef APP-NVUE
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('#list')
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.winOffsetY = ret[0].top
|
||||
this.winHeight = ret[0].height
|
||||
this.itemHeight = this.winHeight / this.lists.length
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['list'], (res) => {
|
||||
this.winOffsetY = res.size.top
|
||||
this.winHeight = res.size.height
|
||||
this.itemHeight = this.winHeight / this.lists.length
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
touchStart(e) {
|
||||
this.touchmove = true
|
||||
let pageY = this.isPC ? e.pageY : e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
// #ifdef APP-NVUE
|
||||
dom.scrollToElement(this.$refs['uni-indexed-list-' + index][0], {
|
||||
animated: false
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
touchMove(e) {
|
||||
// #ifndef APP-PLUS
|
||||
let pageY = this.isPC ? e.pageY : e.touches[0].pageY
|
||||
let index = Math.floor((pageY - this.winOffsetY) / this.itemHeight)
|
||||
if (this.touchmoveIndex === index) {
|
||||
return false
|
||||
}
|
||||
let item = this.lists[index]
|
||||
if (item) {
|
||||
this.scrollViewId = 'uni-indexed-list-' + index
|
||||
this.touchmoveIndex = index
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
throttleTouchMove.call(this, e)
|
||||
// #endif
|
||||
},
|
||||
touchEnd() {
|
||||
this.touchmove = false
|
||||
this.touchmoveIndex = -1
|
||||
},
|
||||
|
||||
/**
|
||||
* 兼容 PC @tian
|
||||
*/
|
||||
|
||||
mousedown(e) {
|
||||
if (!this.isPC) return
|
||||
this.touchStart(e)
|
||||
},
|
||||
mousemove(e) {
|
||||
if (!this.isPC) return
|
||||
this.touchMove(e)
|
||||
},
|
||||
mouseleave(e) {
|
||||
if (!this.isPC) return
|
||||
this.touchEnd(e)
|
||||
},
|
||||
|
||||
// #ifdef H5
|
||||
IsPC() {
|
||||
var userAgentInfo = navigator.userAgent;
|
||||
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
|
||||
var flag = true;
|
||||
for (var v = 0; v < Agents.length; v++) {
|
||||
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
// #endif
|
||||
|
||||
|
||||
onClick(e) {
|
||||
let {
|
||||
idx,
|
||||
index
|
||||
} = e
|
||||
let obj = {}
|
||||
for (let key in this.lists[idx].items[index]) {
|
||||
obj[key] = this.lists[idx].items[index][key]
|
||||
}
|
||||
let select = []
|
||||
if (this.showSelect) {
|
||||
this.lists[idx].items[index].checked = !this.lists[idx].items[index].checked
|
||||
this.lists.forEach((value, idx) => {
|
||||
value.items.forEach((item, index) => {
|
||||
if (item.checked) {
|
||||
let obj = {}
|
||||
for (let key in this.lists[idx].items[index]) {
|
||||
obj[key] = this.lists[idx].items[index][key]
|
||||
}
|
||||
select.push(obj)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
this.$emit('click', {
|
||||
item: obj,
|
||||
select: select
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-indexed-list {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-indexed-list__scroll {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu {
|
||||
width: 24px;
|
||||
background-color: lightgrey;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-text {
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu--active {
|
||||
background-color: #c8c8c8;
|
||||
}
|
||||
|
||||
.uni-indexed-list__menu-text--active {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-indexed-list__alert-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-indexed-list__alert {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 80px;
|
||||
text-align: center;
|
||||
line-height: 80px;
|
||||
font-size: 35px;
|
||||
color: #fff;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
78
components/uni-link/uni-link.vue
Normal file
78
components/uni-link/uni-link.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<text class="uni-link" :class="{'uni-link--withline':showUnderLine===true||showUnderLine==='true'}" :style="{color,fontSize:fontSize+'px'}" @click="openURL">{{text}}</text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Link 外部网页超链接组件
|
||||
* @description uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=1182
|
||||
* @property {String} href 点击后打开的外部网页url
|
||||
* @property {String} text 显示的文字
|
||||
* @property {Boolean} showUnderLine 是否显示下划线
|
||||
* @property {String} copyTips 在小程序端复制链接时显示的提示语
|
||||
* @property {String} color 链接文字颜色
|
||||
* @property {String} fontSize 链接文字大小
|
||||
* @example * <uni-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn"></uni-link>
|
||||
*/
|
||||
export default {
|
||||
name: 'uniLink',
|
||||
props: {
|
||||
href: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showUnderLine: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
copyTips: {
|
||||
type: String,
|
||||
default: '已自动复制网址,请在手机浏览器里粘贴该网址'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#999999'
|
||||
},
|
||||
fontSize: {
|
||||
type: [Number, String],
|
||||
default: 14
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openURL() {
|
||||
// #ifdef APP-PLUS
|
||||
plus.runtime.openURL(this.href)
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
window.open(this.href)
|
||||
// #endif
|
||||
// #ifdef MP
|
||||
uni.setClipboardData({
|
||||
data: this.href
|
||||
});
|
||||
uni.showModal({
|
||||
content: this.copyTips,
|
||||
showCancel: false
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-link {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-link--withline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
106
components/uni-list-ad/uni-list-ad.vue
Normal file
106
components/uni-list-ad/uni-list-ad.vue
Normal file
@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<cell>
|
||||
<!-- #endif -->
|
||||
<view class="uni-list-ad">
|
||||
<view v-if="borderShow" :class="{'uni-list--border':border,'uni-list-item--first':isFirstChild}"></view>
|
||||
<ad style="width: 200px;height: 300px;border-width: 1px;border-color: red;border-style: solid;" adpid="1111111111" unit-id="" appid="" apid="" type="feed" @error="aderror" @close="closeAd"></ad>
|
||||
</view>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
</cell>
|
||||
<!-- #endif -->
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const dom = uni.requireNativePlugin('dom');
|
||||
// #endif
|
||||
export default {
|
||||
name: 'UniListAd',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
|
||||
}
|
||||
},
|
||||
// inject: ['list'],
|
||||
data() {
|
||||
return {
|
||||
isFirstChild: false,
|
||||
border: false,
|
||||
borderShow: true,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.list = this.getForm()
|
||||
if (this.list) {
|
||||
if (!this.list.firstChildAppend) {
|
||||
this.list.firstChildAppend = true
|
||||
this.isFirstChild = true
|
||||
}
|
||||
this.border = this.list.border
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniList') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
aderror(e) {
|
||||
console.log("aderror: " + JSON.stringify(e.detail));
|
||||
},
|
||||
closeAd(e) {
|
||||
this.borderShow = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-list-ad {
|
||||
position: relative;
|
||||
border: 1px red solid;
|
||||
}
|
||||
|
||||
.uni-list--border {
|
||||
position: relative;
|
||||
padding-bottom: 1px;
|
||||
/* #ifdef APP-PLUS */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-list--border:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
content: '';
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-list-item--first:after {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
58
components/uni-list-chat/uni-list-chat.scss
Normal file
58
components/uni-list-chat/uni-list-chat.scss
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 这里是 uni-list 组件内置的常用样式变量
|
||||
* 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码
|
||||
*
|
||||
*/
|
||||
|
||||
// 背景色
|
||||
$background-color : #fff;
|
||||
// 分割线颜色
|
||||
$divide-line-color : #e5e5e5;
|
||||
|
||||
// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像
|
||||
// nvue 页面不支持修改头像大小
|
||||
$avatar-width : 45px ;
|
||||
|
||||
// 头像边框
|
||||
$avatar-border-radius: 5px;
|
||||
$avatar-border-color: #eee;
|
||||
$avatar-border-width: 1px;
|
||||
|
||||
// 标题文字样式
|
||||
$title-size : 16px;
|
||||
$title-color : #3b4144;
|
||||
$title-weight : normal;
|
||||
|
||||
// 描述文字样式
|
||||
$note-size : 12px;
|
||||
$note-color : #999;
|
||||
$note-weight : normal;
|
||||
|
||||
// 右侧额外内容默认样式
|
||||
$right-text-size : 12px;
|
||||
$right-text-color : #999;
|
||||
$right-text-weight : normal;
|
||||
|
||||
// 角标样式
|
||||
// nvue 页面不支持修改圆点位置以及大小
|
||||
// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动
|
||||
$badge-left: 0px;
|
||||
$badge-top: 0px;
|
||||
|
||||
// 显示圆点时,圆点大小
|
||||
$dot-width: 10px;
|
||||
$dot-height: 10px;
|
||||
|
||||
// 显示角标时,角标大小和字体大小
|
||||
$badge-size : 18px;
|
||||
$badge-font : 12px;
|
||||
// 显示角标时,角标前景色
|
||||
$badge-color : #fff;
|
||||
// 显示角标时,角标背景色
|
||||
$badge-background-color : #ff5a5f;
|
||||
// 显示角标时,角标左右间距
|
||||
$badge-space : 6px;
|
||||
|
||||
// 状态样式
|
||||
// 选中颜色
|
||||
$hover : #f5f5f5;
|
||||
497
components/uni-list-chat/uni-list-chat.vue
Normal file
497
components/uni-list-chat/uni-list-chat.vue
Normal file
@ -0,0 +1,497 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<cell>
|
||||
<!-- #endif -->
|
||||
<view :hover-class="!clickable && !link ? '' : 'uni-list-chat--hover'" class="uni-list-chat" @click.stop="onClick">
|
||||
<view :class="{ 'uni-list--border': border, 'uni-list-chat--first': isFirstChild }"></view>
|
||||
<view class="uni-list-chat__container">
|
||||
<view class="uni-list-chat__header-warp">
|
||||
<view v-if="avatarCircle || avatarList.length === 0" class="uni-list-chat__header" :class="{ 'header--circle': avatarCircle }">
|
||||
<image class="uni-list-chat__header-image" :src="avatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
<!-- 头像组 -->
|
||||
<view v-else class="uni-list-chat__header">
|
||||
<view v-for="(item, index) in avatarList" :key="index" class="uni-list-chat__header-box" :class="computedAvatar" :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }">
|
||||
<image class="uni-list-chat__header-image" :style="{ width: imageWidth + 'px', height: imageWidth + 'px' }" :src="item.url" mode="aspectFill"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="badgeText && badgePositon === 'left'" class="uni-list-chat__badge uni-list-chat__badge-pos" :class="[isSingle]">
|
||||
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
|
||||
</view>
|
||||
<view class="uni-list-chat__content">
|
||||
<view class="uni-list-chat__content-main">
|
||||
<text class="uni-list-chat__content-title uni-ellipsis">{{ title }}</text>
|
||||
<text class="uni-list-chat__content-note uni-ellipsis">{{ note }}</text>
|
||||
</view>
|
||||
<view class="uni-list-chat__content-extra">
|
||||
<slot>
|
||||
<text class="uni-list-chat__content-extra-text">{{ time }}</text>
|
||||
<view v-if="badgeText && badgePositon === 'right'" class="uni-list-chat__badge" :class="[isSingle, badgePositon === 'right' ? 'uni-list-chat--right' : '']">
|
||||
<text class="uni-list-chat__badge-text">{{ badgeText === 'dot' ? '' : badgeText }}</text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
</cell>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 头像大小
|
||||
const avatarWidth = 45;
|
||||
|
||||
/**
|
||||
* ListChat 聊天列表
|
||||
* @description 聊天列表,用于创建聊天类列表
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
|
||||
* @property {String} title 标题
|
||||
* @property {String} note 描述
|
||||
* @property {Boolean} clickable = [true|false] 是否开启点击反馈,默认为false
|
||||
* @property {String} badgeText 数字角标内容
|
||||
* @property {String} badgePositon = [left|right] 角标位置,默认为 right
|
||||
* @property {String} link = [false|navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈,默认为false
|
||||
* @value false 不开启
|
||||
* @value navigateTo 同 uni.navigateTo()
|
||||
* @value redirectTo 同 uni.redirectTo()
|
||||
* @value reLaunch 同 uni.reLaunch()
|
||||
* @value switchTab 同 uni.switchTab()
|
||||
* @property {String | PageURIString} to 跳转目标页面
|
||||
* @property {String} time 右侧时间显示
|
||||
* @property {Boolean} avatarCircle = [true|false] 是否显示圆形头像,默认为false
|
||||
* @property {String} avatar 头像地址,avatarCircle 不填时生效
|
||||
* @property {Array} avatarList 头像组,格式为 [{url:''}]
|
||||
* @event {Function} click 点击 uniListChat 触发事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniListChat',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
badgeText: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
badgePositon: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
time: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
avatarCircle: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
avatarList: {
|
||||
type: Array,
|
||||
default () {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
},
|
||||
// inject: ['list'],
|
||||
computed: {
|
||||
isSingle() {
|
||||
if (this.badgeText === 'dot') {
|
||||
return 'uni-badge--dot';
|
||||
} else {
|
||||
const badgeText = this.badgeText.toString();
|
||||
if (badgeText.length > 1) {
|
||||
return 'uni-badge--complex';
|
||||
} else {
|
||||
return 'uni-badge--single';
|
||||
}
|
||||
}
|
||||
},
|
||||
computedAvatar() {
|
||||
if (this.avatarList.length > 4) {
|
||||
this.imageWidth = avatarWidth * 0.31;
|
||||
return 'avatarItem--3';
|
||||
} else if (this.avatarList.length > 1) {
|
||||
this.imageWidth = avatarWidth * 0.47;
|
||||
return 'avatarItem--2';
|
||||
} else {
|
||||
this.imageWidth = avatarWidth;
|
||||
return 'avatarItem--1';
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isFirstChild: false,
|
||||
border: true,
|
||||
// avatarList: 3,
|
||||
imageWidth: 50
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.list = this.getForm()
|
||||
if (this.list) {
|
||||
if (!this.list.firstChildAppend) {
|
||||
this.list.firstChildAppend = true;
|
||||
this.isFirstChild = true;
|
||||
}
|
||||
this.border = this.list.border;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniList') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
onClick() {
|
||||
if (this.to !== '') {
|
||||
this.openPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.clickable || this.link) {
|
||||
this.$emit('click', {
|
||||
data: {}
|
||||
});
|
||||
}
|
||||
},
|
||||
openPage() {
|
||||
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
|
||||
this.pageApi(this.link);
|
||||
} else {
|
||||
this.pageApi('navigateTo');
|
||||
}
|
||||
},
|
||||
pageApi(api) {
|
||||
uni[api]({
|
||||
url: this.to,
|
||||
success: res => {
|
||||
this.$emit('click', {
|
||||
data: res
|
||||
});
|
||||
},
|
||||
fail: err => {
|
||||
this.$emit('click', {
|
||||
data: err
|
||||
});
|
||||
console.error(err.errMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-list-chat {
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-list-chat--hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.uni-list--border {
|
||||
position: relative;
|
||||
margin-left: 15px;
|
||||
/* #ifdef APP-PLUS */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-list--border:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
content: '';
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-list-item--first:after {
|
||||
height: 0px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-list-chat--first {
|
||||
border-top-width: 0px;
|
||||
}
|
||||
|
||||
.uni-ellipsis {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-ellipsis-2 {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 2;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-list-chat__container {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
padding: 10px 15px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__header-warp {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-list-chat__header {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
align-content: center;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-wrap: wrap-reverse;
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
/* #endif */
|
||||
border-radius: 5px;
|
||||
border-color: #eee;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__header-box {
|
||||
/* #ifndef APP-PLUS */
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.uni-list-chat__header-image {
|
||||
margin: 1px;
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-list-chat__header-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatarItem--1 {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatarItem--2 {
|
||||
width: 47%;
|
||||
height: 47%;
|
||||
}
|
||||
|
||||
.avatarItem--3 {
|
||||
width: 32%;
|
||||
height: 32%;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.header--circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.uni-list-chat__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.uni-list-chat__content-main {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding-left: 10px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__content-title {
|
||||
font-size: 16px;
|
||||
color: #3b4144;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__content-note {
|
||||
margin-top: 3px;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__content-extra {
|
||||
/* #ifndef APP-NVUE */
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.uni-list-chat__content-extra-text {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-chat__badge-pos {
|
||||
position: absolute;
|
||||
/* #ifdef APP-NVUE */
|
||||
left: 55px;
|
||||
top: 3px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
left: calc(45px + 10px - 6px + 0px);
|
||||
top: calc(10px/ 2 + 1px + 0px);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-list-chat__badge {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 100px;
|
||||
background-color: #ff5a5f;
|
||||
}
|
||||
|
||||
.uni-list-chat__badge-text {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-badge--single {
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.uni-badge--complex {
|
||||
/* #ifdef APP-NVUE */
|
||||
left: 50px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
width: auto;
|
||||
/* #endif */
|
||||
height: 18px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.uni-badge--dot {
|
||||
/* #ifdef APP-NVUE */
|
||||
left: 60px;
|
||||
top: 6px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
left: calc(45px + 15px - 10px/ 2 + 1px + 0px);
|
||||
/* #endif */
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.uni-list-chat--right {
|
||||
/* #ifdef APP-NVUE */
|
||||
left: 0;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
434
components/uni-list-item/uni-list-item.vue
Normal file
434
components/uni-list-item/uni-list-item.vue
Normal file
@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<cell>
|
||||
<!-- #endif -->
|
||||
|
||||
<view :class="{ 'uni-list-item--disabled': disabled }" :hover-class="(!clickable && !link) || disabled || showSwitch ? '' : 'uni-list-item--hover'" class="uni-list-item" @click.stop="onClick">
|
||||
<view v-if="!isFirstChild" class="border--left" :class="{ 'uni-list--border': border }"></view>
|
||||
<view class="uni-list-item__container" :class="{ 'container--right': showArrow || link, 'flex--direction': direction === 'column' }">
|
||||
<slot name="header">
|
||||
<view class="uni-list-item__header">
|
||||
<view v-if="thumb" class="uni-list-item__icon">
|
||||
<image :src="thumb" class="uni-list-item__icon-img" :class="['uni-list--' + thumbSize]" />
|
||||
</view>
|
||||
<view v-else-if="showExtraIcon" class="uni-list-item__icon">
|
||||
<uni-icons :color="extraIcon.color" :size="extraIcon.size" :type="extraIcon.type" />
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<slot name="body">
|
||||
<view class="uni-list-item__content" :class="{ 'uni-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }">
|
||||
<text v-if="title" class="uni-list-item__content-title" :class="[ellipsis !== 0 && ellipsis <= 2 ? 'uni-ellipsis-' + ellipsis : '']">{{ title }}</text>
|
||||
<text v-if="note" class="uni-list-item__content-note">{{ note }}</text>
|
||||
</view>
|
||||
</slot>
|
||||
<slot name="footer">
|
||||
<view v-if="rightText || showBadge || showSwitch" class="uni-list-item__extra" :class="{ 'flex--justify': direction === 'column' }">
|
||||
<text v-if="rightText" class="uni-list-item__extra-text">{{ rightText }}</text>
|
||||
<uni-badge v-if="showBadge" :type="badgeType" :text="badgeText" />
|
||||
<switch v-if="showSwitch" :disabled="disabled" :checked="switchChecked" @change="onSwitchChange" />
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<uni-icons v-if="showArrow || link" :size="16" class="uni-icon-wrapper" color="#bbb" type="arrowright" />
|
||||
</view>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
</cell>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue';
|
||||
import uniBadge from '../uni-badge/uni-badge.vue';
|
||||
|
||||
/**
|
||||
* ListItem 列表子组件
|
||||
* @description 列表子组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
|
||||
* @property {String} title 标题
|
||||
* @property {String} note 描述
|
||||
* @property {String} thumb 左侧缩略图,若thumb有值,则不会显示扩展图标
|
||||
* @property {String} thumbSize = [lg|base|sm] 略缩图大小
|
||||
* @value lg 大图
|
||||
* @value base 一般
|
||||
* @value sm 小图
|
||||
* @property {String} badgeText 数字角标内容
|
||||
* @property {String} badgeType 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21)
|
||||
* @property {String} rightText 右侧文字内容
|
||||
* @property {Boolean} disabled = [true|false] 是否禁用
|
||||
* @property {Boolean} clickable = [true|false] 是否开启点击反馈
|
||||
* @property {String} link = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈
|
||||
* @value navigateTo 同 uni.navigateTo()
|
||||
* @value redirectTo 同 uni.redirectTo()
|
||||
* @value reLaunch 同 uni.reLaunch()
|
||||
* @value switchTab 同 uni.switchTab()
|
||||
* @property {String | PageURIString} to 跳转目标页面
|
||||
* @property {Boolean} showBadge = [true|false] 是否显示数字角标
|
||||
* @property {Boolean} showSwitch = [true|false] 是否显示Switch
|
||||
* @property {Boolean} switchChecked = [true|false] Switch是否被选中
|
||||
* @property {Boolean} showExtraIcon = [true|false] 左侧是否显示扩展图标
|
||||
* @property {Object} extraIcon 扩展图标参数,格式为 {color: '#4cd964',size: '22',type: 'spinner'}
|
||||
* @property {String} direction = [row|column] 排版方向
|
||||
* @value row 水平排列
|
||||
* @value column 垂直排列
|
||||
* @event {Function} click 点击 uniListItem 触发事件
|
||||
* @event {Function} switchChange 点击切换 Switch 时触发
|
||||
*/
|
||||
export default {
|
||||
name: 'UniListItem',
|
||||
components: {
|
||||
uniIcons,
|
||||
uniBadge
|
||||
},
|
||||
props: {
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'row'
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
note: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
ellipsis: {
|
||||
type: [Number],
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
clickable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
showArrow: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
link: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
to: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
showBadge: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showSwitch: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
switchChecked: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
badgeText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
badgeType: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
rightText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumb: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
thumbSize: {
|
||||
type: String,
|
||||
default: 'base'
|
||||
},
|
||||
showExtraIcon: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
extraIcon: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
type: 'contact',
|
||||
color: '#000000',
|
||||
size: 20
|
||||
};
|
||||
}
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
// inject: ['list'],
|
||||
data() {
|
||||
return {
|
||||
isFirstChild: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.list = this.getForm()
|
||||
// 判断是否存在 uni-list 组件
|
||||
if (this.list) {
|
||||
if (!this.list.firstChildAppend) {
|
||||
this.list.firstChildAppend = true;
|
||||
this.isFirstChild = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取父元素实例
|
||||
*/
|
||||
getForm(name = 'uniList') {
|
||||
let parent = this.$parent;
|
||||
let parentName = parent.$options.name;
|
||||
while (parentName !== name) {
|
||||
parent = parent.$parent;
|
||||
if (!parent) return false
|
||||
parentName = parent.$options.name;
|
||||
}
|
||||
return parent;
|
||||
},
|
||||
onClick() {
|
||||
if (this.to !== '') {
|
||||
this.openPage();
|
||||
return;
|
||||
}
|
||||
if (this.clickable || this.link) {
|
||||
this.$emit('click', {
|
||||
data: {}
|
||||
});
|
||||
}
|
||||
},
|
||||
onSwitchChange(e) {
|
||||
this.$emit('switchChange', e.detail);
|
||||
},
|
||||
openPage() {
|
||||
if (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {
|
||||
this.pageApi(this.link);
|
||||
} else {
|
||||
this.pageApi('navigateTo');
|
||||
}
|
||||
},
|
||||
pageApi(api) {
|
||||
uni[api]({
|
||||
url: this.to,
|
||||
success: res => {
|
||||
this.$emit('click', {
|
||||
data: res
|
||||
});
|
||||
},
|
||||
fail: err => {
|
||||
this.$emit('click', {
|
||||
data: err
|
||||
});
|
||||
console.error(err.errMsg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-list-item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
font-size: 16px;
|
||||
position: relative;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
flex-direction: row;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-list-item--disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.uni-list-item--hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.uni-list-item__container {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 12px 15px;
|
||||
padding-left: 15px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container--right {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.uni-list--border {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-list--border:after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
content: '';
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-list-item__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
padding-right: 8px;
|
||||
flex: 1;
|
||||
color: #3b4144;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-item__content--center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-list-item__content-title {
|
||||
font-size: 14px;
|
||||
color: #3b4144;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-item__content-note {
|
||||
margin-top: 6rpx;
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-list-item__extra {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-list-item__header {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-list-item__icon {
|
||||
margin-right: 18rpx;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-list-item__icon-img {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
marin-right: 10px;
|
||||
}
|
||||
|
||||
.uni-icon-wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.flex--direction {
|
||||
flex-direction: column;
|
||||
/* #ifndef APP-NVUE */
|
||||
align-items: initial;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.flex--justify {
|
||||
/* #ifndef APP-NVUE */
|
||||
justify-content: initial;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-list--lg {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.uni-list--base {
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
}
|
||||
|
||||
.uni-list--sm {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.uni-list-item__extra-text {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.uni-ellipsis-1 {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-ellipsis-2 {
|
||||
/* #ifndef APP-NVUE */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 2;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
107
components/uni-list/uni-list.vue
Normal file
107
components/uni-list/uni-list.vue
Normal file
@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="uni-list uni-border-top-bottom">
|
||||
<view v-if="border" class="uni-list--border-top"></view>
|
||||
<slot />
|
||||
<view v-if="border" class="uni-list--border-bottom"></view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<list class="uni-list" :class="{ 'uni-list--border': border }" :enableBackToTop="enableBackToTop" loadmoreoffset="15">
|
||||
<slot />
|
||||
</list>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* List 列表
|
||||
* @description 列表组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=24
|
||||
* @property {String} border = [true|false] 标题
|
||||
*/
|
||||
export default {
|
||||
name: 'uniList',
|
||||
'mp-weixin': {
|
||||
options: {
|
||||
multipleSlots: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
enableBackToTop: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
scrollY: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
// provide() {
|
||||
// return {
|
||||
// list: this
|
||||
// };
|
||||
// },
|
||||
created() {
|
||||
this.firstChildAppend = false;
|
||||
},
|
||||
methods: {
|
||||
loadMore(e) {
|
||||
this.$emit('scrolltolower');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.uni-list--border {
|
||||
position: relative;
|
||||
/* #ifdef APP-NVUE */
|
||||
border-top-color: #e5e5e5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 0.5px;
|
||||
border-bottom-color: #e5e5e5;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 0.5px;
|
||||
/* #endif */
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-list--border-top {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
background-color: #e5e5e5;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.uni-list--border-bottom {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 1px;
|
||||
-webkit-transform: scaleY(0.5);
|
||||
transform: scaleY(0.5);
|
||||
background-color: #e5e5e5;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
65
components/uni-list/uni-refresh.vue
Normal file
65
components/uni-list/uni-refresh.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown">
|
||||
<slot />
|
||||
</refresh>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view ref="uni-refresh" class="uni-refresh" v-show="isShow">
|
||||
<slot />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UniRefresh',
|
||||
props: {
|
||||
display: {
|
||||
type: [String],
|
||||
default: "hide"
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pulling: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShow() {
|
||||
if (this.display === "show" || this.pulling === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
onchange(value) {
|
||||
this.pulling = value;
|
||||
},
|
||||
onrefresh(e) {
|
||||
this.$emit("refresh", e);
|
||||
},
|
||||
onpullingdown(e) {
|
||||
// #ifdef APP-NVUE
|
||||
this.$emit("pullingdown", e);
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
var detail = {
|
||||
viewHeight: 90,
|
||||
pullingDistance: e.height
|
||||
}
|
||||
this.$emit("pullingdown", detail);
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-refresh {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
87
components/uni-list/uni-refresh.wxs
Normal file
87
components/uni-list/uni-refresh.wxs
Normal file
@ -0,0 +1,87 @@
|
||||
var pullDown = {
|
||||
threshold: 95,
|
||||
maxHeight: 200,
|
||||
callRefresh: 'onrefresh',
|
||||
callPullingDown: 'onpullingdown',
|
||||
refreshSelector: '.uni-refresh'
|
||||
};
|
||||
|
||||
function ready(newValue, oldValue, ownerInstance, instance) {
|
||||
var state = instance.getState()
|
||||
state.canPullDown = newValue;
|
||||
// console.log(newValue);
|
||||
}
|
||||
|
||||
function touchStart(e, instance) {
|
||||
var state = instance.getState();
|
||||
state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
|
||||
state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
|
||||
if (!state.canPullDown) {
|
||||
return
|
||||
}
|
||||
|
||||
// console.log("touchStart");
|
||||
|
||||
state.height = 0;
|
||||
state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
|
||||
state.refreshInstance.setStyle({
|
||||
'height': 0
|
||||
});
|
||||
state.refreshInstance.callMethod("onchange", true);
|
||||
}
|
||||
|
||||
function touchMove(e, ownerInstance) {
|
||||
var instance = e.instance;
|
||||
var state = instance.getState();
|
||||
if (!state.canPullDown) {
|
||||
return
|
||||
}
|
||||
|
||||
var oldHeight = state.height;
|
||||
var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
|
||||
var height = endY - state.touchStartY;
|
||||
if (height > pullDown.maxHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
var refreshInstance = state.refreshInstance;
|
||||
refreshInstance.setStyle({
|
||||
'height': height + 'px'
|
||||
});
|
||||
|
||||
height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
|
||||
state.height = height;
|
||||
refreshInstance.callMethod(pullDown.callPullingDown, {
|
||||
height: height
|
||||
});
|
||||
}
|
||||
|
||||
function touchEnd(e, ownerInstance) {
|
||||
var state = e.instance.getState();
|
||||
if (!state.canPullDown) {
|
||||
return
|
||||
}
|
||||
|
||||
state.refreshInstance.callMethod("onchange", false);
|
||||
|
||||
var refreshInstance = state.refreshInstance;
|
||||
if (state.height > pullDown.threshold) {
|
||||
refreshInstance.callMethod(pullDown.callRefresh);
|
||||
return;
|
||||
}
|
||||
|
||||
refreshInstance.setStyle({
|
||||
'height': 0
|
||||
});
|
||||
}
|
||||
|
||||
function propObserver(newValue, oldValue, instance) {
|
||||
pullDown = newValue;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchmove: touchMove,
|
||||
touchstart: touchStart,
|
||||
touchend: touchEnd,
|
||||
propObserver: propObserver
|
||||
}
|
||||
362
components/uni-load-more/uni-load-more.vue
Normal file
362
components/uni-load-more/uni-load-more.vue
Normal file
File diff suppressed because one or more lines are too long
237
components/uni-nav-bar/uni-nav-bar.vue
Normal file
237
components/uni-nav-bar/uni-nav-bar.vue
Normal file
@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<view class="uni-navbar">
|
||||
<view :class="{ 'uni-navbar--fixed': fixed, 'uni-navbar--shadow': shadow, 'uni-navbar--border': border }" :style="{ 'background-color': backgroundColor }" class="uni-navbar__content">
|
||||
<uni-status-bar v-if="statusBar" />
|
||||
<view :style="{ color: color,backgroundColor: backgroundColor }" class="uni-navbar__header uni-navbar__content_view">
|
||||
<view @tap="onClickLeft" class="uni-navbar__header-btns uni-navbar__header-btns-left uni-navbar__content_view">
|
||||
<view class="uni-navbar__content_view" v-if="leftIcon.length">
|
||||
<uni-icons :color="color" :type="leftIcon" size="24" />
|
||||
</view>
|
||||
<view :class="{ 'uni-navbar-btn-icon-left': !leftIcon.length }" class="uni-navbar-btn-text uni-navbar__content_view" v-if="leftText.length">
|
||||
<text :style="{ color: color, fontSize: '14px' }">{{ leftText }}</text>
|
||||
</view>
|
||||
<slot name="left" />
|
||||
</view>
|
||||
<view class="uni-navbar__header-container uni-navbar__content_view" @tap="onClickTitle">
|
||||
<view class="uni-navbar__header-container-inner uni-navbar__content_view" v-if="title.length">
|
||||
<text class="uni-nav-bar-text" :style="{color: color }">{{ title }}</text>
|
||||
</view>
|
||||
<!-- 标题插槽 -->
|
||||
<slot />
|
||||
</view>
|
||||
<view :class="title.length ? 'uni-navbar__header-btns-right' : ''" @tap="onClickRight" class="uni-navbar__header-btns uni-navbar__content_view">
|
||||
<view class="uni-navbar__content_view" v-if="rightIcon.length">
|
||||
<uni-icons :color="color" :type="rightIcon" size="24" />
|
||||
</view>
|
||||
<!-- 优先显示图标 -->
|
||||
<view class="uni-navbar-btn-text uni-navbar__content_view" v-if="rightText.length && !rightIcon.length">
|
||||
<text class="uni-nav-bar-right-text">{{ rightText }}</text>
|
||||
</view>
|
||||
<slot name="right" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-navbar__placeholder" v-if="fixed">
|
||||
<uni-status-bar v-if="statusBar" />
|
||||
<view class="uni-navbar__placeholder-view" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniStatusBar from "../uni-status-bar/uni-status-bar.vue";
|
||||
import uniIcons from "../uni-icons/uni-icons.vue";
|
||||
|
||||
/**
|
||||
* NavBar 自定义导航栏
|
||||
* @description 导航栏组件,主要用于头部导航
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=52
|
||||
* @property {String} title 标题文字
|
||||
* @property {String} leftText 左侧按钮文本
|
||||
* @property {String} rightText 右侧按钮文本
|
||||
* @property {String} leftIcon 左侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性)
|
||||
* @property {String} rightIcon 右侧按钮图标(图标类型参考 [Icon 图标](http://ext.dcloud.net.cn/plugin?id=28) type 属性)
|
||||
* @property {String} color 图标和文字颜色
|
||||
* @property {String} backgroundColor 导航栏背景颜色
|
||||
* @property {Boolean} fixed = [true|false] 是否固定顶部
|
||||
* @property {Boolean} statusBar = [true|false] 是否包含状态栏
|
||||
* @property {Boolean} shadow = [true|false] 导航栏下是否有阴影
|
||||
* @event {Function} clickLeft 左侧按钮点击时触发
|
||||
* @event {Function} clickRight 右侧按钮点击时触发
|
||||
* @event {Function} clickTitle 中间标题点击时触发
|
||||
*/
|
||||
export default {
|
||||
name: "UniNavBar",
|
||||
components: {
|
||||
uniStatusBar,
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
leftText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
rightText: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
rightIcon: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
fixed: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: "#000000"
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: "#FFFFFF"
|
||||
},
|
||||
statusBar: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
shadow: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
border: {
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (uni.report && this.title !== '') {
|
||||
uni.report('title', this.title)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClickLeft() {
|
||||
this.$emit("clickLeft");
|
||||
},
|
||||
onClickRight() {
|
||||
this.$emit("clickRight");
|
||||
},
|
||||
onClickTitle() {
|
||||
this.$emit("clickTitle");
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-nav-bar-text {
|
||||
/* #ifdef APP-PLUS */
|
||||
font-size: 34rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-PLUS */
|
||||
font-size: 16px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-nav-bar-right-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-navbar__content {
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-navbar__content_view {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-navbar__header {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uni-navbar__header-btns {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-wrap: nowrap;
|
||||
width: 120rpx;
|
||||
padding: 0 6px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-navbar__header-btns-left {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
width: 150rpx;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.uni-navbar__header-btns-right {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
width: 150rpx;
|
||||
padding-right: 30rpx;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-navbar__header-container {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.uni-navbar__header-container-inner {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-navbar__placeholder-view {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.uni-navbar--fixed {
|
||||
position: fixed;
|
||||
z-index: 998;
|
||||
}
|
||||
|
||||
.uni-navbar--shadow {
|
||||
/* #ifndef APP-NVUE */
|
||||
box-shadow: 0 1px 6px #ccc;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-navbar--border {
|
||||
border-bottom-width: 1rpx;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: #e5e5e5;
|
||||
}
|
||||
</style>
|
||||
397
components/uni-notice-bar/uni-notice-bar.vue
Normal file
397
components/uni-notice-bar/uni-notice-bar.vue
Normal file
@ -0,0 +1,397 @@
|
||||
<template>
|
||||
<view v-if="show" class="uni-noticebar" :style="{ backgroundColor: backgroundColor }" @click="onClick">
|
||||
<!-- #ifdef MP-ALIPAY -->
|
||||
<view v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close uni-cursor-point" @click="close">
|
||||
<uni-icons type="closeempty" :color="color" size="12" />
|
||||
</view>
|
||||
<view v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon">
|
||||
<uni-icons type="sound" :color="color" size="14" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-ALIPAY -->
|
||||
<uni-icons v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close uni-cursor-point" type="closeempty" :color="color" size="12" @click="close" />
|
||||
<uni-icons v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon" type="sound" :color="color" size="14" />
|
||||
<!-- #endif -->
|
||||
<view ref="textBox" class="uni-noticebar__content-wrapper" :class="{'uni-noticebar__content-wrapper--scrollable':scrollable, 'uni-noticebar__content-wrapper--single':!scrollable && (single || moreText)}">
|
||||
<view :id="elIdBox" class="uni-noticebar__content" :class="{'uni-noticebar__content--scrollable':scrollable, 'uni-noticebar__content--single':!scrollable && (single || moreText)}">
|
||||
<text :id="elId" ref="animationEle" class="uni-noticebar__content-text" :class="{'uni-noticebar__content-text--scrollable':scrollable,'uni-noticebar__content-text--single':!scrollable && (single || moreText)}" :style="{color:color, width:wrapWidth+'px', 'animationDuration': animationDuration, '-webkit-animationDuration': animationDuration ,animationPlayState: webviewHide?'paused':animationPlayState,'-webkit-animationPlayState':webviewHide?'paused':animationPlayState, animationDelay: animationDelay, '-webkit-animationDelay':animationDelay}">{{text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="showGetMore === true || showGetMore === 'true'" class="uni-noticebar__more uni-cursor-point" @click="clickMore">
|
||||
<text v-if="moreText" :style="{ color: moreColor }" class="uni-noticebar__more-text">{{ moreText }}</text>
|
||||
<uni-icons type="arrowright" :color="moreColor" size="14" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom');
|
||||
const animation = weex.requireModule('animation');
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* NoticeBar 自定义导航栏
|
||||
* @description 通告栏组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=30
|
||||
* @property {Number} speed 文字滚动的速度,默认100px/秒
|
||||
* @property {String} text 显示文字
|
||||
* @property {String} backgroundColor 背景颜色
|
||||
* @property {String} color 文字颜色
|
||||
* @property {String} moreColor 查看更多文字的颜色
|
||||
* @property {String} moreText 设置“查看更多”的文本
|
||||
* @property {Boolean} single = [true|false] 是否单行
|
||||
* @property {Boolean} scrollable = [true|false] 是否滚动,为true时,NoticeBar为单行
|
||||
* @property {Boolean} showIcon = [true|false] 是否显示左侧喇叭图标
|
||||
* @property {Boolean} showClose = [true|false] 是否显示左侧关闭按钮
|
||||
* @property {Boolean} showGetMore = [true|false] 是否显示右侧查看更多图标,为true时,NoticeBar为单行
|
||||
* @event {Function} click 点击 NoticeBar 触发事件
|
||||
* @event {Function} close 关闭 NoticeBar 触发事件
|
||||
* @event {Function} getmore 点击”查看更多“时触发事件
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniNoticeBar',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
text: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
moreText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fffbe8'
|
||||
},
|
||||
speed: {
|
||||
// 默认1s滚动100px
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#de8c17'
|
||||
},
|
||||
moreColor: {
|
||||
type: String,
|
||||
default: '#999999'
|
||||
},
|
||||
single: {
|
||||
// 是否单行
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
scrollable: {
|
||||
// 是否滚动,添加后控制单行效果取消
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showIcon: {
|
||||
// 是否显示左侧icon
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showGetMore: {
|
||||
// 是否显示右侧查看更多
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
showClose: {
|
||||
// 是否显示左侧关闭按钮
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
const elIdBox = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
|
||||
return {
|
||||
textWidth: 0,
|
||||
boxWidth: 0,
|
||||
wrapWidth: '',
|
||||
webviewHide: false,
|
||||
// #ifdef APP-NVUE
|
||||
stopAnimation: false,
|
||||
// #endif
|
||||
elId: elId,
|
||||
elIdBox: elIdBox,
|
||||
show: true,
|
||||
animationDuration: 'none',
|
||||
animationPlayState: 'paused',
|
||||
animationDelay: '0s'
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef APP-PLUS
|
||||
var pages = getCurrentPages();
|
||||
var page = pages[pages.length - 1];
|
||||
var currentWebview = page.$getAppWebview();
|
||||
currentWebview.addEventListener('hide', () => {
|
||||
this.webviewHide = true
|
||||
})
|
||||
currentWebview.addEventListener('show', () => {
|
||||
this.webviewHide = false
|
||||
})
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
this.initSize()
|
||||
})
|
||||
},
|
||||
// #ifdef APP-NVUE
|
||||
beforeDestroy() {
|
||||
this.stopAnimation = true
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
initSize() {
|
||||
if (this.scrollable) {
|
||||
// #ifndef APP-NVUE
|
||||
let query = [],
|
||||
boxWidth = 0,
|
||||
textWidth = 0;
|
||||
let textQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select(`#${this.elId}`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.textWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
let boxQuery = new Promise((resolve, reject) => {
|
||||
uni.createSelectorQuery()
|
||||
// #ifndef MP-ALIPAY
|
||||
.in(this)
|
||||
// #endif
|
||||
.select(`#${this.elIdBox}`)
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
this.boxWidth = ret[0].width
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
query.push(textQuery)
|
||||
query.push(boxQuery)
|
||||
Promise.all(query).then(() => {
|
||||
this.animationDuration = `${this.textWidth / this.speed}s`
|
||||
this.animationDelay = `-${this.boxWidth / this.speed}s`
|
||||
setTimeout(() => {
|
||||
this.animationPlayState = 'running'
|
||||
}, 1000)
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['animationEle'], (res) => {
|
||||
let winWidth = uni.getSystemInfoSync().windowWidth
|
||||
this.textWidth = res.size.width
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${winWidth}px)`
|
||||
},
|
||||
duration: 0,
|
||||
timingFunction: 'linear',
|
||||
delay: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${this.textWidth}px)`
|
||||
},
|
||||
timingFunction: 'linear',
|
||||
duration: (this.textWidth - winWidth) / this.speed * 1000,
|
||||
delay: 1000
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
this.loopAnimation()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
if (!this.scrollable && (this.single || this.moreText)) {
|
||||
dom.getComponentRect(this.$refs['textBox'], (res) => {
|
||||
this.wrapWidth = res.size.width
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
loopAnimation() {
|
||||
// #ifdef APP-NVUE
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(0px)`
|
||||
},
|
||||
duration: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
animation.transition(this.$refs['animationEle'], {
|
||||
styles: {
|
||||
transform: `translateX(-${this.textWidth}px)`
|
||||
},
|
||||
duration: this.textWidth / this.speed * 1000,
|
||||
timingFunction: 'linear',
|
||||
delay: 0
|
||||
}, () => {
|
||||
if (!this.stopAnimation) {
|
||||
this.loopAnimation()
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
clickMore() {
|
||||
this.$emit('getmore')
|
||||
},
|
||||
close() {
|
||||
this.show = false;
|
||||
this.$emit('close')
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-noticebar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar-close {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper--single {
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: 18px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-wrapper--single,
|
||||
.uni-noticebar__content-wrapper--scrollable {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-noticebar__content-wrapper--scrollable {
|
||||
position: relative;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
.uni-noticebar__content--scrollable {
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 0;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
flex: 1;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content--single {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex: none;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
/* #ifndef APP-NVUE */
|
||||
word-break: break-all;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text--single {
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.uni-noticebar__content-text--scrollable {
|
||||
/* #ifdef APP-NVUE */
|
||||
lines: 1;
|
||||
padding-left: 750rpx;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
padding-left: 100%;
|
||||
animation: notice 10s 0s linear infinite both;
|
||||
animation-play-state: paused;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-noticebar__more {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.uni-noticebar__more-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes notice {
|
||||
100% {
|
||||
transform: translate3d(-100%, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
206
components/uni-number-box/uni-number-box.vue
Normal file
206
components/uni-number-box/uni-number-box.vue
Normal file
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<view class="uni-numbox">
|
||||
<view @click="_calcValue('minus')" class="uni-numbox__minus uni-cursor-point">
|
||||
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }">-</text>
|
||||
</view>
|
||||
<input :disabled="disabled" @focus="_onFocus" @blur="_onBlur" class="uni-numbox__value" type="number" v-model="inputValue" />
|
||||
<view @click="_calcValue('plus')" class="uni-numbox__plus uni-cursor-point">
|
||||
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* NumberBox 数字输入框
|
||||
* @description 带加减按钮的数字输入框
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=31
|
||||
* @property {Number} value 输入框当前值
|
||||
* @property {Number} min 最小值
|
||||
* @property {Number} max 最大值
|
||||
* @property {Number} step 每次点击改变的间隔大小
|
||||
* @property {Boolean} disabled = [true|false] 是否为禁用状态
|
||||
* @event {Function} change 输入框值改变时触发的事件,参数为输入框当前的 value
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "UniNumberBox",
|
||||
props: {
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
step: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputValue: 0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.inputValue = +val;
|
||||
},
|
||||
inputValue(newVal, oldVal) {
|
||||
if (+newVal !== +oldVal) {
|
||||
this.$emit("change", newVal);
|
||||
this.$emit("input", newVal);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.inputValue = +this.value;
|
||||
},
|
||||
methods: {
|
||||
_calcValue(type) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
const scale = this._getDecimalScale();
|
||||
let value = this.inputValue * scale;
|
||||
let step = this.step * scale;
|
||||
if (type === "minus") {
|
||||
value -= step;
|
||||
if (value < (this.min * scale)) {
|
||||
return;
|
||||
}
|
||||
if (value > (this.max * scale)) {
|
||||
value = this.max * scale
|
||||
}
|
||||
} else if (type === "plus") {
|
||||
value += step;
|
||||
if (value > (this.max * scale)) {
|
||||
return;
|
||||
}
|
||||
if (value < (this.min * scale)) {
|
||||
value = this.min * scale
|
||||
}
|
||||
}
|
||||
|
||||
this.inputValue = String(value / scale);
|
||||
},
|
||||
_getDecimalScale() {
|
||||
let scale = 1;
|
||||
// 浮点型
|
||||
if (~~this.step !== this.step) {
|
||||
scale = Math.pow(10, (this.step + "").split(".")[1].length);
|
||||
}
|
||||
return scale;
|
||||
},
|
||||
_onBlur(event) {
|
||||
this.$emit('blur', event)
|
||||
let value = event.detail.value;
|
||||
if (!value) {
|
||||
// this.inputValue = 0;
|
||||
return;
|
||||
}
|
||||
value = +value;
|
||||
if (value > this.max) {
|
||||
value = this.max;
|
||||
} else if (value < this.min) {
|
||||
value = this.min;
|
||||
}
|
||||
this.inputValue = value;
|
||||
},
|
||||
_onFocus(event) {
|
||||
this.$emit('focus', event)
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
/* #ifdef APP-NVUE */
|
||||
/* #endif */
|
||||
.uni-numbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.uni-cursor-point {
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-numbox__value {
|
||||
background-color: #ffffff;
|
||||
width: 40px;
|
||||
height: 35px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.uni-numbox__minus {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
background-color: #f8f8f8;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-right-width: 0;
|
||||
}
|
||||
|
||||
.uni-numbox__plus {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
border-width: 1rpx;
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
background-color: #f8f8f8;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.uni-numbox--text {
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-numbox--disabled {
|
||||
color: #c0c0c0;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
206
components/uni-pagination/uni-pagination.vue
Normal file
206
components/uni-pagination/uni-pagination.vue
Normal file
@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<view class="uni-pagination">
|
||||
<view class="uni-pagination__btn" :class="currentIndex === 1 ? 'uni-pagination--disabled' : 'uni-pagination--enabled'" :hover-class="currentIndex === 1 ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70" @click="clickLeft">
|
||||
<template v-if="showIcon===true || showIcon === 'true'">
|
||||
<uni-icons color="#000" size="20" type="arrowleft" />
|
||||
</template>
|
||||
<template v-else><text class="uni-pagination__child-btn">{{ prevText }}</text></template>
|
||||
</view>
|
||||
<view class="uni-pagination__num">
|
||||
<view class="uni-pagination__num-current">
|
||||
<text class="uni-pagination__num-current-text" style="color:#007aff">{{ currentIndex }}</text><text class="uni-pagination__num-current-text">/{{ maxPage || 0 }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-pagination__btn" :class="currentIndex === maxPage ? 'uni-pagination--disabled' : 'uni-pagination--enabled'" :hover-class="currentIndex === maxPage ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70" @click="clickRight">
|
||||
<template v-if="showIcon===true || showIcon === 'true'">
|
||||
<uni-icons color="#000" size="20" type="arrowright" />
|
||||
</template>
|
||||
<template v-else><text class="uni-pagination__child-btn">{{ nextText }}</text></template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from '../uni-icons/uni-icons.vue'
|
||||
|
||||
/**
|
||||
* Pagination 分页器
|
||||
* @description 分页器组件,用于展示页码、请求数据等
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=32
|
||||
* @property {String} prevText 左侧按钮文字
|
||||
* @property {String} nextText 右侧按钮文字
|
||||
* @property {Number} current 当前页
|
||||
* @property {Number} total 数据总量
|
||||
* @property {Number} pageSize 每页数据量
|
||||
* @property {Number} showIcon = [true|false] 是否以 icon 形式展示按钮
|
||||
* @event {Function} change 点击页码按钮时触发 ,e={type,current} current为当前页,type值为:next/prev,表示点击的是上一页还是下一个
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniPagination',
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
prevText: {
|
||||
type: String,
|
||||
default: '上一页'
|
||||
},
|
||||
nextText: {
|
||||
type: String,
|
||||
default: '下一页'
|
||||
},
|
||||
current: {
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
total: { // 数据总量
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
pageSize: { // 每页数据量
|
||||
type: [Number, String],
|
||||
default: 10
|
||||
},
|
||||
showIcon: { // 是否以 icon 形式展示按钮
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
maxPage() {
|
||||
let maxPage = 1
|
||||
let total = Number(this.total)
|
||||
let pageSize = Number(this.pageSize)
|
||||
if (total && pageSize) {
|
||||
maxPage = Math.ceil(total / pageSize)
|
||||
}
|
||||
return maxPage
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current(val) {
|
||||
this.currentIndex = +val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.currentIndex = +this.current
|
||||
},
|
||||
methods: {
|
||||
clickLeft() {
|
||||
if (Number(this.currentIndex) === 1) {
|
||||
return
|
||||
}
|
||||
this.currentIndex -= 1
|
||||
this.change('prev')
|
||||
},
|
||||
clickRight() {
|
||||
if (Number(this.currentIndex) === this.maxPage) {
|
||||
return
|
||||
}
|
||||
this.currentIndex += 1
|
||||
this.change('next')
|
||||
},
|
||||
change(e) {
|
||||
this.$emit('change', {
|
||||
type: e,
|
||||
current: this.currentIndex
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-pagination {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-pagination__btn {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
background-color: #f8f8f8;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-pagination__child-btn {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.uni-pagination__num {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-pagination__num-current {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-pagination__num-current-text {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.uni-pagination--enabled {
|
||||
color: #333333;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-pagination--disabled {
|
||||
opacity: 0.3;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-pagination--hover {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
</style>
|
||||
271
components/uni-popup-dialog/uni-popup-dialog.vue
Normal file
271
components/uni-popup-dialog/uni-popup-dialog.vue
Normal file
@ -0,0 +1,271 @@
|
||||
<template>
|
||||
<view class="uni-popup-dialog">
|
||||
<view class="uni-dialog-title">
|
||||
<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text>
|
||||
</view>
|
||||
<view class="uni-dialog-content">
|
||||
<text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text>
|
||||
<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus">
|
||||
</view>
|
||||
<view class="uni-dialog-button-group">
|
||||
<view class="uni-dialog-button" @click="close">
|
||||
<text class="uni-dialog-button-text">取消</text>
|
||||
</view>
|
||||
<view class="uni-dialog-button uni-border-left" @click="onOk">
|
||||
<text class="uni-dialog-button-text uni-button-color">确定</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="popup.isDesktop" class="uni-popup-dialog__close" @click="close">
|
||||
<span class="uni-popup-dialog__close-icon "></span>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* PopUp 弹出层-对话框样式
|
||||
* @description 弹出层-对话框样式
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} value input 模式下的默认值
|
||||
* @property {String} placeholder input 模式下输入提示
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} mode = [base|input] 模式、
|
||||
* @value base 基础对话框
|
||||
* @value input 可输入对话框
|
||||
* @property {String} content 对话框内容
|
||||
* @property {Boolean} beforeClose 是否拦截取消事件
|
||||
* @event {Function} confirm 点击确认按钮触发
|
||||
* @event {Function} close 点击取消按钮触发
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "uniPopupDialog",
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
placeholder: {
|
||||
type: [String, Number],
|
||||
default: '请输入内容'
|
||||
},
|
||||
/**
|
||||
* 对话框主题 success/warning/info/error 默认 success
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'error'
|
||||
},
|
||||
/**
|
||||
* 对话框模式 base/input
|
||||
*/
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'base'
|
||||
},
|
||||
/**
|
||||
* 对话框标题
|
||||
*/
|
||||
title: {
|
||||
type: String,
|
||||
default: '提示'
|
||||
},
|
||||
/**
|
||||
* 对话框内容
|
||||
*/
|
||||
content: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done()
|
||||
*/
|
||||
beforeClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogType: 'error',
|
||||
focus: false,
|
||||
val: ""
|
||||
}
|
||||
},
|
||||
inject: ['popup'],
|
||||
watch: {
|
||||
type(val) {
|
||||
this.dialogType = val
|
||||
},
|
||||
mode(val) {
|
||||
if (val === 'input') {
|
||||
this.dialogType = 'info'
|
||||
}
|
||||
},
|
||||
value(val) {
|
||||
this.val = val
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 对话框遮罩不可点击
|
||||
this.popup.mkclick = false
|
||||
if (this.mode === 'input') {
|
||||
this.dialogType = 'info'
|
||||
this.val = this.value
|
||||
} else {
|
||||
this.dialogType = this.type
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.focus = true
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 点击确认按钮
|
||||
*/
|
||||
onOk() {
|
||||
this.$emit('confirm', () => {
|
||||
this.popup.close()
|
||||
if (this.mode === 'input') this.val = this.value
|
||||
}, this.mode === 'input' ? this.val : '')
|
||||
},
|
||||
/**
|
||||
* 点击取消按钮
|
||||
*/
|
||||
close() {
|
||||
if (this.beforeClose) {
|
||||
this.$emit('close', () => {
|
||||
this.popup.close()
|
||||
})
|
||||
return
|
||||
}
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-popup-dialog {
|
||||
width: 300px;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-dialog-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 15px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.uni-dialog-title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.uni-dialog-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 5px 15px 15px 15px;
|
||||
}
|
||||
|
||||
.uni-dialog-content-text {
|
||||
font-size: 14px;
|
||||
color: #6e6e6e;
|
||||
}
|
||||
|
||||
.uni-dialog-button-group {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
border-top-color: #f5f5f5;
|
||||
border-top-style: solid;
|
||||
border-top-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 45px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-border-left {
|
||||
border-left-color: #f0f0f0;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.uni-dialog-button-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-button-color {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.uni-dialog-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
color: #4cd964;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
color: #dd524d;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.uni-popup-dialog__close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
right: 17px;
|
||||
}
|
||||
|
||||
.uni-popup-dialog__close-icon {
|
||||
display: inline-block;
|
||||
width: 13px;
|
||||
height: 1px;
|
||||
background: #909399;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.uni-popup-dialog__close-icon::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 13px;
|
||||
height: 1px;
|
||||
background: #909399;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
</style>
|
||||
138
components/uni-popup-message/uni-popup-message.vue
Normal file
138
components/uni-popup-message/uni-popup-message.vue
Normal file
@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<view class="uni-popup-message">
|
||||
<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+[type]">
|
||||
<text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* PopUp 弹出层-消息提示
|
||||
* @description 弹出层-消息提示
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [success|warning|info|error] 主题样式
|
||||
* @value success 成功
|
||||
* @value warning 提示
|
||||
* @value info 消息
|
||||
* @value error 错误
|
||||
* @property {String} message 消息提示文字
|
||||
* @property {String} duration 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniPopupMessage',
|
||||
props: {
|
||||
/**
|
||||
* 主题 success/warning/info/error 默认 success
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'success'
|
||||
},
|
||||
/**
|
||||
* 消息文字
|
||||
*/
|
||||
message: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 显示时间,设置为 0 则不会自动关闭
|
||||
*/
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 3000
|
||||
}
|
||||
},
|
||||
inject: ['popup'],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
created() {
|
||||
this.popup.childrenMsg = this
|
||||
},
|
||||
methods: {
|
||||
open() {
|
||||
if (this.duration === 0) return
|
||||
clearTimeout(this.popuptimer)
|
||||
this.popuptimer = setTimeout(() => {
|
||||
this.popup.close()
|
||||
}, this.duration)
|
||||
},
|
||||
close() {
|
||||
clearTimeout(this.popuptimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-popup-message {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-popup-message__box {
|
||||
background-color: #e1f3d8;
|
||||
padding: 10px 15px;
|
||||
border-color: #eee;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 500px) {
|
||||
.fixforpc-width {
|
||||
margin-top: 20px;
|
||||
border-radius: 4px;
|
||||
flex: none;
|
||||
min-width: 380px;
|
||||
/* #ifndef APP-NVUE */
|
||||
max-width: 50%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
max-width: 500px;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.uni-popup-message-text {
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.uni-popup__success {
|
||||
background-color: #e1f3d8;
|
||||
}
|
||||
|
||||
.uni-popup__success-text {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.uni-popup__warn {
|
||||
background-color: #faecd8;
|
||||
}
|
||||
|
||||
.uni-popup__warn-text {
|
||||
color: #E6A23C;
|
||||
}
|
||||
|
||||
.uni-popup__error {
|
||||
background-color: #fde2e2;
|
||||
}
|
||||
|
||||
.uni-popup__error-text {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.uni-popup__info {
|
||||
background-color: #F2F6FC;
|
||||
}
|
||||
|
||||
.uni-popup__info-text {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
168
components/uni-popup-share/uni-popup-share.vue
Normal file
168
components/uni-popup-share/uni-popup-share.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<view class="uni-popup-share">
|
||||
<view class="uni-share-title"><text class="uni-share-title-text">{{title}}</text></view>
|
||||
<view class="uni-share-content">
|
||||
<view class="uni-share-content-box">
|
||||
<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
|
||||
<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
|
||||
<text class="uni-share-text">{{item.text}}</text>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-share-button-box">
|
||||
<button class="uni-share-button" @click="close">取消</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'UniPopupShare',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '分享到'
|
||||
}
|
||||
},
|
||||
inject: ['popup'],
|
||||
data() {
|
||||
return {
|
||||
bottomData: [{
|
||||
text: '微信',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: '支付宝',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'wx'
|
||||
},
|
||||
{
|
||||
text: 'QQ',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
|
||||
name: 'qq'
|
||||
},
|
||||
{
|
||||
text: '新浪',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
|
||||
name: 'sina'
|
||||
},
|
||||
{
|
||||
text: '百度',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
|
||||
name: 'copy'
|
||||
},
|
||||
{
|
||||
text: '其他',
|
||||
icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
|
||||
name: 'more'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
created() {},
|
||||
methods: {
|
||||
/**
|
||||
* 选择内容
|
||||
*/
|
||||
select(item, index) {
|
||||
this.$emit('select', {
|
||||
item,
|
||||
index
|
||||
}, () => {
|
||||
this.popup.close()
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 关闭窗口
|
||||
*/
|
||||
close() {
|
||||
this.popup.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-popup-share {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.uni-share-title {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.uni-share-title-text {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.uni-share-content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.uni-share-content-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
width: 360px;
|
||||
}
|
||||
|
||||
.uni-share-content-item {
|
||||
width: 90px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-share-content-item:active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.uni-share-image {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.uni-share-text {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
color: #3B4144;
|
||||
}
|
||||
|
||||
.uni-share-button-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.uni-share-button {
|
||||
flex: 1;
|
||||
border-radius: 50px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.uni-share-button::after {
|
||||
border-radius: 50px;
|
||||
}
|
||||
</style>
|
||||
22
components/uni-popup/message.js
Normal file
22
components/uni-popup/message.js
Normal file
@ -0,0 +1,22 @@
|
||||
export default {
|
||||
created() {
|
||||
if (this.type === 'message') {
|
||||
// 不显示遮罩
|
||||
this.maskShow = false
|
||||
// 获取子组件对象
|
||||
this.childrenMsg = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
customOpen() {
|
||||
if (this.childrenMsg) {
|
||||
this.childrenMsg.open()
|
||||
}
|
||||
},
|
||||
customClose() {
|
||||
if (this.childrenMsg) {
|
||||
this.childrenMsg.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
components/uni-popup/popup.js
Normal file
50
components/uni-popup/popup.js
Normal file
@ -0,0 +1,50 @@
|
||||
import message from './message.js';
|
||||
// 定义 type 类型:弹出类型:top/bottom/center
|
||||
const config = {
|
||||
// 顶部弹出
|
||||
top: 'top',
|
||||
// 底部弹出
|
||||
bottom: 'bottom',
|
||||
// 居中弹出
|
||||
center: 'center',
|
||||
// 消息提示
|
||||
message: 'top',
|
||||
// 对话框
|
||||
dialog: 'center',
|
||||
// 分享
|
||||
share: 'bottom',
|
||||
}
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config: config,
|
||||
popupWidth: 0,
|
||||
popupHeight: 0
|
||||
}
|
||||
},
|
||||
mixins: [message],
|
||||
computed: {
|
||||
isDesktop() {
|
||||
return this.popupWidth >= 500 && this.popupHeight >= 500
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const fixSize = () => {
|
||||
const {
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
windowTop
|
||||
} = uni.getSystemInfoSync()
|
||||
this.popupWidth = windowWidth
|
||||
this.popupHeight = windowHeight + windowTop
|
||||
}
|
||||
fixSize()
|
||||
// #ifdef H5
|
||||
window.addEventListener('resize', fixSize)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
window.removeEventListener('resize', fixSize)
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
}
|
||||
16
components/uni-popup/share.js
Normal file
16
components/uni-popup/share.js
Normal file
@ -0,0 +1,16 @@
|
||||
export default {
|
||||
created() {
|
||||
if (this.type === 'share') {
|
||||
// 关闭点击
|
||||
this.mkclick = false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
customOpen() {
|
||||
console.log('share 打开了');
|
||||
},
|
||||
customClose() {
|
||||
console.log('share 关闭了');
|
||||
}
|
||||
}
|
||||
}
|
||||
312
components/uni-popup/uni-popup.vue
Normal file
312
components/uni-popup/uni-popup.vue
Normal file
@ -0,0 +1,312 @@
|
||||
<template>
|
||||
<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']" @touchmove.stop.prevent="clear">
|
||||
<uni-transition v-if="maskShow" class="uni-mask--hook" :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
|
||||
<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
|
||||
<view class="uni-popup__wrapper-box" @click.stop="clear">
|
||||
<slot />
|
||||
</view>
|
||||
</uni-transition>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniTransition from '../uni-transition/uni-transition.vue'
|
||||
import popup from './popup.js'
|
||||
/**
|
||||
* PopUp 弹出层
|
||||
* @description 弹出层组件,为了解决遮罩弹层的问题
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
|
||||
* @property {String} type = [top|center|bottom] 弹出方式
|
||||
* @value top 顶部弹出
|
||||
* @value center 中间弹出
|
||||
* @value bottom 底部弹出
|
||||
* @value message 消息提示
|
||||
* @value dialog 对话框
|
||||
* @value share 底部分享示例
|
||||
* @property {Boolean} animation = [ture|false] 是否开启动画
|
||||
* @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
|
||||
* @event {Function} change 打开关闭弹窗触发,e={show: false}
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniPopup',
|
||||
components: {
|
||||
uniTransition
|
||||
},
|
||||
props: {
|
||||
// 开启动画
|
||||
animation: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
|
||||
// message: 消息提示 ; dialog : 对话框
|
||||
type: {
|
||||
type: String,
|
||||
default: 'center'
|
||||
},
|
||||
// maskClick
|
||||
maskClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
popup: this
|
||||
}
|
||||
},
|
||||
mixins: [popup],
|
||||
watch: {
|
||||
/**
|
||||
* 监听type类型
|
||||
*/
|
||||
type: {
|
||||
handler: function(newVal) {
|
||||
this[this.config[newVal]]()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
isDesktop: {
|
||||
handler: function(newVal) {
|
||||
this[this.config[this.type]]()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
/**
|
||||
* 监听遮罩是否可点击
|
||||
* @param {Object} val
|
||||
*/
|
||||
maskClick: {
|
||||
handler: function(val) {
|
||||
this.mkclick = val
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
duration: 300,
|
||||
ani: [],
|
||||
showPopup: false,
|
||||
showTrans: false,
|
||||
maskClass: {
|
||||
'position': 'fixed',
|
||||
'bottom': 0,
|
||||
'top': 0,
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'backgroundColor': 'rgba(0, 0, 0, 0.4)'
|
||||
},
|
||||
transClass: {
|
||||
'position': 'fixed',
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
},
|
||||
maskShow: true,
|
||||
mkclick: true,
|
||||
popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.mkclick = this.maskClick
|
||||
if (this.animation) {
|
||||
this.duration = 300
|
||||
} else {
|
||||
this.duration = 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clear(e) {
|
||||
// TODO nvue 取消冒泡
|
||||
e.stopPropagation()
|
||||
},
|
||||
open() {
|
||||
this.showPopup = true
|
||||
this.$nextTick(() => {
|
||||
new Promise(resolve => {
|
||||
clearTimeout(this.timer)
|
||||
this.timer = setTimeout(() => {
|
||||
this.showTrans = true
|
||||
// fixed by mehaotian 兼容 app 端
|
||||
this.$nextTick(() => {
|
||||
resolve();
|
||||
})
|
||||
}, 50);
|
||||
}).then(res => {
|
||||
// 自定义打开事件
|
||||
clearTimeout(this.msgtimer)
|
||||
this.msgtimer = setTimeout(() => {
|
||||
this.customOpen && this.customOpen()
|
||||
}, 100)
|
||||
this.$emit('change', {
|
||||
show: true,
|
||||
type: this.type
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
close(type) {
|
||||
this.showTrans = false
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change', {
|
||||
show: false,
|
||||
type: this.type
|
||||
})
|
||||
clearTimeout(this.timer)
|
||||
// 自定义关闭事件
|
||||
this.customOpen && this.customClose()
|
||||
this.timer = setTimeout(() => {
|
||||
this.showPopup = false
|
||||
}, 300)
|
||||
})
|
||||
},
|
||||
onTap() {
|
||||
if (!this.mkclick) return
|
||||
this.close()
|
||||
},
|
||||
/**
|
||||
* 顶部弹出样式处理
|
||||
*/
|
||||
top() {
|
||||
this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
|
||||
this.ani = ['slide-top']
|
||||
this.transClass = {
|
||||
'position': 'fixed',
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 底部弹出样式处理
|
||||
*/
|
||||
bottom() {
|
||||
this.popupstyle = 'bottom'
|
||||
this.ani = ['slide-bottom']
|
||||
this.transClass = {
|
||||
'position': 'fixed',
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'bottom': 0
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 中间弹出样式处理
|
||||
*/
|
||||
center() {
|
||||
this.popupstyle = 'center'
|
||||
this.ani = ['zoom-out', 'fade']
|
||||
this.transClass = {
|
||||
'position': 'fixed',
|
||||
/* #ifndef APP-NVUE */
|
||||
'display': 'flex',
|
||||
'flexDirection': 'column',
|
||||
/* #endif */
|
||||
'bottom': 0,
|
||||
'left': 0,
|
||||
'right': 0,
|
||||
'top': 0,
|
||||
'justifyContent': 'center',
|
||||
'alignItems': 'center'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
@charset "UTF-8";
|
||||
|
||||
.uni-popup {
|
||||
position: fixed;
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 99;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.fixforpc-z-index {
|
||||
/* #ifndef APP-NVUE */
|
||||
z-index: 999;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-popup__mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mask-ani {
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.uni-top-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-bottom-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-center-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.uni-popup__wrapper {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.top {
|
||||
/* #ifdef H5 */
|
||||
top: var(--window-top);
|
||||
/* #endif */
|
||||
/* #ifndef H5 */
|
||||
top: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.fixforpc-top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.uni-popup__wrapper-box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: block;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
/* iphonex 等安全区设置,底部安全区适配 */
|
||||
/* #ifndef APP-NVUE */
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.content-ani {
|
||||
transition-property: transform, opacity;
|
||||
transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
.uni-top-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-bottom-content {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.uni-center-content {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
347
components/uni-rate/uni-rate.vue
Normal file
347
components/uni-rate/uni-rate.vue
Normal file
@ -0,0 +1,347 @@
|
||||
<template>
|
||||
<view>
|
||||
<view ref="uni-rate" class="uni-rate">
|
||||
<view v-if="" class="uni-rate__icon" :class="{'uni-cursor-not-allowed': disabled}" :style="{ 'margin-right': margin + 'px' }" v-for="(star, index) in stars" :key="index" @touchstart.stop="touchstart" @touchmove.stop="touchmove" @mousedown.stop="mousedown" @mousemove.stop="mousemove" @mouseleave="mouseleave">
|
||||
<uni-icons :color="color" :size="size" :type="isFill ? 'star-filled' : 'star'" />
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on">
|
||||
<uni-icons style="text-align: left;" :color="disabled?'#ccc':activeColor" :size="size" type="star-filled" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view :style="{ width: star.activeWitch}" class="uni-rate__icon-on">
|
||||
<uni-icons :color="disabled?disabledColor:activeColor" :size="size" type="star-filled" />
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const dom = uni.requireNativePlugin('dom');
|
||||
// #endif
|
||||
import uniIcons from "../uni-icons/uni-icons.vue";
|
||||
/**
|
||||
* Rate 评分
|
||||
* @description 评分组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=33
|
||||
* @property {Boolean} isFill = [true|false] 星星的类型,是否为实心类型, 默认为实心
|
||||
* @property {String} color 未选中状态的星星颜色,默认为 "#ececec"
|
||||
* @property {String} activeColor 选中状态的星星颜色,默认为 "#ffca3e"
|
||||
* @property {String} disabledColor 禁用状态的星星颜色,默认为 "#c0c0c0"
|
||||
* @property {Number} size 星星的大小
|
||||
* @property {Number} value/v-model 当前评分
|
||||
* @property {Number} max 最大评分评分数量,目前一分一颗星
|
||||
* @property {Number} margin 星星的间距,单位 px
|
||||
* @property {Boolean} disabled = [true|false] 是否为禁用状态,默认为 false
|
||||
* @property {Boolean} readonly = [true|false] 是否为只读状态,默认为 false
|
||||
* @property {Boolean} allowHalf = [true|false] 是否实现半星,默认为 false
|
||||
* @property {Boolean} touchable = [true|false] 是否支持滑动手势,默认为 true
|
||||
* @event {Function} change uniRate 的 value 改变时触发事件,e={value:Number}
|
||||
*/
|
||||
|
||||
export default {
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
name: "UniRate",
|
||||
props: {
|
||||
isFill: {
|
||||
// 星星的类型,是否镂空
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
},
|
||||
color: {
|
||||
// 星星未选中的颜色
|
||||
type: String,
|
||||
default: "#ececec"
|
||||
},
|
||||
activeColor: {
|
||||
// 星星选中状态颜色
|
||||
type: String,
|
||||
default: "#ffca3e"
|
||||
},
|
||||
disabledColor: {
|
||||
// 星星禁用状态颜色
|
||||
type: String,
|
||||
default: "#c0c0c0"
|
||||
},
|
||||
size: {
|
||||
// 星星的大小
|
||||
type: [Number, String],
|
||||
default: 24
|
||||
},
|
||||
value: {
|
||||
// 当前评分
|
||||
type: [Number, String],
|
||||
default: 1
|
||||
},
|
||||
max: {
|
||||
// 最大评分
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
},
|
||||
margin: {
|
||||
// 星星的间距
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
},
|
||||
disabled: {
|
||||
// 是否可点击
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
readonly: {
|
||||
// 是否只读
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
allowHalf: {
|
||||
// 是否显示半星
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
touchable: {
|
||||
// 是否支持滑动手势
|
||||
type: [Boolean, String],
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
valueSync: "",
|
||||
userMouseFristMove: true,
|
||||
userRated: false,
|
||||
userLastRate: 1
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.valueSync = Number(newVal);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
stars() {
|
||||
const value = this.valueSync ? this.valueSync : 0;
|
||||
const starList = [];
|
||||
const floorValue = Math.floor(value);
|
||||
const ceilValue = Math.ceil(value);
|
||||
for (let i = 0; i < this.max; i++) {
|
||||
if (floorValue > i) {
|
||||
starList.push({
|
||||
activeWitch: "100%"
|
||||
});
|
||||
} else if (ceilValue - 1 === i) {
|
||||
starList.push({
|
||||
activeWitch: (value - floorValue) * 100 + "%"
|
||||
});
|
||||
} else {
|
||||
starList.push({
|
||||
activeWitch: "0"
|
||||
});
|
||||
}
|
||||
}
|
||||
return starList;
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.valueSync = Number(this.value);
|
||||
this._rateBoxLeft = 0
|
||||
this._oldValue = null
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this._getSize()
|
||||
}, 100)
|
||||
// #ifdef H5
|
||||
this.PC = this.IsPC()
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
touchstart(e) {
|
||||
// #ifdef H5
|
||||
if (this.IsPC()) return
|
||||
// #endif
|
||||
if (this.readonly || this.disabled) return
|
||||
const {
|
||||
clientX,
|
||||
screenX
|
||||
} = e.changedTouches[0]
|
||||
// TODO 做一下兼容,只有 Nvue 下才有 screenX,其他平台式 clientX
|
||||
this._getRateCount(clientX || screenX)
|
||||
},
|
||||
touchmove(e) {
|
||||
// #ifdef H5
|
||||
if (this.IsPC()) return
|
||||
// #endif
|
||||
if (this.readonly || this.disabled || !this.touchable) return
|
||||
const {
|
||||
clientX,
|
||||
screenX
|
||||
} = e.changedTouches[0]
|
||||
this._getRateCount(clientX || screenX)
|
||||
},
|
||||
|
||||
/**
|
||||
* 兼容 PC @tian
|
||||
*/
|
||||
|
||||
mousedown(e) {
|
||||
// #ifdef H5
|
||||
if (!this.IsPC()) return
|
||||
if (this.readonly || this.disabled) return
|
||||
const {
|
||||
clientX,
|
||||
} = e
|
||||
this.userLastRate = this.valueSync
|
||||
this._getRateCount(clientX)
|
||||
this.userRated = true
|
||||
// #endif
|
||||
},
|
||||
mousemove(e) {
|
||||
// #ifdef H5
|
||||
if (!this.IsPC()) return
|
||||
if (this.userRated) return
|
||||
if (this.userMouseFristMove) {
|
||||
console.log('---mousemove----', this.valueSync);
|
||||
this.userLastRate = this.valueSync
|
||||
this.userMouseFristMove = false
|
||||
}
|
||||
if (this.readonly || this.disabled || !this.touchable) return
|
||||
const {
|
||||
clientX,
|
||||
} = e
|
||||
this._getRateCount(clientX)
|
||||
// #endif
|
||||
},
|
||||
mouseleave(e) {
|
||||
// #ifdef H5
|
||||
if (!this.IsPC()) return
|
||||
if (this.readonly || this.disabled || !this.touchable) return
|
||||
if (this.userRated) {
|
||||
this.userRated = false
|
||||
return
|
||||
}
|
||||
this.valueSync = this.userLastRate
|
||||
// #endif
|
||||
},
|
||||
// #ifdef H5
|
||||
IsPC() {
|
||||
var userAgentInfo = navigator.userAgent;
|
||||
var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
|
||||
var flag = true;
|
||||
for (var v = 0; v < Agents.length; v++) {
|
||||
if (userAgentInfo.indexOf(Agents[v]) > 0) {
|
||||
flag = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 获取星星个数
|
||||
*/
|
||||
_getRateCount(clientX) {
|
||||
const size = Number(this.size)
|
||||
if (size === NaN) {
|
||||
return new Error('size 属性只能设置为数字')
|
||||
}
|
||||
const rateMoveRange = clientX - this._rateBoxLeft
|
||||
let index = parseInt(rateMoveRange / (size + this.margin))
|
||||
index = index < 0 ? 0 : index;
|
||||
index = index > this.max ? this.max : index;
|
||||
const range = parseInt(rateMoveRange - (size + this.margin) * index);
|
||||
let value = 0;
|
||||
if (this._oldValue === index && !this.PC) return;
|
||||
this._oldValue = index;
|
||||
if (this.allowHalf) {
|
||||
if (range > (size / 2)) {
|
||||
value = index + 1
|
||||
} else {
|
||||
value = index + 0.5
|
||||
}
|
||||
} else {
|
||||
value = index + 1
|
||||
}
|
||||
|
||||
value = Math.max(0.5, Math.min(value, this.max))
|
||||
this.valueSync = value
|
||||
this._onChange()
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发动态修改
|
||||
*/
|
||||
_onChange() {
|
||||
|
||||
this.$emit("input", this.valueSync);
|
||||
this.$emit("change", {
|
||||
value: this.valueSync
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 获取星星距离屏幕左侧距离
|
||||
*/
|
||||
_getSize() {
|
||||
// #ifndef APP-NVUE
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.uni-rate')
|
||||
.boundingClientRect()
|
||||
.exec(ret => {
|
||||
if (ret) {
|
||||
this._rateBoxLeft = ret[0].left
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
dom.getComponentRect(this.$refs['uni-rate'], (ret) => {
|
||||
const size = ret.size
|
||||
if (size) {
|
||||
this._rateBoxLeft = size.left
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-rate {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
line-height: 1;
|
||||
font-size: 0;
|
||||
flex-direction: row;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-rate__icon {
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.uni-rate__icon-on {
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
line-height: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.uni-cursor-not-allowed {
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed !important;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
152
components/uni-row/uni-row.vue
Normal file
152
components/uni-row/uni-row.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<view :class="[ 'uni-row', typeClass , justifyClass, alignClass, ]" :style="{
|
||||
marginLeft:`${Number(marginValue)}rpx`,
|
||||
marginRight:`${Number(marginValue)}rpx`,
|
||||
}">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* uni-row 布局-行
|
||||
* @description 流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=
|
||||
*
|
||||
* @property {gutter} type = Number 栅格间隔
|
||||
* @property {justify} type = String flex 布局下的水平排列方式
|
||||
* 可选 start/end/center/space-around/space-between start
|
||||
* 默认值 start
|
||||
* @property {align} type = String flex 布局下的垂直排列方式
|
||||
* 可选 top/middle/bottom
|
||||
* 默认值 top
|
||||
* @property {width} type = String|Number nvue下需要自行配置宽度用于计算
|
||||
* 默认值 750
|
||||
*/
|
||||
const ComponentClass = 'uni-row';
|
||||
const modifierSeparator = '--';
|
||||
|
||||
export default {
|
||||
name: 'uniRow',
|
||||
componentName: 'uniRow',
|
||||
// #ifdef MP-WEIXIN
|
||||
options: {
|
||||
virtualHost: true // 在微信小程序中将组件节点渲染为虚拟节点,更加接近Vue组件的表现,可使用flex布局
|
||||
},
|
||||
// #endif
|
||||
props: {
|
||||
type: String,
|
||||
gutter: Number,
|
||||
justify: {
|
||||
type: String,
|
||||
default: 'start'
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
// nvue如果使用span等属性,需要配置宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: 750
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// #ifdef APP-NVUE
|
||||
this.type = 'flex';
|
||||
// #endif
|
||||
},
|
||||
computed: {
|
||||
marginValue() {
|
||||
// #ifndef APP-NVUE
|
||||
if (this.gutter) {
|
||||
return -(this.gutter / 2);
|
||||
}
|
||||
// #endif
|
||||
return 0;
|
||||
},
|
||||
typeClass() {
|
||||
return this.type === 'flex' ? `${ComponentClass + modifierSeparator}flex` : '';
|
||||
},
|
||||
justifyClass() {
|
||||
return this.justify !== 'start' ? `${ComponentClass + modifierSeparator}flex-justify-${this.justify}` : ''
|
||||
},
|
||||
alignClass() {
|
||||
return this.align !== 'top' ? `${ComponentClass + modifierSeparator}flex-align-${this.align}` : ''
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-row {
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
/* #ifdef APP-NVUE */
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
/* #ifndef MP-QQ || MP-TOUTIAO || MP-BAIDU */
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-row::before,
|
||||
.uni-row::after {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.uni-row::after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.uni-row--flex {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-row--flex:before,
|
||||
.uni-row--flex:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.uni-row--flex-justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.uni-row--flex-justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.uni-row--flex-justify-space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.uni-row--flex-justify-space-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.uni-row--flex-align-middle {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-row--flex-align-bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
/* #ifdef MP-WEIXIN || MP-TOUTIAO || MP-QQ */
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
||||
249
components/uni-search-bar/uni-search-bar.vue
Normal file
249
components/uni-search-bar/uni-search-bar.vue
Normal file
@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<view class="uni-searchbar">
|
||||
<view :style="{borderRadius:radius+'px',backgroundColor: bgColor}" class="uni-searchbar__box" @click="searchClick">
|
||||
<view class="uni-searchbar__box-icon-search">
|
||||
<slot name="searchIcon">
|
||||
<uni-icons color="#999999" size="18" type="search" />
|
||||
</slot>
|
||||
</view>
|
||||
<input v-if="show || searchVal" :focus="showSync" :placeholder="placeholder" :maxlength="maxlength" class="uni-searchbar__box-search-input" confirm-type="search" type="text" v-model="searchVal" @confirm="confirm" @blur="blur" />
|
||||
<text v-else class="uni-searchbar__text-placeholder">{{ placeholder }}</text>
|
||||
<view v-if="show && (clearButton==='always'||clearButton==='auto'&&searchVal!=='')" class="uni-searchbar__box-icon-clear" @click="clear">
|
||||
<slot name="clearIcon">
|
||||
<uni-icons color="#c0c4cc" size="18" type="clear" />
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
<text @click="cancel" class="uni-searchbar__cancel" v-if="cancelButton ==='always' || show && cancelButton ==='auto'">{{cancelText}}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniIcons from "../uni-icons/uni-icons.vue";
|
||||
|
||||
/**
|
||||
* SearchBar 搜索栏
|
||||
* @description 评分组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=866
|
||||
* @property {Number} radius 搜索栏圆角
|
||||
* @property {Number} maxlength 输入最大长度
|
||||
* @property {String} placeholder 搜索栏Placeholder
|
||||
* @property {String} clearButton = [always|auto|none] 是否显示清除按钮
|
||||
* @value always 一直显示
|
||||
* @value auto 输入框不为空时显示
|
||||
* @value none 一直不显示
|
||||
* @property {String} cancelButton = [always|auto|none] 是否显示取消按钮
|
||||
* @value always 一直显示
|
||||
* @value auto 输入框不为空时显示
|
||||
* @value none 一直不显示
|
||||
* @property {String} cancelText 取消按钮的文字
|
||||
* @property {String} bgColor 输入框背景颜色
|
||||
* @property {Boolean} focus 是否自动聚焦
|
||||
* @event {Function} confirm uniSearchBar 的输入框 confirm 事件,返回参数为uniSearchBar的value,e={value:Number}
|
||||
* @event {Function} input uniSearchBar 的 value 改变时触发事件,返回参数为uniSearchBar的value,e=value
|
||||
* @event {Function} cancel 点击取消按钮时触发事件,返回参数为uniSearchBar的value,e={value:Number}
|
||||
* @event {Function} clear 点击清除按钮时触发事件,返回参数为uniSearchBar的value,e={value:Number}
|
||||
* @event {Function} blur input失去焦点时触发事件,返回参数为uniSearchBar的value,e={value:Number}
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: "UniSearchBar",
|
||||
components: {
|
||||
uniIcons
|
||||
},
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "请输入搜索内容"
|
||||
},
|
||||
radius: {
|
||||
type: [Number, String],
|
||||
default: 5
|
||||
},
|
||||
clearButton: {
|
||||
type: String,
|
||||
default: "auto"
|
||||
},
|
||||
cancelButton: {
|
||||
type: String,
|
||||
default: "auto"
|
||||
},
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: "#F8F8F8"
|
||||
},
|
||||
maxlength: {
|
||||
type: [Number, String],
|
||||
default: 100
|
||||
},
|
||||
value: {
|
||||
type: [Number, String],
|
||||
default: ""
|
||||
},
|
||||
focus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
showSync: false,
|
||||
searchVal: ''
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
this.searchVal = newVal
|
||||
if (newVal) {
|
||||
this.show = true
|
||||
}
|
||||
}
|
||||
},
|
||||
focus: {
|
||||
immediate: true,
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.show = true;
|
||||
this.$nextTick(() => {
|
||||
this.showSync = true
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
searchVal(newVal, oldVal) {
|
||||
this.$emit("input", newVal)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
searchClick() {
|
||||
if (this.show) {
|
||||
return
|
||||
}
|
||||
this.show = true;
|
||||
this.$nextTick(() => {
|
||||
this.showSync = true
|
||||
})
|
||||
},
|
||||
clear() {
|
||||
this.$emit("clear", {
|
||||
value: this.searchVal
|
||||
})
|
||||
this.searchVal = ""
|
||||
},
|
||||
cancel() {
|
||||
this.$emit("cancel", {
|
||||
value: this.searchVal
|
||||
});
|
||||
this.searchVal = ""
|
||||
this.show = false
|
||||
this.showSync = false
|
||||
// #ifndef APP-PLUS
|
||||
uni.hideKeyboard()
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.key.hideSoftKeybord()
|
||||
// #endif
|
||||
},
|
||||
confirm() {
|
||||
// #ifndef APP-PLUS
|
||||
uni.hideKeyboard();
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.key.hideSoftKeybord()
|
||||
// #endif
|
||||
this.$emit("confirm", {
|
||||
value: this.searchVal
|
||||
})
|
||||
},
|
||||
blur() {
|
||||
// #ifndef APP-PLUS
|
||||
uni.hideKeyboard();
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
plus.key.hideSoftKeybord()
|
||||
// #endif
|
||||
this.$emit("blur", {
|
||||
value: this.searchVal
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-searchbar {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.uni-searchbar__box {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
height: 36px;
|
||||
padding: 5px 8px 5px 0px;
|
||||
border-width: 0.5px;
|
||||
border-style: solid;
|
||||
border-color: #e5e5e5;
|
||||
}
|
||||
|
||||
.uni-searchbar__box-icon-search {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 0 8px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.uni-searchbar__box-search-input {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-searchbar__box-icon-clear {
|
||||
align-items: center;
|
||||
line-height: 24px;
|
||||
padding-left: 8px;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-searchbar__text-placeholder {
|
||||
font-size: 14px;
|
||||
color: #808080;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.uni-searchbar__cancel {
|
||||
padding-left: 10px;
|
||||
line-height: 36px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
124
components/uni-section/uni-section.vue
Normal file
124
components/uni-section/uni-section.vue
Normal file
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<view class="uni-section" nvue>
|
||||
<view v-if="type" class="uni-section__head">
|
||||
<view :class="type" class="uni-section__head-tag" />
|
||||
</view>
|
||||
<view class="uni-section__content">
|
||||
<text :class="{'distraction':!subTitle}" class="uni-section__content-title">{{ title }}</text>
|
||||
<text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text>
|
||||
</view>
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* Section 标题栏
|
||||
* @description 标题栏
|
||||
* @property {String} type = [line|circle] 标题装饰类型
|
||||
* @value line 竖线
|
||||
* @value circle 圆形
|
||||
* @property {String} title 主标题
|
||||
* @property {String} subTitle 副标题
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniSection',
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
subTitle: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
watch: {
|
||||
title(newVal) {
|
||||
if (uni.report && newVal !== '') {
|
||||
uni.report('title', newVal)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.uni-section {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
margin-top: 10px;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
height: 50px;
|
||||
background-color: #f8f8f8;
|
||||
/* #ifdef APP-NVUE */
|
||||
/* #endif */
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
/* #endif */
|
||||
.uni-section__head {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.line {
|
||||
height: 15px;
|
||||
background-color: #c0c0c0;
|
||||
border-radius: 5px;
|
||||
width: 3px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-top-right-radius: 50px;
|
||||
border-top-left-radius: 50px;
|
||||
border-bottom-left-radius: 50px;
|
||||
border-bottom-right-radius: 50px;
|
||||
background-color: #c0c0c0;
|
||||
}
|
||||
|
||||
.uni-section__content {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.uni-section__content-title {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.distraction {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uni-section__content-sub {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
136
components/uni-segmented-control/uni-segmented-control.vue
Normal file
136
components/uni-segmented-control/uni-segmented-control.vue
Normal file
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<view :class="[styleType === 'text'?'segmented-control--text' : 'segmented-control--button' ]" :style="{ borderColor: styleType === 'text' ? '' : activeColor }" class="segmented-control">
|
||||
<view v-for="(item, index) in values" :class="[ styleType === 'text'?'segmented-control__item--text': 'segmented-control__item--button' , index === currentIndex&&styleType === 'button'?'segmented-control__item--button--active': '' , index === 0&&styleType === 'button'?'segmented-control__item--button--first': '',index === values.length - 1&&styleType === 'button'?'segmented-control__item--button--last': '' ]" :key="index" :style="{
|
||||
backgroundColor: index === currentIndex && styleType === 'button' ? activeColor : '',borderColor: index === currentIndex&&styleType === 'text'||styleType === 'button'?activeColor:'transparent'
|
||||
}" class="segmented-control__item" @click="_onClick(index)">
|
||||
<text :style="{color:
|
||||
index === currentIndex
|
||||
? styleType === 'text'
|
||||
? activeColor
|
||||
: '#fff'
|
||||
: styleType === 'text'
|
||||
? '#000'
|
||||
: activeColor}" class="segmented-control__text">{{ item }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* SegmentedControl 分段器
|
||||
* @description 用作不同视图的显示
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=54
|
||||
* @property {Number} current 当前选中的tab索引值,从0计数
|
||||
* @property {String} styleType = [button|text] 分段器样式类型
|
||||
* @value button 按钮类型
|
||||
* @value text 文字类型
|
||||
* @property {String} activeColor 选中的标签背景色与边框颜色
|
||||
* @property {Array} values 选项数组
|
||||
* @event {Function} clickItem 组件触发点击事件时触发,e={currentIndex}
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'UniSegmentedControl',
|
||||
props: {
|
||||
current: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
values: {
|
||||
type: Array,
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#007aff'
|
||||
},
|
||||
styleType: {
|
||||
type: String,
|
||||
default: 'button'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentIndex: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current(val) {
|
||||
if (val !== this.currentIndex) {
|
||||
this.currentIndex = val
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.currentIndex = this.current
|
||||
},
|
||||
methods: {
|
||||
_onClick(index) {
|
||||
if (this.currentIndex !== index) {
|
||||
this.currentIndex = index
|
||||
this.$emit('clickItem', {
|
||||
currentIndex: index
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.segmented-control {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
height: 36px;
|
||||
overflow: hidden;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.segmented-control__item {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: inline-flex;
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.segmented-control__item--button {
|
||||
border-style: solid;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
border-right-width: 1px;
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.segmented-control__item--button--first {
|
||||
border-left-width: 1px;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.segmented-control__item--button--last {
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.segmented-control__item--text {
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: 3px;
|
||||
}
|
||||
|
||||
.segmented-control__text {
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
23
components/uni-status-bar/uni-status-bar.vue
Normal file
23
components/uni-status-bar/uni-status-bar.vue
Normal file
@ -0,0 +1,23 @@
|
||||
<template>
|
||||
<view :style="{ height: statusBarHeight }" class="uni-status-bar">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
var statusBarHeight = uni.getSystemInfoSync().statusBarHeight + 'px'
|
||||
export default {
|
||||
name: 'UniStatusBar',
|
||||
data() {
|
||||
return {
|
||||
statusBarHeight: statusBarHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.uni-status-bar {
|
||||
height: 20px;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user