Compare commits

...

No commits in common. "main" and "mini-program" have entirely different histories.

1251 changed files with 167027 additions and 27 deletions

BIN
.DS_Store vendored 100644

Binary file not shown.

View File

@ -0,0 +1,16 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

73
App.vue
View File

@ -1,18 +1,55 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>
<style lang="scss">
/*每个页面公共css */
@import "uview-ui/index.scss";
</style>
<script>
export default {
onLaunch: function() {
// console.log('App Launch')
const updateManager = uni.getUpdateManager();
//
updateManager.onCheckForUpdate(function(res) {
//
if (res.hasUpdate) {
//
updateManager.onUpdateReady(function() {
uni.showModal({
title: '更新提示',
content: '新版本已经准备好,是否重启应用?',
showCancel: false,
success(res) {
// console.log(res)
if (res.confirm) {
// applyUpdate
updateManager.applyUpdate();
}
// if (res.cancel) {
// // applyUpdate
// uni.showModal({
// title: '',
// content: ' - ',
// })
// }
}
})
})
updateManager.onUpdateFailed(function() {
//
uni.showModal({
title: '已经有新版本啦!',
content: '请您删除当前小程序,到微信 “发现-小程序” 页,重新搜索打开。',
})
})
}
})
},
onShow: function() {
// console.log('App Show')
},
onHide: function() {
// console.log('App Hide')
}
}
</script>
<style lang="scss">
/*每个页面公共css */
@import "uview-ui/index.scss";
</style>

21
LICENSE 100644
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 www.uviewui.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

106
README.md 100644
View File

@ -0,0 +1,106 @@
<p align="center">
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
<h3 align="center">多平台快速开发的UI框架</h3>
## 说明
uView UI是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架全面的组件和便捷的工具会让您信手拈来如鱼得水
## 特性
- 兼容安卓iOS微信小程序H5QQ小程序百度小程序支付宝小程序头条小程序
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
- 众多贴心的JS利器让您飞镖在手召之即来百步穿杨
- 众多的常用页面和布局,让您专注逻辑,事半功倍
- 详尽的文档支持,现代化的演示效果
- 按需引入,精简打包体积
## 安装
```bash
# npm方式安装
npm i uview-ui
```
## 快速上手
1. `main.js`引入uView库
```js
// main.js
import uView from 'uview-ui';
Vue.use(uView);
```
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
```css
/* App.vue */
<style lang="scss">
@import "uview-ui/index.scss";
</style>
```
3. `uni.scss`引入全局scss变量文件
```css
/* uni.scss */
@import "uview-ui/theme.scss";
```
4. `pages.json`配置easycom规则(按需引入)
```js
// pages.json
{
"easycom": {
// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
// npm安装方式
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
// 下载安装方式
// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
},
// 此为本身已有的内容
"pages": [
// ......
]
}
```
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
## 使用方法
配置easycom规则后自动按需引入无需`import`组件,直接引用即可。
```html
<template>
<u-button>按钮</u-button>
</template>
```
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
## 链接
- [官方文档](https://uviewui.com/)
- [更新日志](https://uviewui.com/components/changelog.html)
- [升级指南](https://uviewui.com/components/changelog.html)
- [关于我们](https://uviewui.com/cooperation/about.html)
## 预览
您可以通过**微信**扫码,查看最佳的演示效果。
<br>
<br>
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
<!-- ## 捐赠uView的研发
uView文档和源码全部开源免费如果您认为uView帮到了您的开发工作您可以捐赠uView的研发工作捐赠无门槛哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
<img src="https://uviewui.com/common/wechat.png" width="220" >
<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
-->
## 版权信息
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议意味着您无需支付任何费用也无需授权即可将uView应用到您的产品中。

View File

@ -0,0 +1,28 @@
// 可修改的公共变量
let type = 'app'
// #ifdef APP-PLUS
type = "app"
// #endif
// #ifdef H5
type = "h5"
// #endif
// #ifdef MP-WEIXIN
type = "wxMini"
// #endif
// 在maps里面有一个地图的key,需要手动改变
const variable = {
name: "", //应用名字
whatType: type, //判断当前是哪端
// baseUrl: "http://hy.ecbeauty.cn", //线上测试接口的基础路径
baseUrl: "https://api.lihe-control.com", //线上接口的基础路径
mapKey: "", //jsapi
webKey: '', //webApi
wxMapKey: "", //小程序key
socket: ''
}
export default variable

394
api/driverapi.js 100644
View File

@ -0,0 +1,394 @@
import variable from '@/api/commonVariable.js'
// 司机端接口
let api = {
// 公共key
baseUrl: variable.baseUrl,
user_login: '/user/login', // 登录
selFs_time: '/selFs/time', //主页查看施肥机历史数据
user_logout: '/user/logout', //退出
user_upload: '/user/upload', //上传logo
update_user: "/api/update/user", //用户更新个人信息,修改昵称密码等
sel_user: "/api/sel/user", //根据id查询用户信息
getFs_rtdata: "/getFs/rtdata", //主页查看施肥机实时数据
selFs_time: "/selFs/time", //主页查看施肥机历史数据
sel_eqbyid: "/sel/eqbyid/", //根据用户id查询对应的设备数据
chart_fsdata: "/chart/fsdata", //实时数据折线图
user_update_pwd: "/user/update/pwd", //用户修改密码
readFs_state: "/readFs/state", //根据设备ID获取实时数据
writeFs: "/writeFs", //根据设备ID获取实时数据
readFs_reg: "/readFs/reg", //根据设备ID获取实时数据
readJinHuaControl_reg: "/readJinHuaControl/reg", //读取所有寄存器的状态值
writeJinHuaControl_write: "/writeJinHuaControl/write", //控制器写入数据(金华)
getcontrol_rtdata: "/getcontrol/rtdata", //主页查看控制器实时数据
getcontrol_fsdata: "/getcontrol/fsdata", //控制器实时数据折线图
getcontrol_cpermission: '/getcontrol/cpermission', //根据设备id查询控制器权限
getControl_getState: '/readControl/getState', //控制器 设备运行状态
getReadControlliShuiControl: '/readControl/liShuiControl', //控制器 顶模和风机
//countType计算类型 0是原数据 1是原数据-400 2是原数据/10 3是(原数据-400) / 10 4是(原数据-1000)/10
typeList: [{
label: '无意义的传感器',
value: 0,
countType: 0,
unit: ''
},
{
label: '空气温度',
value: 1,
countType: 3,
unit: '℃'
},
{
label: '空气湿度',
value: 2,
countType: 2,
unit: '%'
},
{
label: '光亮度',
value: 3,
countType: 2,
unit: 'Klux'
},
{
label: '二氧化碳',
value: 4,
countType: 0,
unit: 'ppm'
},
{
label: '土壤温度',
value: 5,
countType: 3,
unit: '℃'
},
{
label: '土壤湿度',
value: 6,
countType: 2,
unit: '%'
},
{
label: '水温',
value: 7,
countType: 3,
unit: '℃'
},
{
label: '水肥PH',
value: 8,
countType: 2,
unit: ''
},
{
label: '水肥EC',
value: 9,
countType: 2,
unit: 'mS/cm'
},
{
label: '压差',
value: 10,
countType: 0,
unit: 'Pa'
},
{
label: '风速',
value: 11,
countType: 2,
unit: 'm/s'
},
{
label: '风向',
value: 12,
countType: 0,
unit: '°'
},
{
label: '雨雪信号',
value: 13,
countType: 0,
unit: ''
},
{
label: '降雨量',
value: 14,
countType: 2,
unit: 'mm'
},
{
label: '光合有效辐射',
value: 15,
countType: 2,
unit: 'umol/m2.s'
},
{
label: '太阳总辐射',
value: 16,
countType: 0,
unit: 'W/m2'
},
{
label: '流量',
value: 17,
countType: 0,
unit: 'L/H'
},
{
label: '大气压强',
value: 18,
countType: 2,
unit: 'KPa'
},
{
label: '土壤PH',
value: 19,
countType: 2,
unit: ''
},
{
label: '土壤EC',
value: 20,
countType: 0,
unit: 'uS/cm'
},
{
label: '叶面温度',
value: 21,
countType: 3,
unit: '℃'
},
{
label: '叶面湿度',
value: 22,
countType: 2,
unit: '%'
},
{
label: '果实直径',
value: 23,
countType: 0,
unit: 'mm'
},
{
label: '茎秆直径',
value: 24,
countType: 0,
unit: 'mm'
},
{
label: '流速',
value: 25,
countType: 2,
unit: 'm/s'
},
{
label: '瞬时流量',
value: 26,
countType: 2,
unit: 'm3'
},
{
label: '当日流量',
value: 27,
countType: 2,
unit: 'T'
},
{
label: '累计流量',
value: 28,
countType: 0,
unit: 'T'
},
{
label: '无线气象站电池电压',
value: 29,
countType: 2,
unit: 'V'
},
{
label: '无线气象站无线信号强度',
value: 30,
countType: 1,
unit: 'dBm'
},
{
label: '无线气象站信噪比SNR',
value: 31,
countType: 1,
unit: 'dB'
},
{
label: 'PM1.0',
value: 32,
countType: 0,
unit: 'μg/m2'
},
{
label: 'PM2.5',
value: 33,
countType: 0,
unit: 'μg/m2'
},
{
label: 'PM10',
value: 34,
countType: 0,
unit: 'μg/m2'
},
{
label: '紫外辐射',
value: 35,
countType: 0,
unit: 'W/m2'
},
{
label: '蒸发量',
value: 36,
countType: 2,
unit: 'mm'
},
{
label: '负氧离子',
value: 37,
countType: 0,
unit: '个/cm3'
},
{
label: '露点温度',
value: 38,
countType: 3,
unit: '℃'
},
{
label: '设备电压',
value: 39,
countType: 2,
unit: 'V'
},
{
label: '设备电流',
value: 40,
countType: 2,
unit: 'A'
},
{
label: '土壤氮含量',
value: 41,
countType: 0,
unit: 'mg/kg'
},
{
label: '土壤磷含量',
value: 42,
countType: 0,
unit: 'mg/kg'
},
{
label: '土壤钾含量',
value: 43,
countType: 0,
unit: 'mg/kg'
},
{
label: '管道压力',
value: 44,
countType: 0,
unit: 'Kpa'
},
{
label: '一氧化碳',
value: 45,
countType: 0,
unit: 'ppm'
},
{
label: '氨气',
value: 46,
countType: 0,
unit: 'ppm'
},
{
label: '硫化氢',
value: 47,
countType: 0,
unit: 'ppm'
},
{
label: '氧气',
value: 48,
countType: 2,
unit: '%vol'
},
{
label: '土壤张力',
value: 49,
countType: 4,
unit: 'KPa'
},
{
label: '铜CU',
value: 50,
countType: 0,
unit: 'mg/kg'
},
{
label: '镉CD',
value: 51,
countType: 2,
unit: 'mg/kg'
},
{
label: '铅PB',
value: 52,
countType: 0,
unit: 'mg/kg'
},
{
label: '噪声',
value: 53,
countType: 0,
unit: 'dB'
},
{
label: '叶面面积',
value: 54,
countType: 0,
unit: 'mm'
},
{
label: '虫数量',
value: 55,
countType: 0,
unit: '个'
},
{
label: '无线传感器电池电压',
value: 56,
countType: 2,
unit: 'V'
},
{
label: '无线传感器无线信号强度',
value: 57,
countType: 1,
unit: 'dBm'
},
{
label: '无线传感器信噪比SNR',
value: 58,
countType: 1,
unit: 'dB'
},
]
}
export default api

63
api/nvuerequest.js 100644
View File

@ -0,0 +1,63 @@
import api from './driverapi.js'
import variable from '@/api/commonVariable.js'
export const myRequest = (options) => {
return new Promise((resolve, reject) => {
uni.request({
url: api.baseUrl + options.url,
method: options.method || 'POST',
header: {
token: uni.getStorageSync('token'),
// terminal: variable.whatType,
// 'Content-Type': 'application/x-www-form-urlencoded'
},
data: options.data || {},
success: (res) => {
resolve(res.data)
if(res.data.code==200){
}
else if (res.data.code == 401) {
uni.reLaunch({
url: '/pages/login/login'
})
uni.removeStorage({
key: 'token',
success() {
uni.removeStorage({
key: 'user_info',
success() {
}
});
}
});
}else{
uni.hideLoading()
uni.stopPullDownRefresh();
uni.showToast({
icon:'none',
title: res.data.msg,
duration: 2000,
})
}
// 接口成功可以在此写成功需要的步骤
if (res.data.code != 1) {
// uni.showToast({
// title: res.data.msg,
// icon: 'none'
// });
}
},
fail: (err) => {
uni.showToast({
title: '请求接口失败',
icon: 'none'
})
reject(err)
}
})
})
}

View File

@ -0,0 +1,190 @@
<template>
<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
{{tips.text}}
</view>
<block v-for="(item, index) in list" :key="index">
<view
@touchmove.stop.prevent
@tap="itemClick(index)"
:style="[itemStyle(index)]"
class="u-action-sheet-item u-line-1"
:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
:hover-stay-time="150"
>
<text>{{item.text}}</text>
<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
</view>
</block>
<view class="u-gab" v-if="cancelBtn">
</view>
<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
:hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
</u-popup>
</template>
<script>
/**
* actionSheet 操作菜单
* @description 本组件用于从底部弹出一个操作菜单供用户选择并返回结果本组件功能类似于uni的uni.showActionSheetAPI配置更加灵活所有平台都表现一致
* @tutorial https://www.uviewui.com/components/actionSheet.html
* @property {Array<Object>} list 按钮的文字数组见官方文档示例
* @property {Object} tips 顶部的提示文字见官方文档示例
* @property {String} cancel-text 取消按钮的提示文字
* @property {Boolean} cancel-btn 是否显示底部的取消按钮默认true
* @property {Number String} border-radius 弹出部分顶部左右的圆角值单位rpx默认0
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭默认true
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配默认false
* @property {Number String} z-index z-index值默认1075
* @property {String} cancel-text 取消按钮的提示文字
* @event {Function} click 点击ActionSheet列表项时触发
* @event {Function} close 点击取消按钮时触发
* @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
*/
export default {
name: "u-action-sheet",
props: {
// actionsheet
maskCloseAble: {
type: Boolean,
default: true
},
// rpx
list: {
type: Array,
default () {
//
// return [{
// text: '',
// color: '',
// fontSize: ''
// }]
return [];
}
},
//
tips: {
type: Object,
default () {
return {
text: '',
color: '',
fontSize: '26'
}
}
},
//
cancelBtn: {
type: Boolean,
default: true
},
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: false
},
//
value: {
type: Boolean,
default: false
},
//
borderRadius: {
type: [String, Number],
default: 0
},
// z-index
zIndex: {
type: [String, Number],
default: 0
},
//
cancelText: {
type: String,
default: '取消'
}
},
computed: {
//
tipsStyle() {
let style = {};
if (this.tips.color) style.color = this.tips.color;
if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
return style;
},
//
itemStyle() {
return (index) => {
let style = {};
if (this.list[index].color) style.color = this.list[index].color;
if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
//
if (this.list[index].disabled) style.color = '#c0c4cc';
return style;
}
},
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
methods: {
//
close() {
// inputpropsvalue
// vue
this.popupClose();
this.$emit('close');
},
//
popupClose() {
this.$emit('input', false);
},
// item
itemClick(index) {
// disabled
if(this.list[index].disabled) return;
this.$emit('click', index);
this.$emit('input', false);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-tips {
font-size: 26rpx;
text-align: center;
padding: 34rpx 0;
line-height: 1;
color: $u-tips-color;
}
.u-action-sheet-item {
@include vue-flex;;
line-height: 1;
justify-content: center;
align-items: center;
font-size: 32rpx;
padding: 34rpx 0;
flex-direction: column;
}
.u-action-sheet-item__subtext {
font-size: 24rpx;
color: $u-tips-color;
margin-top: 20rpx;
}
.u-gab {
height: 12rpx;
background-color: rgb(234, 234, 236);
}
.u-actionsheet-cancel {
color: $u-main-color;
}
</style>

View File

@ -0,0 +1,256 @@
<template>
<view class="u-alert-tips" v-if="show" :class="[
!show ? 'u-close-alert-tips': '',
type ? 'u-alert-tips--bg--' + type + '-light' : '',
type ? 'u-alert-tips--border--' + type + '-disabled' : '',
]" :style="{
backgroundColor: bgColor,
borderColor: borderColor
}">
<view class="u-icon-wrap">
<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
</view>
<view class="u-alert-content" @tap.stop="click">
<view class="u-alert-title" :style="[uTitleStyle]">
{{title}}
</view>
<view v-if="description" class="u-alert-desc" :style="[descStyle]">
{{description}}
</view>
</view>
<view class="u-icon-wrap">
<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
:size="22" class="u-close-icon" :style="{
top: description ? '18rpx' : '24rpx'
}"></u-icon>
</view>
<text v-if="closeAble && closeText" class="u-close-text" :style="{
top: description ? '18rpx' : '24rpx'
}">{{closeText}}</text>
</view>
</template>
<script>
/**
* alertTips 警告提示
* @description 警告提示展现需要关注的信息
* @tutorial https://uviewui.com/components/alertTips.html
* @property {String} title 显示的标题文字
* @property {String} description 辅助性文字颜色比title浅一点字号也小一点可选
* @property {String} type 关闭按钮(默认为叉号icon图标)
* @property {String} icon 图标名称
* @property {Object} icon-style 图标的样式对象形式
* @property {Object} title-style 标题的样式对象形式
* @property {Object} desc-style 描述的样式对象形式
* @property {String} close-able 用文字替代关闭图标close-able为true时有效
* @property {Boolean} show-icon 是否显示左边的辅助图标
* @property {Boolean} show 显示或隐藏组件
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
*/
export default {
name: 'u-alert-tips',
props: {
//
title: {
type: String,
default: ''
},
// success/warning/info/error
type: {
type: String,
default: 'warning'
},
//
description: {
type: String,
default: ''
},
//
closeAble: {
type: Boolean,
default: false
},
//
closeText: {
type: String,
default: ''
},
//
showIcon: {
type: Boolean,
default: false
},
// coloricon
color: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
show: {
type: Boolean,
default: true
},
// icon
icon: {
type: String,
default: ''
},
// icon
iconStyle: {
type: Object,
default() {
return {}
}
},
//
titleStyle: {
type: Object,
default() {
return {}
}
},
//
descStyle: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
}
},
computed: {
uTitleStyle() {
let style = {};
//
style.fontWeight = this.description ? 500 : 'normal';
// stylestyle
return this.$u.deepMerge(style, this.titleStyle);
},
uIcon() {
// icon使type
return this.icon ? this.icon : this.$u.type2icon(this.type);
},
uIconType() {
// 使type
return Object.keys(this.iconStyle).length ? '' : this.type;
}
},
methods: {
//
click() {
this.$emit('click');
},
//
close() {
this.$emit('close');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-alert-tips {
@include vue-flex;
align-items: center;
padding: 16rpx 30rpx;
border-radius: 8rpx;
position: relative;
transition: all 0.3s linear;
border: 1px solid #fff;
&--bg--primary-light {
background-color: $u-type-primary-light;
}
&--bg--info-light {
background-color: $u-type-info-light;
}
&--bg--success-light {
background-color: $u-type-success-light;
}
&--bg--warning-light {
background-color: $u-type-warning-light;
}
&--bg--error-light {
background-color: $u-type-error-light;
}
&--border--primary-disabled {
border-color: $u-type-primary-disabled;
}
&--border--success-disabled {
border-color: $u-type-success-disabled;
}
&--border--error-disabled {
border-color: $u-type-error-disabled;
}
&--border--warning-disabled {
border-color: $u-type-warning-disabled;
}
&--border--info-disabled {
border-color: $u-type-info-disabled;
}
}
.u-close-alert-tips {
opacity: 0;
visibility: hidden;
}
.u-icon {
margin-right: 16rpx;
}
.u-alert-title {
font-size: 28rpx;
color: $u-main-color;
}
.u-alert-desc {
font-size: 26rpx;
text-align: left;
color: $u-content-color;
}
.u-close-icon {
position: absolute;
top: 20rpx;
right: 20rpx;
}
.u-close-hover {
color: red;
}
.u-close-text {
font-size: 24rpx;
color: $u-tips-color;
position: absolute;
top: 20rpx;
right: 20rpx;
line-height: 1;
}
</style>

View File

@ -0,0 +1,290 @@
<template>
<view class="content">
<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
<canvas
class="cropper"
:disable-scroll="true"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
canvas-id="cropper"
id="cropper"
></canvas>
<canvas
class="cropper"
:disable-scroll="true"
:style="{
position: 'fixed',
top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
height: `${cropperOpt.height * cropperOpt.pixelRatio}`
}"
canvas-id="targetId"
id="targetId"
></canvas>
</view>
<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
<!-- #ifdef H5 -->
<view class="upload" @tap="uploadTap"></view>
<!-- #endif -->
<!-- #ifndef H5 -->
<view class="upload" @tap="uploadTap"></view>
<!-- #endif -->
<view class="getCropperImage" @tap="getCropperImage(false)"></view>
</view>
</view>
</template>
<script>
import WeCropper from './weCropper.js';
export default {
props: {
// lineWidth-(rpx)color:
// mask-rgba"rgba(0, 0, 0, 0.35)"
boundStyle: {
type: Object,
default() {
return {
lineWidth: 4,
borderColor: 'rgb(245, 245, 245)',
mask: 'rgba(0, 0, 0, 0.35)'
};
}
}
// // rpx
// rectWidth: {
// type: [String, Number],
// default: 400
// },
// // rpx
// rectHeight: {
// type: [String, Number],
// default: 400
// },
// // rpx
// destWidth: {
// type: [String, Number],
// default: 400
// },
// // rpx
// destHeight: {
// type: [String, Number],
// default: 400
// },
// // "png""jpg"
// fileType: {
// type: String,
// default: 'jpg',
// },
// //
// // H5使
// quality: {
// type: [Number, String],
// default: 1
// }
},
data() {
return {
//
bottomNavHeight: 50,
originWidth: 200,
width: 0,
height: 0,
cropperOpt: {
id: 'cropper',
targetId: 'targetCropper',
pixelRatio: 1,
width: 0,
height: 0,
scale: 2.5,
zoom: 8,
cut: {
x: (this.width - this.originWidth) / 2,
y: (this.height - this.originWidth) / 2,
width: this.originWidth,
height: this.originWidth
},
boundStyle: {
lineWidth: uni.upx2px(this.boundStyle.lineWidth),
mask: this.boundStyle.mask,
color: this.boundStyle.borderColor
}
},
//
// px
destWidth: 200,
// px
rectWidth: 200,
// 'png'"jpg"
fileType: 'jpg',
src: '', //
};
},
onLoad(option) {
let rectInfo = uni.getSystemInfoSync();
this.width = rectInfo.windowWidth;
this.height = rectInfo.windowHeight - this.bottomNavHeight;
this.cropperOpt.width = this.width;
this.cropperOpt.height = this.height;
this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
if (option.destWidth) this.destWidth = option.destWidth;
if (option.rectWidth) {
let rectWidth = Number(option.rectWidth);
this.cropperOpt.cut = {
x: (this.width - rectWidth) / 2,
y: (this.height - rectWidth) / 2,
width: rectWidth,
height: rectWidth
};
}
this.rectWidth = option.rectWidth;
if (option.fileType) this.fileType = option.fileType;
//
this.cropper = new WeCropper(this.cropperOpt)
.on('ready', ctx => {
// wecropper is ready for work!
})
.on('beforeImageLoad', ctx => {
// before picture loaded, i can do something
})
.on('imageLoad', ctx => {
// picture loaded
})
.on('beforeDraw', (ctx, instance) => {
// before canvas draw,i can do something
});
// page.json
uni.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: '#000000'
});
uni.chooseImage({
count: 1, // 9
sizeType: ['compressed'], //
sourceType: ['album', 'camera'], //
success: res => {
this.src = res.tempFilePaths[0];
// datasrc
this.cropper.pushOrign(this.src);
}
});
},
methods: {
touchStart(e) {
this.cropper.touchStart(e);
},
touchMove(e) {
this.cropper.touchMove(e);
},
touchEnd(e) {
this.cropper.touchEnd(e);
},
getCropperImage(isPre = false) {
if(!this.src) return this.$u.toast('请先选择图片再裁剪');
let cropper_opt = {
destHeight: Number(this.destWidth), // uni.canvasToTempFilePath
destWidth: Number(this.destWidth),
fileType: this.fileType
};
this.cropper.getCropperImage(cropper_opt, (path, err) => {
if (err) {
uni.showModal({
title: '温馨提示',
content: err.message
});
} else {
if (isPre) {
uni.previewImage({
current: '', // http
urls: [path] // http
});
} else {
uni.$emit('uAvatarCropper', path);
this.$u.route({
type: 'back'
});
}
}
});
},
uploadTap() {
const self = this;
uni.chooseImage({
count: 1, // 9
sizeType: ['original', 'compressed'], //
sourceType: ['album', 'camera'], //
success: (res) => {
self.src = res.tempFilePaths[0];
// datasrc
self.cropper.pushOrign(this.src);
}
});
}
}
};
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.content {
background: rgba(255, 255, 255, 1);
}
.cropper {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 11;
}
.cropper-buttons {
background-color: #000000;
color: #eee;
}
.cropper-wrapper {
position: relative;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
background-color: #000;
}
.cropper-buttons {
width: 100vw;
@include vue-flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
position: fixed;
bottom: 0;
left: 0;
font-size: 28rpx;
}
.cropper-buttons .upload,
.cropper-buttons .getCropperImage {
width: 50%;
text-align: center;
}
.cropper-buttons .upload {
text-align: left;
padding-left: 50rpx;
}
.cropper-buttons .getCropperImage {
text-align: right;
padding-right: 50rpx;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,244 @@
<template>
<view class="u-avatar" :style="[wrapStyle]" @tap="click">
<image
@error="loadError"
:style="[imgStyle]"
class="u-avatar__img"
v-if="!uText && avatar"
:src="avatar"
:mode="imgMode"
></image>
<text class="u-line-1" v-else-if="uText" :style="{
fontSize: '38rpx'
}">{{uText}}</text>
<slot v-else></slot>
<view class="u-avatar__sex" v-if="showSex" :class="['u-avatar__sex--' + sexIcon]" :style="[uSexStyle]">
<u-icon :name="sexIcon" size="20"></u-icon>
</view>
<view class="u-avatar__level" v-if="showLevel" :style="[uLevelStyle]">
<u-icon :name="levelIcon" size="20"></u-icon>
</view>
</view>
</template>
<script>
let base64Avatar = "data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z";
/**
* avatar 头像
* @description 本组件一般用于展示头像的地方如个人中心或者评论列表页的用户头像展示等场所
* @tutorial https://www.uviewui.com/components/avatar.html
* @property {String} bg-color 背景颜色一般显示文字时用默认#ffffff
* @property {String} src 头像路径如加载失败将会显示默认头像
* @property {String Number} size 头像尺寸可以为指定字符串(large, default, mini)或者数值单位rpx默认default
* @property {String} mode 显示类型见上方说明默认circle
* @property {String} sex-icon 性别图标man-woman-默认man
* @property {String} level-icon 等级图标默认level
* @property {String} sex-bg-color 性别图标背景颜色
* @property {String} level-bg-color 等级图标背景颜色
* @property {String} show-sex 是否显示性别图标默认false
* @property {String} show-level 是否显示等级图标默认false
* @property {String} img-mode 头像图片的裁剪类型与uni的image组件的mode参数一致如效果达不到需求可尝试传widthFix值默认aspectFill
* @property {String} index 用户传递的标识符值如果是列表循环可穿v-for的index值
* @event {Function} click 头像被点击
* @example <u-avatar :src="src"></u-avatar>
*/
export default {
name: 'u-avatar',
props: {
//
bgColor: {
type: String,
default: 'transparent'
},
//
src: {
type: String,
default: ''
},
// large-default-mini-rpx
//
size: {
type: [String, Number],
default: 'default'
},
// square-circle-
mode: {
type: String,
default: 'circle'
},
//
text: {
type: String,
default: ''
},
//
imgMode: {
type: String,
default: 'aspectFill'
},
//
index: {
type: [String, Number],
default: ''
},
// man-woman-
sexIcon: {
type: String,
default: 'man'
},
//
levelIcon: {
type: String,
default: 'level'
},
//
levelBgColor: {
type: String,
default: ''
},
//
sexBgColor: {
type: String,
default: ''
},
//
showSex: {
type: Boolean,
default: false
},
//
showLevel: {
type: Boolean,
default: false
}
},
data() {
return {
error: false,
// props
avatar: this.src ? this.src : base64Avatar,
}
},
watch: {
src(n) {
//
if(!n) {
// null''undefined
this.avatar = base64Avatar;
this.error = true;
} else {
this.avatar = n;
this.error = false;
}
}
},
computed: {
wrapStyle() {
let style = {};
style.height = this.size == 'large' ? '120rpx' : this.size == 'default' ?
'90rpx' : this.size == 'mini' ? '70rpx' : this.size + 'rpx';
style.width = style.height;
style.flex = `0 0 ${style.height}`;
style.backgroundColor = this.bgColor;
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
if(this.text) style.padding = `0 6rpx`;
return style;
},
imgStyle() {
let style = {};
style.borderRadius = this.mode == 'circle' ? '500px' : '5px';
return style;
},
//
uText() {
return String(this.text)[0];
},
//
uSexStyle() {
let style = {};
if(this.sexBgColor) style.backgroundColor = this.sexBgColor;
return style;
},
//
uLevelStyle() {
let style = {};
if(this.levelBgColor) style.backgroundColor = this.levelBgColor;
return style;
}
},
methods: {
//
loadError() {
this.error = true;
this.avatar = base64Avatar;
},
click() {
this.$emit('click', this.index);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-avatar {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
justify-content: center;
font-size: 28rpx;
color: $u-content-color;
border-radius: 10px;
position: relative;
&__img {
width: 100%;
height: 100%;
}
&__sex {
position: absolute;
width: 32rpx;
color: #ffffff;
height: 32rpx;
@include vue-flex;
justify-content: center;
align-items: center;
border-radius: 100rpx;
top: 5%;
z-index: 1;
right: -7%;
border: 1px #ffffff solid;
&--man {
background-color: $u-type-primary;
}
&--woman {
background-color: $u-type-error;
}
&--none {
background-color: $u-type-warning;
}
}
&__level {
position: absolute;
width: 32rpx;
color: #ffffff;
height: 32rpx;
@include vue-flex;
justify-content: center;
align-items: center;
border-radius: 100rpx;
bottom: 5%;
z-index: 1;
right: -7%;
border: 1px #ffffff solid;
background-color: $u-type-warning;
}
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
bottom: bottom + 'rpx',
right: right + 'rpx',
borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
zIndex: uZIndex,
opacity: opacity
}, customStyle]">
<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
<view class="u-back-top__content__tips">
{{tips}}
</view>
</view>
<slot v-else />
</view>
</template>
<script>
export default {
name: 'u-back-top',
props: {
// circle-square-
mode: {
type: String,
default: 'circle'
},
//
icon: {
type: String,
default: 'arrow-upward'
},
//
tips: {
type: String,
default: ''
},
//
duration: {
type: [Number, String],
default: 100
},
//
scrollTop: {
type: [Number, String],
default: 0
},
// rpx
top: {
type: [Number, String],
default: 400
},
// rpx
bottom: {
type: [Number, String],
default: 200
},
// rpx
right: {
type: [Number, String],
default: 40
},
//
zIndex: {
type: [Number, String],
default: '9'
},
//
iconStyle: {
type: Object,
default() {
return {
color: '#909399',
fontSize: '38rpx'
}
}
},
//
customStyle: {
type: Object,
default() {
return {}
}
}
},
watch: {
showBackTop(nVal, oVal) {
//
// v-if
if(nVal) {
this.uZIndex = this.zIndex;
this.opacity = 1;
} else {
this.uZIndex = -1;
this.opacity = 0;
}
}
},
computed: {
showBackTop() {
// scrollToppxtop(rpx)
// px
return this.scrollTop > uni.upx2px(this.top);
},
},
data() {
return {
//
opacity: 0,
// z-index-1
uZIndex: -1
}
},
methods: {
backToTop() {
uni.pageScrollTo({
scrollTop: 0,
duration: this.duration
});
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-back-top {
width: 80rpx;
height: 80rpx;
position: fixed;
z-index: 9;
@include vue-flex;
flex-direction: column;
justify-content: center;
background-color: #E1E1E1;
color: $u-content-color;
align-items: center;
transition: opacity 0.4s;
&__content {
@include vue-flex;
flex-direction: column;
align-items: center;
&__tips {
font-size: 24rpx;
transform: scale(0.8);
line-height: 1;
}
}
}
</style>

View File

@ -0,0 +1,216 @@
<template>
<view v-if="show" class="u-badge" :class="[
isDot ? 'u-badge-dot' : '',
size == 'mini' ? 'u-badge-mini' : '',
type ? 'u-badge--bg--' + type : ''
]" :style="[{
top: offset[0] + 'rpx',
right: offset[1] + 'rpx',
fontSize: fontSize + 'rpx',
position: absolute ? 'absolute' : 'static',
color: color,
backgroundColor: bgColor
}, boxStyle]"
>
{{showText}}
</view>
</template>
<script>
/**
* badge 角标
* @description 本组件一般用于展示头像的地方如个人中心或者评论列表页的用户头像展示等场所
* @tutorial https://www.uviewui.com/components/badge.html
* @property {String Number} count 展示的数字大于 overflowCount 时显示为 ${overflowCount}+为0且show-zero为false时隐藏
* @property {Boolean} is-dot 不展示数字只有一个小点默认false
* @property {Boolean} absolute 组件是否绝对定位为true时offset参数才有效默认true
* @property {String Number} overflow-count 展示封顶的数字值默认99
* @property {String} type 使用预设的背景颜色默认error
* @property {Boolean} show-zero 当数值为 0 是否展示 Badge默认false
* @property {String} size Badge的尺寸设为mini会得到小一号的Badge默认default
* @property {Array} offset 设置badge的位置偏移格式为 [x, y]也即设置的为top和right的值单位rpxabsolute为true时有效默认[20, 20]
* @property {String} color 字体颜色默认#ffffff
* @property {String} bgColor 背景颜色优先级比type高如设置type参数会失效
* @property {Boolean} is-center 组件中心点是否和父组件右上角重合优先级比offset高如设置offset参数会失效默认false
* @example <u-badge type="error" count="7"></u-badge>
*/
export default {
name: 'u-badge',
props: {
// primary,warning,success,error,info
type: {
type: String,
default: 'error'
},
// default, mini
size: {
type: String,
default: 'default'
},
//
isDot: {
type: Boolean,
default: false
},
//
count: {
type: [Number, String],
},
//
overflowCount: {
type: Number,
default: 99
},
// 0 Badge
showZero: {
type: Boolean,
default: false
},
//
offset: {
type: Array,
default: () => {
return [20, 20]
}
},
// offset
absolute: {
type: Boolean,
default: true
},
//
fontSize: {
type: [String, Number],
default: '24'
},
//
color: {
type: String,
default: '#ffffff'
},
// badge
bgColor: {
type: String,
default: ''
},
// badgeoffset
isCenter: {
type: Boolean,
default: false
}
},
computed: {
// badge
boxStyle() {
let style = {};
if(this.isCenter) {
style.top = 0;
style.right = 0;
// Y-50%badgebadgeX50%
style.transform = "translateY(-50%) translateX(50%)";
} else {
style.top = this.offset[0] + 'rpx';
style.right = this.offset[1] + 'rpx';
style.transform = "translateY(0) translateX(0)";
}
// miniscal()
if(this.size == 'mini') {
style.transform = style.transform + " scale(0.8)";
}
return style;
},
// isDot
showText() {
if(this.isDot) return '';
else {
if(this.count > this.overflowCount) return `${this.overflowCount}+`;
else return this.count;
}
},
//
show() {
// count0showZerofalse
if(this.count == 0 && this.showZero == false) return false;
else return true;
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-badge {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
justify-content: center;
align-items: center;
line-height: 24rpx;
padding: 4rpx 8rpx;
border-radius: 100rpx;
z-index: 9;
&--bg--primary {
background-color: $u-type-primary;
}
&--bg--error {
background-color: $u-type-error;
}
&--bg--success {
background-color: $u-type-success;
}
&--bg--info {
background-color: $u-type-info;
}
&--bg--warning {
background-color: $u-type-warning;
}
}
.u-badge-dot {
height: 16rpx;
width: 16rpx;
border-radius: 100rpx;
line-height: 1;
}
.u-badge-mini {
transform: scale(0.8);
transform-origin: center center;
}
// .u-primary {
// background: $u-type-primary;
// color: #fff;
// }
// .u-error {
// background: $u-type-error;
// color: #fff;
// }
// .u-warning {
// background: $u-type-warning;
// color: #fff;
// }
// .u-success {
// background: $u-type-success;
// color: #fff;
// }
// .u-black {
// background: #585858;
// color: #fff;
// }
.u-info {
background-color: $u-type-info;
color: #fff;
}
</style>

View File

@ -0,0 +1,596 @@
<template>
<button
id="u-wave-btn"
class="u-btn u-line-1 u-fix-ios-appearance"
:class="[
'u-size-' + size,
plain ? 'u-btn--' + type + '--plain' : '',
loading ? 'u-loading' : '',
shape == 'circle' ? 'u-round-circle' : '',
hairLine ? showHairLineBorder : 'u-btn--bold-border',
'u-btn--' + type,
disabled ? `u-btn--${type}--disabled` : '',
]"
:hover-start-time="Number(hoverStartTime)"
:hover-stay-time="Number(hoverStayTime)"
:disabled="disabled"
:form-type="formType"
:open-type="openType"
:app-parameter="appParameter"
:hover-stop-propagation="hoverStopPropagation"
:send-message-title="sendMessageTitle"
send-message-path="sendMessagePath"
:lang="lang"
:data-name="dataName"
:session-from="sessionFrom"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
@getphonenumber="getphonenumber"
@getuserinfo="getuserinfo"
@error="error"
@opensetting="opensetting"
@launchapp="launchapp"
:style="[customStyle, {
overflow: ripple ? 'hidden' : 'visible'
}]"
@tap.stop="click($event)"
:hover-class="getHoverClass"
:loading="loading"
>
<slot></slot>
<view
v-if="ripple"
class="u-wave-ripple"
:class="[waveActive ? 'u-wave-active' : '']"
:style="{
top: rippleTop + 'px',
left: rippleLeft + 'px',
width: fields.targetWidth + 'px',
height: fields.targetWidth + 'px',
'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)'
}"
></view>
</button>
</template>
<script>
/**
* button 按钮
* @description Button 按钮
* @tutorial https://www.uviewui.com/components/button.html
* @property {String} size 按钮的大小
* @property {Boolean} ripple 是否开启点击水波纹效果
* @property {String} ripple-bg-color 水波纹的背景色ripple为true时有效
* @property {String} type 按钮的样式类型
* @property {Boolean} plain 按钮是否镂空背景色透明
* @property {Boolean} disabled 是否禁用
* @property {Boolean} hair-line 是否显示按钮的细边框(默认true)
* @property {Boolean} shape 按钮外观形状见文档说明
* @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台 ios 上为雪花Android上为圆圈)
* @property {String} form-type 用于 <form> 组件点击分别会触发 <form> 组件的 submit/reset 事件
* @property {String} open-type 开放能力
* @property {String} data-name 额外传参参数用于小程序的data-xxx属性通过target.dataset.name获取
* @property {String} hover-class 指定按钮按下去的样式类 hover-class="none" 没有点击态效果(App-nvue 平台暂不支持)
* @property {Number} hover-start-time 按住后多久出现点击态单位毫秒
* @property {Number} hover-stay-time 手指松开后点击态保留时间单位毫秒
* @property {Object} custom-style 对按钮的自定义样式对象形式见文档说明
* @event {Function} click 按钮点击
* @event {Function} getphonenumber open-type="getPhoneNumber"时有效
* @event {Function} getuserinfo 用户点击该按钮时会返回获取到的用户信息从返回参数的detail中获取到的值同uni.getUserInfo
* @event {Function} error 当使用开放能力时发生错误的回调
* @event {Function} opensetting 在打开授权设置页并关闭后回调
* @event {Function} launchapp 打开 APP 成功的回调
* @example <u-button>月落</u-button>
*/
export default {
name: 'u-button',
props: {
//
hairLine: {
type: Boolean,
default: true
},
// defaultprimaryerrorwarningsuccess
type: {
type: String,
default: 'default'
},
// defaultmediummini
size: {
type: String,
default: 'default'
},
// circlesquare
shape: {
type: String,
default: 'square'
},
//
plain: {
type: Boolean,
default: false
},
//
disabled: {
type: Boolean,
default: false
},
//
loading: {
type: Boolean,
default: false
},
// uniappbutton
// https://uniapp.dcloud.io/component/button
openType: {
type: String,
default: ''
},
// <form> <form> submit/reset
// submitreset
formType: {
type: String,
default: ''
},
// APP APP open-type=launchApp
// QQ
appParameter: {
type: String,
default: ''
},
//
hoverStopPropagation: {
type: Boolean,
default: false
},
// zh_CN zh_TW en
lang: {
type: String,
default: 'en'
},
// open-type="contact"
sessionFrom: {
type: String,
default: ''
},
// open-type="contact"
//
sendMessageTitle: {
type: String,
default: ''
},
// open-type="contact"
//
sendMessagePath: {
type: String,
default: ''
},
// open-type="contact"
//
sendMessageImg: {
type: String,
default: ''
},
// true""
// open-type="contact"
showMessageCard: {
type: Boolean,
default: false
},
//
hoverBgColor: {
type: String,
default: ''
},
//
rippleBgColor: {
type: String,
default: ''
},
//
ripple: {
type: Boolean,
default: false
},
//
hoverClass: {
type: String,
default: ''
},
//
customStyle: {
type: Object,
default() {
return {};
}
},
// data-xxxtarget.dataset.name
dataName: {
type: String,
default: ''
},
//
throttleTime: {
type: [String, Number],
default: 1000
},
//
hoverStartTime: {
type: [String, Number],
default: 20
},
//
hoverStayTime: {
type: [String, Number],
default: 150
},
},
computed: {
// bgColor
getHoverClass() {
// hover-class
if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
let hoverClass = '';
hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
return hoverClass;
},
// 'primary', 'success', 'error', 'warning'
showHairLineBorder() {
if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
return '';
} else {
return 'u-hairline-border';
}
}
},
data() {
return {
rippleTop: 0, // Y
rippleLeft: 0, // X
fields: {}, //
waveActive: false //
};
},
methods: {
//
click(e) {
// this.throttle
this.$u.throttle(() => {
// disabledloading
if (this.loading === true || this.disabled === true) return;
//
if (this.ripple) {
//
this.waveActive = false;
this.$nextTick(function() {
this.getWaveQuery(e);
});
}
this.$emit('click', e);
}, this.throttleTime);
},
//
getWaveQuery(e) {
this.getElQuery().then(res => {
//
let data = res[0];
//
if (!data.width || !data.width) return;
// (border-radius)
//
data.targetWidth = data.height > data.width ? data.height : data.width;
if (!data.targetWidth) return;
this.fields = data;
let touchesX = '',
touchesY = '';
// #ifdef MP-BAIDU
touchesX = e.changedTouches[0].clientX;
touchesY = e.changedTouches[0].clientY;
// #endif
// #ifdef MP-ALIPAY
touchesX = e.detail.clientX;
touchesY = e.detail.clientY;
// #endif
// #ifndef MP-BAIDU || MP-ALIPAY
touchesX = e.touches[0].clientX;
touchesY = e.touches[0].clientY;
// #endif
// xytouchesYdata.top
// `transform-origin`centerview
//
this.rippleTop = touchesY - data.top - data.targetWidth / 2;
this.rippleLeft = touchesX - data.left - data.targetWidth / 2;
this.$nextTick(() => {
this.waveActive = true;
});
});
},
//
getElQuery() {
return new Promise(resolve => {
let queryInfo = '';
// uniapp
// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
queryInfo = uni.createSelectorQuery().in(this);
//#ifdef MP-ALIPAY
queryInfo = uni.createSelectorQuery();
//#endif
queryInfo.select('.u-btn').boundingClientRect();
queryInfo.exec(data => {
resolve(data);
});
});
},
// uniapp
getphonenumber(res) {
this.$emit('getphonenumber', res);
},
getuserinfo(res) {
this.$emit('getuserinfo', res);
},
error(res) {
this.$emit('error', res);
},
opensetting(res) {
this.$emit('opensetting', res);
},
launchapp(res) {
this.$emit('launchapp', res);
}
}
};
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.u-btn::after {
border: none;
}
.u-btn {
position: relative;
border: 0;
//border-radius: 10rpx;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
// hidden
overflow: visible;
line-height: 1;
@include vue-flex;
align-items: center;
justify-content: center;
cursor: pointer;
padding: 0 40rpx;
z-index: 1;
box-sizing: border-box;
transition: all 0.15s;
&--bold-border {
border: 1px solid #ffffff;
}
&--default {
color: $u-content-color;
border-color: #c0c4cc;
background-color: #ffffff;
}
&--primary {
color: #ffffff;
border-color: $u-type-primary;
background-color: $u-type-primary;
}
&--success {
color: #ffffff;
border-color: $u-type-success;
background-color: $u-type-success;
}
&--error {
color: #ffffff;
border-color: $u-type-error;
background-color: $u-type-error;
}
&--warning {
color: #ffffff;
border-color: $u-type-warning;
background-color: $u-type-warning;
}
&--default--disabled {
color: #ffffff;
border-color: #e4e7ed;
background-color: #ffffff;
}
&--primary--disabled {
color: #ffffff!important;
border-color: $u-type-primary-disabled!important;
background-color: $u-type-primary-disabled!important;
}
&--success--disabled {
color: #ffffff!important;
border-color: $u-type-success-disabled!important;
background-color: $u-type-success-disabled!important;
}
&--error--disabled {
color: #ffffff!important;
border-color: $u-type-error-disabled!important;
background-color: $u-type-error-disabled!important;
}
&--warning--disabled {
color: #ffffff!important;
border-color: $u-type-warning-disabled!important;
background-color: $u-type-warning-disabled!important;
}
&--primary--plain {
color: $u-type-primary!important;
border-color: $u-type-primary-disabled!important;
background-color: $u-type-primary-light!important;
}
&--success--plain {
color: $u-type-success!important;
border-color: $u-type-success-disabled!important;
background-color: $u-type-success-light!important;
}
&--error--plain {
color: $u-type-error!important;
border-color: $u-type-error-disabled!important;
background-color: $u-type-error-light!important;
}
&--warning--plain {
color: $u-type-warning!important;
border-color: $u-type-warning-disabled!important;
background-color: $u-type-warning-light!important;
}
}
.u-hairline-border:after {
content: ' ';
position: absolute;
pointer-events: none;
// border-boxscale0.5border-boxborder
box-sizing: border-box;
// (scale())
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
left: 0;
top: 0;
width: 199.8%;
height: 199.7%;
-webkit-transform: scale(0.5, 0.5);
transform: scale(0.5, 0.5);
border: 1px solid currentColor;
z-index: 1;
}
.u-wave-ripple {
z-index: 0;
position: absolute;
border-radius: 100%;
background-clip: padding-box;
pointer-events: none;
user-select: none;
transform: scale(0);
opacity: 1;
transform-origin: center;
}
.u-wave-ripple.u-wave-active {
opacity: 0;
transform: scale(2);
transition: opacity 1s linear, transform 0.4s linear;
}
.u-round-circle {
border-radius: 100rpx;
}
.u-round-circle::after {
border-radius: 100rpx;
}
.u-loading::after {
background-color: hsla(0, 0%, 100%, 0.35);
}
.u-size-default {
font-size: 30rpx;
height: 80rpx;
line-height: 80rpx;
}
.u-size-medium {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
width: auto;
font-size: 26rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 80rpx;
}
.u-size-mini {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
width: auto;
font-size: 22rpx;
padding-top: 1px;
height: 50rpx;
line-height: 50rpx;
padding: 0 20rpx;
}
.u-primary-plain-hover {
color: #ffffff !important;
background: $u-type-primary-dark !important;
}
.u-default-plain-hover {
color: $u-type-primary-dark !important;
background: $u-type-primary-light !important;
}
.u-success-plain-hover {
color: #ffffff !important;
background: $u-type-success-dark !important;
}
.u-warning-plain-hover {
color: #ffffff !important;
background: $u-type-warning-dark !important;
}
.u-error-plain-hover {
color: #ffffff !important;
background: $u-type-error-dark !important;
}
.u-info-plain-hover {
color: #ffffff !important;
background: $u-type-info-dark !important;
}
.u-default-hover {
color: $u-type-primary-dark !important;
border-color: $u-type-primary-dark !important;
background-color: $u-type-primary-light !important;
}
.u-primary-hover {
background: $u-type-primary-dark !important;
color: #fff;
}
.u-success-hover {
background: $u-type-success-dark !important;
color: #fff;
}
.u-info-hover {
background: $u-type-info-dark !important;
color: #fff;
}
.u-warning-hover {
background: $u-type-warning-dark !important;
color: #fff;
}
.u-error-hover {
background: $u-type-error-dark !important;
color: #fff;
}
</style>

View File

@ -0,0 +1,643 @@
<template>
<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
:safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
<view class="u-calendar">
<view class="u-calendar__header">
<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
{{toolTip}}
</view>
<slot v-else name="tooltip" />
</view>
<view class="u-calendar__action u-flex u-row-center">
<view class="u-calendar__action__icon">
<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
</view>
<view class="u-calendar__action__icon">
<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
</view>
<view class="u-calendar__action__text">{{ showTitle }}</view>
<view class="u-calendar__action__icon">
<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
</view>
<view class="u-calendar__action__icon">
<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
</view>
</view>
<view class="u-calendar__week-day">
<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
</view>
<view class="u-calendar__content">
<!-- 前置空白部分 -->
<block v-for="(item, index) in weekdayArr" :key="index">
<view class="u-calendar__content__item"></view>
</block>
<view class="u-calendar__content__item" :class="{
'u-hover-class':openDisAbled(year,month,index+1),
'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
@tap="dateClick(index)">
<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
<view>{{ index + 1 }}</view>
</view>
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
</view>
<view class="u-calendar__content__bg-month">{{month}}</view>
</view>
<view class="u-calendar__bottom">
<view class="u-calendar__bottom__choose">
<text>{{mode == 'date' ? activeDate : startDate}}</text>
<text v-if="endDate">{{endDate}}</text>
</view>
<view class="u-calendar__bottom__btn">
<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)"></u-button>
</view>
</view>
</view>
</u-popup>
</template>
<script>
/**
* calendar 日历
* @description 此组件用于单个选择日期范围选择日期等日历被包裹在底部弹起的容器中
* @tutorial http://uviewui.com/components/calendar.html
* @property {String} mode 选择日期的模式date-为单个日期range-为选择日期范围
* @property {Boolean} v-model 布尔值变量用于控制日历的弹出与收起
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
* @property {String Number} max-year 可切换的最大年份(默认2050)
* @property {String Number} min-year 可切换的最小年份(默认1950)
* @property {String Number} min-date 最小可选日期(默认1950-01-01)
* @property {String Number} max-date 最大可选日期(默认当前日期)
* @property {String Number} 弹窗顶部左右两边的圆角值单位rpx(默认20)
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
* @property {String} color 日期字体的默认颜色(默认#303133)
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
* @property {String Number} z-index 弹出时的z-index值(默认10075)
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
* @property {String} range-color 选择范围内字体颜色(默认#2979ff)
* @property {String} start-text 起始日期底部的提示文字(默认 '开始')
* @property {String} end-text 结束日期底部的提示文字(默认 '结束')
* @property {String} btn-type 底部确定按钮的主题(默认 'primary')
* @property {String} toolTip 顶部提示文字如设置名为tooltip的slot此参数将失效(默认 '选择日期')
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
* @example <u-calendar v-model="show" :mode="mode"></u-calendar>
*/
export default {
name: 'u-calendar',
props: {
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
//
value: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: [String, Number],
default: 0
},
//
changeYear: {
type: Boolean,
default: true
},
//
changeMonth: {
type: Boolean,
default: true
},
// date-range-+
mode: {
type: String,
default: 'date'
},
//
maxYear: {
type: [Number, String],
default: 2050
},
//
minYear: {
type: [Number, String],
default: 1950
},
// ()
minDate: {
type: [Number, String],
default: '1950-01-01'
},
/**
* 最大可选日期
* 默认最大值为今天之后的日期不可选
* 2030-12-31
* */
maxDate: {
type: [Number, String],
default: ''
},
//
borderRadius: {
type: [String, Number],
default: 20
},
//
monthArrowColor: {
type: String,
default: '#606266'
},
//
yearArrowColor: {
type: String,
default: '#909399'
},
//
color: {
type: String,
default: '#303133'
},
// |
activeBgColor: {
type: String,
default: '#2979ff'
},
// |
activeColor: {
type: String,
default: '#ffffff'
},
//
rangeBgColor: {
type: String,
default: 'rgba(41,121,255,0.13)'
},
//
rangeColor: {
type: String,
default: '#2979ff'
},
// mode=range
startText: {
type: String,
default: '开始'
},
// mode=range
endText: {
type: String,
default: '结束'
},
//
btnType: {
type: String,
default: 'primary'
},
//
isActiveCurrent: {
type: Boolean,
default: true
},
// mode=date
isChange: {
type: Boolean,
default: false
},
//
closeable: {
type: Boolean,
default: true
},
//
toolTip: {
type: String,
default: '选择日期'
}
},
data() {
return {
// ,1-7
weekday: 1,
weekdayArr:[],
//
days: 0,
daysArr:[],
showTitle: '',
year: 2020,
month: 0,
day: 0,
startYear: 0,
startMonth: 0,
startDay: 0,
endYear: 0,
endMonth: 0,
endDay: 0,
today: '',
activeDate: '',
startDate: '',
endDate: '',
isStart: true,
min: null,
max: null,
weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
};
},
computed: {
dataChange() {
return `${this.mode}-${this.minDate}-${this.maxDate}`;
},
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
watch: {
dataChange(val) {
this.init()
}
},
created() {
this.init()
},
methods: {
getColor(index, type) {
let color = type == 1 ? '' : this.color;
let day = index + 1
let date = `${this.year}-${this.month}-${day}`
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
let start = this.startDate.replace(/\-/g, '/')
let end = this.endDate.replace(/\-/g, '/')
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
color = type == 1 ? this.activeBgColor : this.activeColor;
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
color = type == 1 ? this.rangeBgColor : this.rangeColor;
}
return color;
},
init() {
let now = new Date();
let minDate = new Date(this.minDate);
let maxDate = new Date(this.maxDate);
if (now < minDate) now = minDate;
if (now > maxDate) now = maxDate;
this.year = now.getFullYear();
this.month = now.getMonth() + 1;
this.day = now.getDate();
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
this.activeDate = this.today;
this.min = this.initDate(this.minDate);
this.max = this.initDate(this.maxDate || this.today);
this.startDate = "";
this.startYear = 0;
this.startMonth = 0;
this.startDay = 0;
this.endYear = 0;
this.endMonth = 0;
this.endDay = 0;
this.endDate = "";
this.isStart = true;
this.changeData();
},
//
initDate(date) {
let fdate = date.split('-');
return {
year: Number(fdate[0] || 1920),
month: Number(fdate[1] || 1),
day: Number(fdate[2] || 1)
}
},
openDisAbled: function(year, month, day) {
let bool = true;
let date = `${year}/${month}/${day}`;
// let today = this.today.replace(/\-/g, '/');
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
let timestamp = new Date(date).getTime();
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
bool = false;
}
return bool;
},
generateArray: function(start, end) {
return Array.from(new Array(end + 1).keys()).slice(start);
},
formatNum: function(num) {
return num < 10 ? '0' + num : num + '';
},
//
getMonthDay(year, month) {
let days = new Date(year, month, 0).getDate();
return days;
},
getWeekday(year, month) {
let date = new Date(`${year}/${month}/01 00:00:00`);
return date.getDay();
},
checkRange(year) {
let overstep = false;
if (year < this.minYear || year > this.maxYear) {
uni.showToast({
title: "日期超出范围啦~",
icon: 'none'
})
overstep = true;
}
return overstep;
},
changeMonthHandler(isAdd) {
if (isAdd) {
let month = this.month + 1;
let year = month > 12 ? this.year + 1 : this.year;
if (!this.checkRange(year)) {
this.month = month > 12 ? 1 : month;
this.year = year;
this.changeData();
}
} else {
let month = this.month - 1;
let year = month < 1 ? this.year - 1 : this.year;
if (!this.checkRange(year)) {
this.month = month < 1 ? 12 : month;
this.year = year;
this.changeData();
}
}
},
changeYearHandler(isAdd) {
let year = isAdd ? this.year + 1 : this.year - 1;
if (!this.checkRange(year)) {
this.year = year;
this.changeData();
}
},
changeData() {
this.days = this.getMonthDay(this.year, this.month);
this.daysArr=this.generateArray(1,this.days)
this.weekday = this.getWeekday(this.year, this.month);
this.weekdayArr=this.generateArray(1,this.weekday)
this.showTitle = `${this.year}${this.month}`;
if (this.isChange && this.mode == 'date') {
this.btnFix(true);
}
},
dateClick: function(day) {
day += 1;
if (!this.openDisAbled(this.year, this.month, day)) {
this.day = day;
let date = `${this.year}-${this.month}-${day}`;
if (this.mode == 'date') {
this.activeDate = date;
} else {
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
if (this.isStart || compare) {
this.startDate = date;
this.startYear = this.year;
this.startMonth = this.month;
this.startDay = this.day;
this.endYear = 0;
this.endMonth = 0;
this.endDay = 0;
this.endDate = "";
this.activeDate = "";
this.isStart = false;
} else {
this.endDate = date;
this.endYear = this.year;
this.endMonth = this.month;
this.endDay = this.day;
this.isStart = true;
}
}
}
},
close() {
// v-modelfalse
this.$emit('input', false);
},
getWeekText(date) {
date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
let week = date.getDay();
return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
},
btnFix(show) {
if (!show) {
this.close();
}
if (this.mode == 'date') {
let arr = this.activeDate.split('-')
let year = this.isChange ? this.year : Number(arr[0]);
let month = this.isChange ? this.month : Number(arr[1]);
let day = this.isChange ? this.day : Number(arr[2]);
//
let days = this.getMonthDay(year, month);
let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
let weekText = this.getWeekText(result);
let isToday = false;
if (`${year}-${month}-${day}` == this.today) {
//
isToday = true;
}
this.$emit('change', {
year: year,
month: month,
day: day,
days: days,
result: result,
week: weekText,
isToday: isToday,
// switch: show //
});
} else {
if (!this.startDate || !this.endDate) return;
let startMonth = this.formatNum(this.startMonth);
let startDay = this.formatNum(this.startDay);
let startDate = `${this.startYear}-${startMonth}-${startDay}`;
let startWeek = this.getWeekText(startDate)
let endMonth = this.formatNum(this.endMonth);
let endDay = this.formatNum(this.endDay);
let endDate = `${this.endYear}-${endMonth}-${endDay}`;
let endWeek = this.getWeekText(endDate);
this.$emit('change', {
startYear: this.startYear,
startMonth: this.startMonth,
startDay: this.startDay,
startDate: startDate,
startWeek: startWeek,
endYear: this.endYear,
endMonth: this.endMonth,
endDay: this.endDay,
endDate: endDate,
endWeek: endWeek
});
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-calendar {
color: $u-content-color;
&__header {
width: 100%;
box-sizing: border-box;
font-size: 30rpx;
background-color: #fff;
color: $u-main-color;
&__text {
margin-top: 30rpx;
padding: 0 60rpx;
@include vue-flex;
justify-content: center;
align-items: center;
}
}
&__action {
padding: 40rpx 0 40rpx 0;
&__icon {
margin: 0 16rpx;
}
&__text {
padding: 0 16rpx;
color: $u-main-color;
font-size: 32rpx;
line-height: 32rpx;
font-weight: bold;
}
}
&__week-day {
@include vue-flex;
align-items: center;
justify-content: center;
padding: 6px 0;
overflow: hidden;
&__text {
flex: 1;
text-align: center;
}
}
&__content {
width: 100%;
@include vue-flex;
flex-wrap: wrap;
padding: 6px 0;
box-sizing: border-box;
background-color: #fff;
position: relative;
&--end-date {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
&--start-date {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
&__item {
width: 14.2857%;
@include vue-flex;
align-items: center;
justify-content: center;
padding: 6px 0;
overflow: hidden;
position: relative;
z-index: 2;
&__inner {
height: 84rpx;
@include vue-flex;
align-items: center;
justify-content: center;
flex-direction: column;
font-size: 32rpx;
position: relative;
border-radius: 50%;
&__desc {
width: 100%;
font-size: 24rpx;
line-height: 24rpx;
transform: scale(0.75);
transform-origin: center center;
position: absolute;
left: 0;
text-align: center;
bottom: 2rpx;
}
}
&__tips {
width: 100%;
font-size: 24rpx;
line-height: 24rpx;
position: absolute;
left: 0;
transform: scale(0.8);
transform-origin: center center;
text-align: center;
bottom: 8rpx;
z-index: 2;
}
}
&__bg-month {
position: absolute;
font-size: 130px;
line-height: 130px;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: #e4e7ed;
z-index: 1;
}
}
&__bottom {
width: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: #fff;
padding: 0 40rpx 30rpx;
box-sizing: border-box;
font-size: 24rpx;
color: $u-tips-color;
&__choose {
height: 50rpx;
}
&__btn {
width: 100%;
}
}
}
</style>

View File

@ -0,0 +1,257 @@
<template>
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
<view class="u-keyboard-grids">
<block>
<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
v-for="(item, j) in group" :key="j">
{{ item }}
</view>
</view>
<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
hover-class="u-hover-class">
<u-icon :size="38" name="backspace" :bold="true"></u-icon>
</view>
<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
<text class="zh" :class="[!abc ? 'active' : 'inactive']"></text>
/
<text class="en" :class="[abc ? 'active' : 'inactive']"></text>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
name: "u-keyboard",
props: {
//
random: {
type: Boolean,
default: false
}
},
data() {
return {
// abc=truebac=false
abc: false
};
},
computed: {
areaList() {
let data = [
'京',
'沪',
'粤',
'津',
'冀',
'豫',
'云',
'辽',
'黑',
'湘',
'皖',
'鲁',
'苏',
'浙',
'赣',
'鄂',
'桂',
'甘',
'晋',
'陕',
'蒙',
'吉',
'闽',
'贵',
'渝',
'川',
'青',
'琼',
'宁',
'挂',
'藏',
'港',
'澳',
'新',
'使',
'学'
];
let tmp = [];
//
if (this.random) data = this.$u.randomArray(data);
//
tmp[0] = data.slice(0, 10);
tmp[1] = data.slice(10, 20);
tmp[2] = data.slice(20, 30);
tmp[3] = data.slice(30, 36);
return tmp;
},
EngKeyBoardList() {
let data = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
0,
'Q',
'W',
'E',
'R',
'T',
'Y',
'U',
'I',
'O',
'P',
'A',
'S',
'D',
'F',
'G',
'H',
'J',
'K',
'L',
'Z',
'X',
'C',
'V',
'B',
'N',
'M'
];
let tmp = [];
if (this.random) data = this.$u.randomArray(data);
tmp[0] = data.slice(0, 10);
tmp[1] = data.slice(10, 20);
tmp[2] = data.slice(20, 30);
tmp[3] = data.slice(30, 36);
return tmp;
}
},
methods: {
//
carInputClick(i, j) {
let value = '';
//
if (this.abc) value = this.EngKeyBoardList[i][j];
else value = this.areaList[i][j];
this.$emit('change', value);
},
// |
changeCarInputMode() {
this.abc = !this.abc;
},
// 退
backspaceClick() {
this.$emit('backspace');
clearInterval(this.timer); //
this.timer = null;
this.timer = setInterval(() => {
this.$emit('backspace');
}, 250);
},
clearTimer() {
clearInterval(this.timer);
this.timer = null;
},
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-keyboard-grids {
background: rgb(215, 215, 217);
padding: 24rpx 0;
position: relative;
}
.u-keyboard-grids-item {
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-keyboard-grids-btn {
text-decoration: none;
width: 62rpx;
flex: 0 0 64rpx;
height: 80rpx;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
font-size: 36rpx;
text-align: center;
line-height: 80rpx;
background-color: #fff;
margin: 8rpx 5rpx;
border-radius: 8rpx;
box-shadow: 0 2rpx 0rpx #888992;
font-weight: 500;
justify-content: center;
}
.u-carinput-hover {
background-color: rgb(185, 188, 195) !important;
}
.u-keyboard-back {
position: absolute;
width: 96rpx;
right: 22rpx;
bottom: 32rpx;
height: 80rpx;
background-color: rgb(185, 188, 195);
@include vue-flex;
align-items: center;
border-radius: 8rpx;
justify-content: center;
box-shadow: 0 2rpx 0rpx #888992;
}
.u-keyboard-change {
font-size: 24rpx;
box-shadow: 0 2rpx 0rpx #888992;
position: absolute;
width: 96rpx;
left: 22rpx;
line-height: 1;
bottom: 32rpx;
height: 80rpx;
background-color: #ffffff;
@include vue-flex;
align-items: center;
border-radius: 8rpx;
justify-content: center;
}
.u-keyboard-change .inactive.zh {
transform: scale(0.85) translateY(-10rpx);
}
.u-keyboard-change .inactive.en {
transform: scale(0.85) translateY(10rpx);
}
.u-keyboard-change .active {
color: rgb(237, 112, 64);
font-size: 30rpx;
}
.u-keyboard-change .zh {
transform: translateY(-10rpx);
}
.u-keyboard-change .en {
transform: translateY(10rpx);
}
</style>

View File

@ -0,0 +1,299 @@
<template>
<view
class="u-card"
@tap.stop="click"
:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }"
:style="{
borderRadius: borderRadius + 'rpx',
margin: margin,
boxShadow: boxShadow
}"
>
<view
v-if="showHead"
class="u-card__head"
:style="[{padding: padding + 'rpx'}, headStyle]"
:class="{
'u-border-bottom': headBorderBottom
}"
@tap="headClick"
>
<view v-if="!$slots.head" class="u-flex u-row-between">
<view class="u-card__head--left u-flex u-line-1" v-if="title">
<image
:src="thumb"
class="u-card__head--left__thumb"
mode="aspectfull"
v-if="thumb"
:style="{
height: thumbWidth + 'rpx',
width: thumbWidth + 'rpx',
borderRadius: thumbCircle ? '100rpx' : '6rpx'
}"
></image>
<text
class="u-card__head--left__title u-line-1"
:style="{
fontSize: titleSize + 'rpx',
color: titleColor
}"
>
{{ title }}
</text>
</view>
<view class="u-card__head--right u-line-1" v-if="subTitle">
<text
class="u-card__head__title__text"
:style="{
fontSize: subTitleSize + 'rpx',
color: subTitleColor
}"
>
{{ subTitle }}
</text>
</view>
</view>
<slot name="head" v-else />
</view>
<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view>
<view
v-if="showFoot"
class="u-card__foot"
@tap="footClick"
:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]"
:class="{
'u-border-top': footBorderTop
}"
>
<slot name="foot" />
</view>
</view>
</template>
<script>
/**
* card 卡片
* @description 卡片组件一般用于多个列表条目且风格统一的场景
* @tutorial https://www.uviewui.com/components/card.html
* @property {Boolean} full 卡片与屏幕两侧是否留空隙默认false
* @property {String} title 头部左边的标题
* @property {String} title-color 标题颜色默认#303133
* @property {String | Number} title-size 标题字体大小单位rpx默认30
* @property {String} sub-title 头部右边的副标题
* @property {String} sub-title-color 副标题颜色默认#909399
* @property {String | Number} sub-title-size 副标题字体大小默认26
* @property {Boolean} border 是否显示边框默认true
* @property {String | Number} index 用于标识点击了第几个卡片
* @property {String} box-shadow 卡片外围阴影字符串形式默认none
* @property {String} margin 卡片与屏幕两边和上下元素的间距需带单位"30rpx 20rpx"默认30rpx
* @property {String | Number} border-radius 卡片整体的圆角值单位rpx默认16
* @property {Object} head-style 头部自定义样式对象形式
* @property {Object} body-style 中部自定义样式对象形式
* @property {Object} foot-style 底部自定义样式对象形式
* @property {Boolean} head-border-bottom 是否显示头部的下边框默认true
* @property {Boolean} foot-border-top 是否显示底部的上边框默认true
* @property {Boolean} show-head 是否显示头部默认true
* @property {Boolean} show-head 是否显示尾部默认true
* @property {String} thumb 缩略图路径如设置将显示在标题的左边不建议使用相对路径
* @property {String | Number} thumb-width 缩略图的宽度高等于宽单位rpx默认60
* @property {Boolean} thumb-circle 缩略图是否为圆形默认false
* @event {Function} click 整个卡片任意位置被点击时触发
* @event {Function} head-click 卡片头部被点击时触发
* @event {Function} body-click 卡片主体部分被点击时触发
* @event {Function} foot-click 卡片底部部分被点击时触发
* @example <u-card padding="30" title="card"></u-card>
*/
export default {
name: 'u-card',
props: {
//
full: {
type: Boolean,
default: false
},
//
title: {
type: String,
default: ''
},
//
titleColor: {
type: String,
default: '#303133'
},
// rpx
titleSize: {
type: [Number, String],
default: '30'
},
//
subTitle: {
type: String,
default: ''
},
//
subTitleColor: {
type: String,
default: '#909399'
},
// rpx
subTitleSize: {
type: [Number, String],
default: '26'
},
// full=false()
border: {
type: Boolean,
default: true
},
//
index: {
type: [Number, String, Object],
default: ''
},
// "30rpx 30rpx""20rpx 20rpx 30rpx 30rpx"
margin: {
type: String,
default: '30rpx'
},
// card
borderRadius: {
type: [Number, String],
default: '16'
},
//
headStyle: {
type: Object,
default() {
return {};
}
},
//
bodyStyle: {
type: Object,
default() {
return {};
}
},
//
footStyle: {
type: Object,
default() {
return {};
}
},
//
headBorderBottom: {
type: Boolean,
default: true
},
//
footBorderTop: {
type: Boolean,
default: true
},
//
thumb: {
type: String,
default: ''
},
// rpx
thumbWidth: {
type: [String, Number],
default: '60'
},
//
thumbCircle: {
type: Boolean,
default: false
},
// headbodyfoot
padding: {
type: [String, Number],
default: '30'
},
//
showHead: {
type: Boolean,
default: true
},
//
showFoot: {
type: Boolean,
default: true
},
//
boxShadow: {
type: String,
default: 'none'
}
},
data() {
return {};
},
methods: {
click() {
this.$emit('click', this.index);
},
headClick() {
this.$emit('head-click', this.index);
},
bodyClick() {
this.$emit('body-click', this.index);
},
footClick() {
this.$emit('foot-click', this.index);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-card {
position: relative;
overflow: hidden;
font-size: 28rpx;
background-color: #ffffff;
box-sizing: border-box;
&-full {
// 0
margin-left: 0 !important;
margin-right: 0 !important;
width: 100%;
}
&--border:after {
border-radius: 16rpx;
}
&__head {
&--left {
color: $u-main-color;
&__thumb {
margin-right: 16rpx;
}
&__title {
max-width: 400rpx;
}
}
&--right {
color: $u-tips-color;
margin-left: 6rpx;
}
}
&__body {
color: $u-content-color;
}
&__foot {
color: $u-tips-color;
}
}
</style>

View File

@ -0,0 +1,70 @@
<template>
<view class="u-cell-box">
<view class="u-cell-title" v-if="title" :style="[titleStyle]">
{{title}}
</view>
<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
<slot />
</view>
</view>
</template>
<script>
/**
* cellGroup 单元格父组件Group
* @description cell单元格一般用于一组列表的情况比如个人中心页设置页等搭配u-cell-item
* @tutorial https://www.uviewui.com/components/cell.html
* @property {String} title 分组标题
* @property {Boolean} border 是否显示外边框默认true
* @property {Object} title-style 分组标题的的样式对象形式{'font-size': '24rpx'} {'fontSize': '24rpx'}
* @example <u-cell-group title="设置喜好">
*/
export default {
name: "u-cell-group",
props: {
//
title: {
type: String,
default: ''
},
// list
border: {
type: Boolean,
default: true
},
//
// {'font-size': '24rpx'} {'fontSize': '24rpx'}
titleStyle: {
type: Object,
default () {
return {};
}
}
},
data() {
return {
index: 0,
}
},
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-cell-box {
width: 100%;
}
.u-cell-title {
padding: 30rpx 32rpx 10rpx 32rpx;
font-size: 30rpx;
text-align: left;
color: $u-tips-color;
}
.u-cell-item-box {
background-color: #FFFFFF;
flex-direction: row;
}
</style>

View File

@ -0,0 +1,316 @@
<template>
<view
@tap="click"
class="u-cell"
:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }"
hover-stay-time="150"
:hover-class="hoverClass"
:style="{
backgroundColor: bgColor
}"
>
<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon>
<view class="u-flex" v-else>
<slot name="icon"></slot>
</view>
<view
class="u-cell_title"
:style="[
{
width: titleWidth ? titleWidth + 'rpx' : 'auto'
},
titleStyle
]"
>
<block v-if="title !== ''">{{ title }}</block>
<slot name="title" v-else></slot>
<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]">
<block v-if="label !== ''">{{ label }}</block>
<slot name="label" v-else></slot>
</view>
</view>
<view class="u-cell__value" :style="[valueStyle]">
<block class="u-cell__value" v-if="value !== ''">{{ value }}</block>
<slot v-else></slot>
</view>
<view class="u-flex u-cell_right" v-if="$slots['right-icon']">
<slot name="right-icon"></slot>
</view>
<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon>
</view>
</template>
<script>
/**
* cellItem 单元格Item
* @description cell单元格一般用于一组列表的情况比如个人中心页设置页等搭配u-cell-group使用
* @tutorial https://www.uviewui.com/components/cell.html
* @property {String} title 左侧标题
* @property {String} icon 左侧图标名只支持uView内置图标见Icon 图标
* @property {Object} icon-style 左边图标的样式对象形式
* @property {String} value 右侧内容
* @property {String} label 标题下方的描述信息
* @property {Boolean} border-bottom 是否显示cell的下边框默认true
* @property {Boolean} border-top 是否显示cell的上边框默认false
* @property {Boolean} center 是否使内容垂直居中默认false
* @property {String} hover-class 是否开启点击反馈none为无效果默认true
* // @property {Boolean} border-gap border-bottomtrueCelltrue
* @property {Boolean} arrow 是否显示右侧箭头默认true
* @property {Boolean} required 箭头方向可选值默认right
* @property {Boolean} arrow-direction 是否显示左边表示必填的星号默认false
* @property {Object} title-style 标题样式对象形式
* @property {Object} value-style 右侧内容样式对象形式
* @property {Object} label-style 标题下方描述信息的样式对象形式
* @property {String} bg-color 背景颜色默认transparent
* @property {String Number} index 用于在click事件回调中返回标识当前是第几个Item
* @property {String Number} title-width 标题的宽度单位rpx
* @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
*/
export default {
name: 'u-cell-item',
props: {
// (uView)src
icon: {
type: String,
default: ''
},
//
title: {
type: [String, Number],
default: ''
},
//
value: {
type: [String, Number],
default: ''
},
//
label: {
type: [String, Number],
default: ''
},
//
borderBottom: {
type: Boolean,
default: true
},
//
borderTop: {
type: Boolean,
default: false
},
// cellcell线线
// 1.4.0border-topborder-bottom
// borderGap: {
// type: Boolean,
// default: true
// },
// cellnone
hoverClass: {
type: String,
default: 'u-cell-hover'
},
//
arrow: {
type: Boolean,
default: true
},
//
center: {
type: Boolean,
default: false
},
//
required: {
type: Boolean,
default: false
},
// rpx
titleWidth: {
type: [Number, String],
default: ''
},
// right|up|downright
arrowDirection: {
type: String,
default: 'right'
},
//
titleStyle: {
type: Object,
default() {
return {};
}
},
//
valueStyle: {
type: Object,
default() {
return {};
}
},
//
labelStyle: {
type: Object,
default() {
return {};
}
},
//
bgColor: {
type: String,
default: 'transparent'
},
// cell
index: {
type: [String, Number],
default: ''
},
// 使lable
useLabelSlot: {
type: Boolean,
default: false
},
// rpxicon
iconSize: {
type: [Number, String],
default: 34
},
//
iconStyle: {
type: Object,
default() {
return {}
}
},
},
data() {
return {
};
},
computed: {
arrowStyle() {
let style = {};
if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)';
else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)';
else style.transform = 'rotate(0deg)';
return style;
}
},
methods: {
click() {
this.$emit('click', this.index);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-cell {
@include vue-flex;
align-items: center;
position: relative;
/* #ifndef APP-NVUE */
box-sizing: border-box;
/* #endif */
width: 100%;
padding: 26rpx 32rpx;
font-size: 28rpx;
line-height: 54rpx;
color: $u-content-color;
background-color: #fff;
text-align: left;
}
.u-cell_title {
font-size: 28rpx;
}
.u-cell__left-icon-wrap {
margin-right: 10rpx;
font-size: 32rpx;
}
.u-cell__right-icon-wrap {
margin-left: 10rpx;
color: #969799;
font-size: 28rpx;
}
.u-cell__left-icon-wrap,
.u-cell__right-icon-wrap {
@include vue-flex;
align-items: center;
height: 48rpx;
}
.u-cell-border:after {
position: absolute;
/* #ifndef APP-NVUE */
box-sizing: border-box;
content: ' ';
pointer-events: none;
border-bottom: 1px solid $u-border-color;
/* #endif */
right: 0;
left: 0;
top: 0;
transform: scaleY(0.5);
}
.u-cell-border {
position: relative;
}
.u-cell__label {
margin-top: 6rpx;
font-size: 26rpx;
line-height: 36rpx;
color: $u-tips-color;
/* #ifndef APP-NVUE */
word-wrap: break-word;
/* #endif */
}
.u-cell__value {
overflow: hidden;
text-align: right;
/* #ifndef APP-NVUE */
vertical-align: middle;
/* #endif */
color: $u-tips-color;
font-size: 26rpx;
}
.u-cell__title,
.u-cell__value {
flex: 1;
}
.u-cell--required {
/* #ifndef APP-NVUE */
overflow: visible;
/* #endif */
@include vue-flex;
align-items: center;
}
.u-cell--required:before {
position: absolute;
/* #ifndef APP-NVUE */
content: '*';
/* #endif */
left: 8px;
margin-top: 4rpx;
font-size: 14px;
color: $u-type-error;
}
.u-cell_right {
line-height: 1;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<view class="u-checkbox-group u-clearfix">
<slot></slot>
</view>
</template>
<script>
import Emitter from '../../libs/util/emitter.js';
/**
* checkboxGroup 开关选择器父组件Group
* @description 复选框组件一般用于需要多个选择的场景该组件功能完整使用方便
* @tutorial https://www.uviewui.com/components/checkbox.html
* @property {String Number} max 最多能选中多少个checkbox默认999
* @property {String Number} size 组件整体的大小单位rpx默认40
* @property {Boolean} disabled 是否禁用所有checkbox默认false
* @property {String Number} icon-size 图标大小单位rpx默认20
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
* @property {String} width 宽度需带单位
* @property {String} width 宽度需带单位
* @property {String} shape 外观形状shape-方形circle-圆形(默认circle)
* @property {Boolean} wrap 是否每个checkbox都换行默认false
* @property {String} active-color 选中时的颜色应用到所有子Checkbox组件默认#2979ff
* @event {Function} change 任一个checkbox状态发生变化时触发回调为一个对象
* @example <u-checkbox-group></u-checkbox-group>
*/
export default {
name: 'u-checkbox-group',
mixins: [Emitter],
props: {
// checkbox
max: {
type: [Number, String],
default: 999
},
// name
// value: {
// default: Array,
// default() {
// return []
// }
// },
//
disabled: {
type: Boolean,
default: false
},
//
name: {
type: [Boolean, String],
default: ''
},
//
labelDisabled: {
type: Boolean,
default: false
},
// squarecircle
shape: {
type: String,
default: 'square'
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
size: {
type: [String, Number],
default: 34
},
// checkboxu-checkbox-group
width: {
type: String,
default: 'auto'
},
// checkbox
wrap: {
type: Boolean,
default: false
},
// rpx
iconSize: {
type: [String, Number],
default: 20
},
},
data() {
return {
}
},
created() {
// childrendata
this.children = [];
},
methods: {
emitEvent() {
let values = [];
this.children.map(val => {
if(val.value) values.push(val.name);
})
this.$emit('change', values);
// checkbox
//
setTimeout(() => {
// u-form-item
this.dispatch('u-form-item', 'on-form-change', values);
}, 60)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-checkbox-group {
/* #ifndef MP || APP-NVUE */
display: inline-flex;
flex-wrap: wrap;
/* #endif */
}
</style>

View File

@ -0,0 +1,284 @@
<template>
<view class="u-checkbox" :style="[checkboxStyle]">
<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
<u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/>
</view>
<view class="u-checkbox__label" @tap="onClickLabel" :style="{
fontSize: $u.addUnit(labelSize)
}">
<slot />
</view>
</view>
</template>
<script>
/**
* checkbox 复选框
* @description 该组件需要搭配checkboxGroup组件使用以便用户进行操作时获得当前复选框组的选中情况
* @tutorial https://www.uviewui.com/components/checkbox.html
* @property {String Number} icon-size 图标大小单位rpx默认20
* @property {String Number} label-size label字体大小单位rpx默认28
* @property {String Number} name checkbox组件的标示符
* @property {String} shape 形状见官网说明默认circle
* @property {Boolean} disabled 是否禁用
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox
* @property {String} active-color 选中时的颜色如设置CheckboxGroup的active-color将失效
* @event {Function} change 某个checkbox状态发生变化时触发回调为一个对象
* @example <u-checkbox v-model="checked" :disabled="false"></u-checkbox>
*/
export default {
name: "u-checkbox",
props: {
// checkbox
name: {
type: [String, Number],
default: ''
},
// squarecircle
shape: {
type: String,
default: ''
},
//
value: {
type: Boolean,
default: false
},
//
disabled: {
type: [String, Boolean],
default: ''
},
//
labelDisabled: {
type: [String, Boolean],
default: ''
},
// checkboxGroupactiveColor
activeColor: {
type: String,
default: ''
},
// rpx
iconSize: {
type: [String, Number],
default: ''
},
// labelrpx
labelSize: {
type: [String, Number],
default: ''
},
//
size: {
type: [String, Number],
default: ''
},
},
data() {
return {
parentDisabled: false,
newParams: {},
};
},
created() {
// provide/inject使created
this.parent = this.$u.$parent.call(this, 'u-checkbox-group');
// u-checkbox-groupthischildren
this.parent && this.parent.children.push(this);
},
computed: {
// u-checkbox-group
isDisabled() {
return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false;
},
// label
isLabelDisabled() {
return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
},
// size34rpx
checkboxSize() {
return this.size ? this.size : (this.parent ? this.parent.size : 34);
},
// 20
checkboxIconSize() {
return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20);
},
//
elActiveColor() {
return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary');
},
//
elShape() {
return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square');
},
iconStyle() {
let style = {};
// v-modelfalse
if (this.elActiveColor && this.value && !this.isDisabled) {
style.borderColor = this.elActiveColor;
style.backgroundColor = this.elActiveColor;
}
style.width = this.$u.addUnit(this.checkboxSize);
style.height = this.$u.addUnit(this.checkboxSize);
return style;
},
// checkbox
iconColor() {
return this.value ? '#ffffff' : 'transparent';
},
iconClass() {
let classes = [];
classes.push('u-checkbox__icon-wrap--' + this.elShape);
if (this.value == true) classes.push('u-checkbox__icon-wrap--checked');
if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled');
if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked');
// ","
return classes.join(' ');
},
checkboxStyle() {
let style = {};
if(this.parent && this.parent.width) {
style.width = this.parent.width;
// #ifdef MP
// 使float
style.float = 'left';
// #endif
// #ifndef MP
// H5APP使flex
style.flex = `0 0 ${this.parent.width}`;
// #endif
}
if(this.parent && this.parent.wrap) {
style.width = '100%';
// #ifndef MP
// H5APP使flex100%
style.flex = '0 0 100%';
// #endif
}
return style;
}
},
methods: {
onClickLabel() {
if (!this.isLabelDisabled && !this.isDisabled) {
this.setValue();
}
},
toggle() {
if (!this.isDisabled) {
this.setValue();
}
},
emitEvent() {
this.$emit('change', {
value: !this.value,
name: this.name
})
// u-checkbox-group
// this.$emit('input')
setTimeout(() => {
if(this.parent && this.parent.emitEvent) this.parent.emitEvent();
}, 80);
},
// inputinputv-model
setValue() {
//
let checkedNum = 0;
if(this.parent && this.parent.children) {
// valuetrue1()
this.parent.children.map(val => {
if (val.value) checkedNum++;
})
}
//
if (this.value == true) {
this.emitEvent();
this.$emit('input', !this.value);
} else {
//
if(this.parent && checkedNum >= this.parent.max) {
return this.$u.toast(`最多可选${this.parent.max}`);
}
// max
this.emitEvent();
this.$emit('input', !this.value);
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-checkbox {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
overflow: hidden;
user-select: none;
line-height: 1.8;
&__icon-wrap {
color: $u-content-color;
flex: none;
display: -webkit-flex;
@include vue-flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 42rpx;
height: 42rpx;
color: transparent;
text-align: center;
transition-property: color, border-color, background-color;
font-size: 20px;
border: 1px solid #c8c9cc;
transition-duration: 0.2s;
/* #ifdef MP-TOUTIAO */
// 0
&__icon {
line-height: 0;
}
/* #endif */
&--circle {
border-radius: 100%;
}
&--square {
border-radius: 6rpx;
}
&--checked {
color: #fff;
background-color: $u-type-primary;
border-color: $u-type-primary;
}
&--disabled {
background-color: #ebedf0;
border-color: #c8c9cc;
}
&--disabled--checked {
color: #c8c9cc !important;
}
}
&__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 24rpx;
color: $u-content-color;
font-size: 30rpx;
&--disabled {
color: #c8c9cc;
}
}
}
</style>

View File

@ -0,0 +1,220 @@
<template>
<view
class="u-circle-progress"
:style="{
width: widthPx + 'px',
height: widthPx + 'px',
backgroundColor: bgColor
}"
>
<!-- 支付宝小程序不支持canvas-id属性必须用id属性 -->
<canvas
class="u-canvas-bg"
:canvas-id="elBgId"
:id="elBgId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<canvas
class="u-canvas"
:canvas-id="elId"
:id="elId"
:style="{
width: widthPx + 'px',
height: widthPx + 'px'
}"
></canvas>
<slot></slot>
</view>
</template>
<script>
/**
* circleProgress 环形进度条
* @description 展示操作或任务的当前进度比如上传文件是一个圆形的进度条注意此组件的percent值只能动态增加不能动态减少
* @tutorial https://www.uviewui.com/components/circleProgress.html
* @property {String Number} percent 圆环进度百分比值为数值类型0-100
* @property {String} inactive-color 圆环的底色默认为灰色(该值无法动态变更)默认#ececec
* @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)默认#19be6b
* @property {String Number} width 整个圆环组件的宽度高度默认等于宽度值单位rpx默认200
* @property {String Number} border-width 圆环的边框宽度单位rpx默认14
* @property {String Number} duration 整个圆环执行一圈的时间单位ms默认呢1500
* @property {String} type 如设置active-color值将会失效
* @property {String} bg-color 整个组件背景颜色默认为白色
* @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
*/
export default {
name: 'u-circle-progress',
props: {
//
percent: {
type: Number,
default: 0,
// 0100
validator: val => {
return val >= 0 && val <= 100;
}
},
//
inactiveColor: {
type: String,
default: '#ececec'
},
//
activeColor: {
type: String,
default: '#19be6b'
},
// 线rpx
borderWidth: {
type: [Number, String],
default: 14
},
// rpx
width: {
type: [Number, String],
default: 200
},
// ms
duration: {
type: [Number, String],
default: 1500
},
//
type: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: '#ffffff'
}
},
data() {
return {
// #ifdef MP-WEIXIN
elBgId: 'uCircleProgressBgId', // 使this.$u.guid()id
elId: 'uCircleProgressElId',
// #endif
// #ifndef MP-WEIXIN
elBgId: this.$u.guid(), // id
elId: this.$u.guid(),
// #endif
widthPx: uni.upx2px(this.width), // px
borderWidthPx: uni.upx2px(this.borderWidth), // px
startAngle: -Math.PI / 2, // canvas312
progressContext: null, // canvas
newPercent: 0, //
oldPercent: 0 //
};
},
watch: {
percent(nVal, oVal = 0) {
if (nVal > 100) nVal = 100;
if (nVal < 0) oVal = 0;
// this.percent
this.newPercent = nVal;
this.oldPercent = oVal;
setTimeout(() => {
//
//
this.drawCircleByProgress(oVal);
}, 50);
}
},
created() {
// 使
this.newPercent = this.percent;
this.oldPercent = 0;
},
computed: {
// type
circleColor() {
if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
else return this.activeColor;
}
},
mounted() {
// h5this.$nextTick()(HX2.4.7)
setTimeout(() => {
this.drawProgressBg();
this.drawCircleByProgress(this.oldPercent);
}, 50);
},
methods: {
drawProgressBg() {
let ctx = uni.createCanvasContext(this.elBgId, this);
ctx.setLineWidth(this.borderWidthPx); //
ctx.setStrokeStyle(this.inactiveColor); // 线
ctx.beginPath(); //
// (110,110)100
let radius = this.widthPx / 2;
ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false);
ctx.stroke(); //
ctx.draw();
},
drawCircleByProgress(progress) {
// this.data使
let ctx = this.progressContext;
if (!ctx) {
ctx = uni.createCanvasContext(this.elId, this);
this.progressContext = ctx;
}
//
ctx.setLineCap('round');
// 线
ctx.setLineWidth(this.borderWidthPx);
ctx.setStrokeStyle(this.circleColor);
// 100
let time = Math.floor(this.duration / 100);
// 2π100
// 312this.startAngle
let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle;
ctx.beginPath();
// canvas
let radius = this.widthPx / 2;
ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false);
ctx.stroke();
ctx.draw();
//
if (this.newPercent > this.oldPercent) {
//
progress++;
//
if (progress > this.newPercent) return;
} else {
//
progress--;
if (progress < this.newPercent) return;
}
setTimeout(() => {
// time
this.drawCircleByProgress(progress);
}, time);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-circle-progress {
position: relative;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
justify-content: center;
}
.u-canvas-bg {
position: absolute;
}
.u-canvas {
position: absolute;
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<view class="u-progress" :style="{
borderRadius: round ? '100rpx' : 0,
height: height + 'rpx',
backgroundColor: inactiveColor
}">
<view :class="[
type ? `u-type-${type}-bg` : '',
striped ? 'u-striped' : '',
striped && stripedActive ? 'u-striped-active' : ''
]" class="u-active" :style="[progressStyle]">
<slot v-if="$slots.default || $slots.$default" />
<block v-else-if="showPercent">
{{percent + '%'}}
</block>
</view>
</view>
</template>
<script>
/**
* lineProgress 线型进度条
* @description 展示操作或任务的当前进度比如上传文件是一个线形的进度条
* @tutorial https://www.uviewui.com/components/lineProgress.html
* @property {String Number} percent 进度条百分比值为数值类型0-100
* @property {Boolean} round 进度条两端是否为半圆默认true
* @property {String} type 如设置active-color值将会失效
* @property {String} active-color 进度条激活部分的颜色默认#19be6b
* @property {String} inactive-color 进度条的底色默认#ececec
* @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值默认true
* @property {String Number} height 进度条的高度单位rpx默认28
* @property {Boolean} striped 是否显示进度条激活部分的条纹默认false
* @property {Boolean} striped-active 条纹是否具有动态效果默认false
* @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
*/
export default {
name: "u-line-progress",
props: {
//
round: {
type: Boolean,
default: true
},
//
type: {
type: String,
default: ''
},
//
activeColor: {
type: String,
default: '#19be6b'
},
inactiveColor: {
type: String,
default: '#ececec'
},
//
percent: {
type: Number,
default: 0
},
//
showPercent: {
type: Boolean,
default: true
},
// rpx
height: {
type: [Number, String],
default: 28
},
//
striped: {
type: Boolean,
default: false
},
//
stripedActive: {
type: Boolean,
default: false
}
},
data() {
return {
}
},
computed: {
progressStyle() {
let style = {};
style.width = this.percent + '%';
if(this.activeColor) style.backgroundColor = this.activeColor;
return style;
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-progress {
overflow: hidden;
height: 15px;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
width: 100%;
border-radius: 100rpx;
}
.u-active {
width: 0;
height: 100%;
align-items: center;
@include vue-flex;
justify-items: flex-end;
justify-content: space-around;
font-size: 20rpx;
color: #ffffff;
transition: all 0.4s ease;
}
.u-striped {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-size: 39px 39px;
}
.u-striped-active {
animation: progress-stripes 2s linear infinite;
}
@keyframes progress-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 39px 0;
}
}
</style>

View File

@ -0,0 +1,156 @@
<template>
<view class="u-col" :class="[
'u-col-' + span
]" :style="{
padding: `0 ${Number(gutter)/2 + 'rpx'}`,
marginLeft: 100 / 12 * offset + '%',
flex: `0 0 ${100 / 12 * span}%`,
alignItems: uAlignItem,
justifyContent: uJustify,
textAlign: textAlign
}"
@tap="click">
<slot></slot>
</view>
</template>
<script>
/**
* col 布局单元格
* @description 通过基础的 12 分栏迅速简便地创建布局搭配<u-row>使用
* @tutorial https://www.uviewui.com/components/layout.html
* @property {String Number} span 栅格占据的列数总12等分默认0
* @property {String} text-align 文字水平对齐方式默认left
* @property {String Number} offset 分栏左边偏移计算方式与span相同默认0
* @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col>
*/
export default {
name: "u-col",
props: {
// 12
span: {
type: [Number, String],
default: 12
},
// (12)
offset: {
type: [Number, String],
default: 0
},
// `start`(`flex-start`)`end`(`flex-end`)`center``around`(`space-around`)`between`(`space-between`)
justify: {
type: String,
default: 'start'
},
// topcenterbottom
align: {
type: String,
default: 'center'
},
//
textAlign: {
type: String,
default: 'left'
},
//
stop: {
type: Boolean,
default: true
}
},
data() {
return {
gutter: 20, // colu-row
}
},
created() {
this.parent = false;
},
mounted() {
//
this.parent = this.$u.$parent.call(this, 'u-row');
if (this.parent) {
this.gutter = this.parent.gutter;
}
},
computed: {
uJustify() {
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify;
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify;
else return this.justify;
},
uAlignItem() {
if (this.align == 'top') return 'flex-start';
if (this.align == 'bottom') return 'flex-end';
else return this.align;
}
},
methods: {
click(e) {
this.$emit('click');
}
}
}
</script>
<style lang="scss">
@import "../../libs/css/style.components.scss";
.u-col {
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
float: left;
/* #endif */
}
.u-col-0 {
width: 0;
}
.u-col-1 {
width: calc(100%/12);
}
.u-col-2 {
width: calc(100%/12 * 2);
}
.u-col-3 {
width: calc(100%/12 * 3);
}
.u-col-4 {
width: calc(100%/12 * 4);
}
.u-col-5 {
width: calc(100%/12 * 5);
}
.u-col-6 {
width: calc(100%/12 * 6);
}
.u-col-7 {
width: calc(100%/12 * 7);
}
.u-col-8 {
width: calc(100%/12 * 8);
}
.u-col-9 {
width: calc(100%/12 * 9);
}
.u-col-10 {
width: calc(100%/12 * 10);
}
.u-col-11 {
width: calc(100%/12 * 11);
}
.u-col-12 {
width: calc(100%/12 * 12);
}
</style>

View File

@ -0,0 +1,205 @@
<template>
<view class="u-collapse-item" :style="[itemStyle]">
<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]">
<block v-if="!$slots['title-all']">
<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
isShow && activeStyle && !arrow ? activeStyle : '']">
{{ title }}
</view>
<slot v-else name="title" />
<view class="u-icon-wrap">
<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }"
class="u-arrow-down-icon" name="arrow-down"></u-icon>
</view>
</block>
<slot v-else name="title-all" />
</view>
<view class="u-collapse-body" :style="[{
height: isShow ? height + 'px' : '0'
}]">
<view class="u-collapse-content" :id="elId" :style="[bodyStyle]">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* collapseItem 手风琴Item
* @description 通过折叠面板收纳内容区域搭配u-collapse使用
* @tutorial https://www.uviewui.com/components/collapse.html
* @property {String} title 面板标题
* @property {String Number} index 主要用于事件的回调标识那个Item被点击
* @property {Boolean} disabled 面板是否可以打开或收起默认false
* @property {Boolean} open 设置某个面板的初始状态是否打开默认false
* @property {String Number} name 唯一标识符如不设置默认用当前collapse-item的索引值
* @property {String} align 标题的对齐方式默认left
* @property {Object} active-style 不显示箭头时可以添加当前选择的collapse-item活动样式对象形式
* @event {Function} change 某个item被打开或者收起时触发
* @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
*/
export default {
name: "u-collapse-item",
props: {
//
title: {
type: String,
default: ''
},
//
align: {
type: String,
default: 'left'
},
//
disabled: {
type: Boolean,
default: false
},
// collapse
open: {
type: Boolean,
default: false
},
//
name: {
type: [Number, String],
default: ''
},
//
activeStyle: {
type: Object,
default () {
return {}
}
},
//
index: {
type: [String, Number],
default: ''
}
},
data() {
return {
isShow: false,
elId: this.$u.guid(),
height: 0, // body
headStyle: {}, //
bodyStyle: {}, //
itemStyle: {}, // item
arrowColor: '', //
hoverClass: '', //
arrow: true, //
};
},
watch: {
open(val) {
this.isShow = val;
}
},
created() {
this.parent = false;
// u-collapseu-collapse便u-collapse-item
this.isShow = this.open;
},
methods: {
//
init() {
this.parent = this.$u.$parent.call(this, 'u-collapse');
if(this.parent) {
this.nameSync = this.name ? this.name : this.parent.childrens.length;
//
!this.parent.childrens.includes(this) && this.parent.childrens.push(this);
this.headStyle = this.parent.headStyle;
this.bodyStyle = this.parent.bodyStyle;
this.arrowColor = this.parent.arrowColor;
this.hoverClass = this.parent.hoverClass;
this.arrow = this.parent.arrow;
this.itemStyle = this.parent.itemStyle;
}
this.$nextTick(() => {
this.queryRect();
});
},
// collapsehead
headClick() {
if (this.disabled) return;
if (this.parent && this.parent.accordion == true) {
this.parent.childrens.map(val => {
// falsethis.isShow = !this.isShow;
if (this != val) {
val.isShow = false;
}
});
}
this.isShow = !this.isShow;
//
this.$emit('change', {
index: this.index,
show: this.isShow
})
//
if (this.isShow) this.parent && this.parent.onChange();
this.$forceUpdate();
},
//
queryRect() {
// $uGetRectuViewhttps://www.uviewui.com/js/getRect.html
// this.$uGetRectthis.$u.getRect
this.$uGetRect('#' + this.elId).then(res => {
this.height = res.height;
})
}
},
mounted() {
this.init();
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-collapse-head {
position: relative;
@include vue-flex;
justify-content: space-between;
align-items: center;
color: $u-main-color;
font-size: 30rpx;
line-height: 1;
padding: 24rpx 0;
text-align: left;
}
.u-collapse-title {
flex: 1;
overflow: hidden;
}
.u-arrow-down-icon {
transition: all 0.3s;
margin-right: 20rpx;
margin-left: 14rpx;
}
.u-arrow-down-icon-active {
transform: rotate(180deg);
transform-origin: center center;
}
.u-collapse-body {
overflow: hidden;
transition: all 0.3s;
}
.u-collapse-content {
overflow: hidden;
font-size: 28rpx;
color: $u-tips-color;
text-align: left;
}
</style>

View File

@ -0,0 +1,99 @@
<template>
<view class="u-collapse">
<slot />
</view>
</template>
<script>
/**
* collapse 手风琴
* @description 通过折叠面板收纳内容区域
* @tutorial https://www.uviewui.com/components/collapse.html
* @property {Boolean} accordion 是否手风琴模式默认true
* @property {Boolean} arrow 是否显示标题右侧的箭头默认true
* @property {String} arrow-color 标题右侧箭头的颜色默认#909399
* @property {Object} head-style 标题自定义样式对象形式
* @property {Object} body-style 主体自定义样式对象形式
* @property {String} hover-class 样式类名按下时有效默认u-hover-class
* @event {Function} change 当前激活面板展开时触发(如果是手风琴模式参数activeNames类型为String否则为Array)
* @example <u-collapse></u-collapse>
*/
export default {
name:"u-collapse",
props: {
//
accordion: {
type: Boolean,
default: true
},
//
headStyle: {
type: Object,
default () {
return {}
}
},
//
bodyStyle: {
type: Object,
default () {
return {}
}
},
// item
itemStyle: {
type: Object,
default () {
return {}
}
},
//
arrow: {
type: Boolean,
default: true
},
//
arrowColor: {
type: String,
default: '#909399'
},
// "none"
hoverClass: {
type: String,
default: 'u-hover-class'
}
},
created() {
this.childrens = []
},
data() {
return {
}
},
methods: {
//
init() {
this.childrens.forEach((vm, index) => {
vm.init();
})
},
// collapse itemcollapse item
onChange() {
let activeItem = [];
this.childrens.forEach((vm, index) => {
if (vm.isShow) {
activeItem.push(vm.nameSync);
}
})
// activeItem1
if (this.accordion) activeItem = activeItem.join('');
this.$emit('change', activeItem);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
</style>

View File

@ -0,0 +1,237 @@
<template>
<view
class="u-notice-bar"
:style="{
background: computeBgColor,
padding: padding
}"
:class="[
type ? `u-type-${type}-light-bg` : ''
]"
>
<view class="u-icon-wrap">
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
</view>
<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper">
<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item">
<view
class="u-news-item u-line-1"
:style="[textStyle]"
@tap="click(index)"
:class="['u-type-' + type]"
>
{{ item }}
</view>
</swiper-item>
</swiper>
<view class="u-icon-wrap">
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
</view>
</view>
</template>
<script>
export default {
props: {
//
list: {
type: Array,
default() {
return [];
}
},
// success|error|primary|info|warning
type: {
type: String,
default: 'warning'
},
//
volumeIcon: {
type: Boolean,
default: true
},
//
moreIcon: {
type: Boolean,
default: false
},
//
closeIcon: {
type: Boolean,
default: false
},
//
autoplay: {
type: Boolean,
default: true
},
// 使
color: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: ''
},
// row-column-
direction: {
type: String,
default: 'row'
},
//
show: {
type: Boolean,
default: true
},
// rpx
fontSize: {
type: [Number, String],
default: 26
},
// ms
duration: {
type: [Number, String],
default: 2000
},
//
volumeSize: {
type: [Number, String],
default: 34
},
// rpx
speed: {
type: Number,
default: 160
},
//
isCircular: {
type: Boolean,
default: true
},
// horizontal-vertical-
mode: {
type: String,
default: 'horizontal'
},
// play-paused-
playState: {
type: String,
default: 'play'
},
//
// HX2.6.11App 2.5.5+H5 2.5.5+
disableTouch: {
type: Boolean,
default: true
},
//
padding: {
type: [Number, String],
default: '18rpx 24rpx'
}
},
computed: {
// uview
computeColor() {
if (this.color) return this.color;
// 使content-color
else if(this.type == 'none') return '#606266';
else return this.type;
},
//
textStyle() {
let style = {};
if (this.color) style.color = this.color;
else if(this.type == 'none') style.color = '#606266';
style.fontSize = this.fontSize + 'rpx';
return style;
},
//
vertical() {
if(this.mode == 'horizontal') return false;
else return true;
},
//
computeBgColor() {
if (this.bgColor) return this.bgColor;
else if(this.type == 'none') return 'transparent';
}
},
data() {
return {
// animation: false
};
},
methods: {
//
click(index) {
this.$emit('click', index);
},
//
close() {
this.$emit('close');
},
//
getMore() {
this.$emit('getMore');
},
change(e) {
let index = e.detail.current;
if(index == this.list.length - 1) {
this.$emit('end');
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-notice-bar {
width: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
padding: 18rpx 24rpx;
overflow: hidden;
}
.u-swiper {
font-size: 26rpx;
height: 32rpx;
@include vue-flex;
align-items: center;
flex: 1;
margin-left: 12rpx;
}
.u-swiper-item {
@include vue-flex;
align-items: center;
overflow: hidden;
}
.u-news-item {
overflow: hidden;
}
.u-right-icon {
margin-left: 12rpx;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
}
.u-left-icon {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
}
</style>

View File

@ -0,0 +1,318 @@
<template>
<view class="u-countdown">
<view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))">
<view class="u-countdown-time" :style="[letterStyle]">
{{ d }}
</view>
</view>
<view
class="u-countdown-colon"
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
>
{{ separator == 'colon' ? ':' : '天' }}
</view>
<view class="u-countdown-item" :style="[itemStyle]" v-if="showHours">
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
{{ h }}
</view>
</view>
<view
class="u-countdown-colon"
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
v-if="showHours"
>
{{ separator == 'colon' ? ':' : '时' }}
</view>
<view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes">
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
{{ i }}
</view>
</view>
<view
class="u-countdown-colon"
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
v-if="showMinutes"
>
{{ separator == 'colon' ? ':' : '分' }}
</view>
<view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds">
<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
{{ s }}
</view>
</view>
<view
class="u-countdown-colon"
:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
v-if="showSeconds && separator == 'zh'"
>
</view>
</view>
</template>
<script>
/**
* countDown 倒计时
* @description 该组件一般使用于某个活动的截止时间上通过数字的变化给用户明确的时间感受提示用户进行某一个行为操作
* @tutorial https://www.uviewui.com/components/countDown.html
* @property {String Number} timestamp 倒计时单位为秒
* @property {Boolean} autoplay 是否自动开始倒计时如果为false需手动调用开始方法见官网说明默认true
* @property {String} separator 分隔符colon为英文冒号zh为中文默认colon
* @property {String Number} separator-size 分隔符的字体大小单位rpx默认30
* @property {String} separator-color 分隔符的颜色默认#303133
* @property {String Number} font-size 倒计时字体大小单位rpx默认30
* @property {Boolean} show-border 是否显示倒计时数字的边框默认false
* @property {Boolean} hide-zero-day "天"的部分为0时隐藏该字段 默认true
* @property {String} border-color 数字边框的颜色默认#303133
* @property {String} bg-color 倒计时数字的背景颜色默认#ffffff
* @property {String} color 倒计时数字的颜色默认#303133
* @property {String} height 数字高度值(宽度等同此值)设置边框时看情况是否需要设置此值单位rpx默认auto
* @property {Boolean} show-days 是否显示倒计时的"天"部分默认true
* @property {Boolean} show-hours 是否显示倒计时的"时"部分默认true
* @property {Boolean} show-minutes 是否显示倒计时的"分"部分默认true
* @property {Boolean} show-seconds 是否显示倒计时的"秒"部分默认true
* @event {Function} end 倒计时结束
* @event {Function} change 每秒触发一次回调为当前剩余的倒计秒数
* @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down>
*/
export default {
name: 'u-count-down',
props: {
//
timestamp: {
type: [Number, String],
default: 0
},
//
autoplay: {
type: Boolean,
default: true
},
// (colon)(zh)false"11:22""1122"
separator: {
type: String,
default: 'colon'
},
// rpx
separatorSize: {
type: [Number, String],
default: 30
},
//
separatorColor: {
type: String,
default: "#303133"
},
//
color: {
type: String,
default: '#303133'
},
// rpx
fontSize: {
type: [Number, String],
default: 30
},
//
bgColor: {
type: String,
default: '#fff'
},
// rpx
height: {
type: [Number, String],
default: 'auto'
},
//
showBorder: {
type: Boolean,
default: false
},
//
borderColor: {
type: String,
default: '#303133'
},
//
showSeconds: {
type: Boolean,
default: true
},
//
showMinutes: {
type: Boolean,
default: true
},
//
showHours: {
type: Boolean,
default: true
},
//
showDays: {
type: Boolean,
default: true
},
// ""0
hideZeroDay: {
type: Boolean,
default: false
}
},
watch: {
//
timestamp(newVal, oldVal) {
//
this.clearTimer();
this.start();
}
},
data() {
return {
d: '00', //
h: '00', //
i: '00', //
s: '00', //
timer: null ,//
seconds: 0, //
};
},
computed: {
// itemitem
itemStyle() {
let style = {};
if(this.height) {
style.height = this.height + 'rpx';
style.width = this.height + 'rpx';
}
if(this.showBorder) {
style.borderStyle = 'solid';
style.borderColor = this.borderColor;
style.borderWidth = '1px';
}
if(this.bgColor) {
style.backgroundColor = this.bgColor;
}
return style;
},
//
letterStyle() {
let style = {};
if(this.fontSize) style.fontSize = this.fontSize + 'rpx';
if(this.color) style.color = this.color;
return style;
}
},
mounted() {
//
this.autoplay && this.timestamp && this.start();
},
methods: {
//
start() {
//
this.clearTimer();
if (this.timestamp <= 0) return;
this.seconds = Number(this.timestamp);
this.formatTime(this.seconds);
this.timer = setInterval(() => {
this.seconds--;
// change
this.$emit('change', this.seconds);
if (this.seconds < 0) {
return this.end();
}
this.formatTime(this.seconds);
}, 1000);
},
//
formatTime(seconds) {
// 0
seconds <= 0 && this.end();
let [day, hour, minute, second] = [0, 0, 0, 0];
day = Math.floor(seconds / (60 * 60 * 24));
//
// hour()
hour = Math.floor(seconds / (60 * 60)) - day * 24;
// showHour
let showHour = null;
if(this.showDays) {
showHour = hour;
} else {
//
showHour = Math.floor(seconds / (60 * 60));
}
minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60;
second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
// 10"0"
showHour = showHour < 10 ? '0' + showHour : showHour;
minute = minute < 10 ? '0' + minute : minute;
second = second < 10 ? '0' + second : second;
day = day < 10 ? '0' + day : day;
this.d = day;
this.h = showHour;
this.i = minute;
this.s = second;
},
//
end() {
this.clearTimer();
this.$emit('end', {});
},
//
clearTimer() {
if(this.timer) {
//
clearInterval(this.timer);
this.timer = null;
}
}
},
beforeDestroy() {
clearInterval(this.timer);
this.timer = null;
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-countdown {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
}
.u-countdown-item {
@include vue-flex;
align-items: center;
justify-content: center;
padding: 2rpx;
border-radius: 6rpx;
white-space: nowrap;
transform: translateZ(0);
}
.u-countdown-time {
margin: 0;
padding: 0;
line-height: 1;
}
.u-countdown-colon {
@include vue-flex;
justify-content: center;
padding: 0 5rpx;
line-height: 1;
align-items: center;
padding-bottom: 4rpx;
}
.u-countdown-scale {
transform: scale(0.9);
transform-origin: center center;
}
</style>

View File

@ -0,0 +1,241 @@
<template>
<view
class="u-count-num"
:style="{
fontSize: fontSize + 'rpx',
fontWeight: bold ? 'bold' : 'normal',
color: color
}"
>
{{ displayValue }}
</view>
</template>
<script>
/**
* countTo 数字滚动
* @description 该组件一般用于需要滚动数字到某一个值的场景目标要求是一个递增的值
* @tutorial https://www.uviewui.com/components/countTo.html
* @property {String Number} start-val 开始值
* @property {String Number} end-val 结束值
* @property {String Number} duration 滚动过程所需的时间单位ms默认2000
* @property {Boolean} autoplay 是否自动开始滚动默认true
* @property {String Number} decimals 要显示的小数位数见官网说明默认0
* @property {Boolean} use-easing 滚动结束时是否缓动结尾见官网说明默认true
* @property {String} separator 千位分隔符见官网说明
* @property {String} color 字体颜色默认#303133
* @property {String Number} font-size 字体大小单位rpx默认50
* @property {Boolean} bold 字体是否加粗默认false
* @event {Function} end 数值滚动到目标值时触发
* @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
*/
export default {
name: 'u-count-to',
props: {
// 0
startVal: {
type: [Number, String],
default: 0
},
//
endVal: {
type: [Number, String],
default: 0,
required: true
},
// ms
duration: {
type: [Number, String],
default: 2000
},
//
autoplay: {
type: Boolean,
default: true
},
//
decimals: {
type: [Number, String],
default: 0
},
// 使
useEasing: {
type: Boolean,
default: true
},
//
decimal: {
type: [Number, String],
default: '.'
},
//
color: {
type: String,
default: '#303133'
},
//
fontSize: {
type: [Number, String],
default: 50
},
//
bold: {
type: Boolean,
default: false
},
// (23,321.05",")
separator: {
type: String,
default: ''
}
},
data() {
return {
localStartVal: this.startVal,
displayValue: this.formatNumber(this.startVal),
printVal: null,
paused: false, //
localDuration: Number(this.duration),
startTime: null, //
timestamp: null, //
remaining: null, //
rAF: null,
lastTime: 0 //
};
},
computed: {
countDown() {
return this.startVal > this.endVal;
}
},
watch: {
startVal() {
this.autoplay && this.start();
},
endVal() {
this.autoplay && this.start();
}
},
mounted() {
this.autoplay && this.start();
},
methods: {
easingFn(t, b, c, d) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
},
requestAnimationFrame(callback) {
const currTime = new Date().getTime();
// 使setTimteout60
const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
const id = setTimeout(() => {
callback(currTime + timeToCall);
}, timeToCall);
this.lastTime = currTime + timeToCall;
return id;
},
cancelAnimationFrame(id) {
clearTimeout(id);
},
//
start() {
this.localStartVal = this.startVal;
this.startTime = null;
this.localDuration = this.duration;
this.paused = false;
this.rAF = this.requestAnimationFrame(this.count);
},
//
reStart() {
if (this.paused) {
this.resume();
this.paused = false;
} else {
this.stop();
this.paused = true;
}
},
//
stop() {
this.cancelAnimationFrame(this.rAF);
},
// ()
resume() {
this.startTime = null;
this.localDuration = this.remaining;
this.localStartVal = this.printVal;
this.requestAnimationFrame(this.count);
},
//
reset() {
this.startTime = null;
this.cancelAnimationFrame(this.rAF);
this.displayValue = this.formatNumber(this.startVal);
},
count(timestamp) {
if (!this.startTime) this.startTime = timestamp;
this.timestamp = timestamp;
const progress = timestamp - this.startTime;
this.remaining = this.localDuration - progress;
if (this.useEasing) {
if (this.countDown) {
this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
} else {
this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
}
} else {
if (this.countDown) {
this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
} else {
this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
}
}
if (this.countDown) {
this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
} else {
this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
}
this.displayValue = this.formatNumber(this.printVal);
if (progress < this.localDuration) {
this.rAF = this.requestAnimationFrame(this.count);
} else {
this.$emit('end');
}
},
//
isNumber(val) {
return !isNaN(parseFloat(val));
},
formatNumber(num) {
// numNumbertoFixed
num = Number(num);
num = num.toFixed(Number(this.decimals));
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? this.decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (this.separator && !this.isNumber(this.separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + this.separator + '$2');
}
}
return x1 + x2;
},
destroyed() {
this.cancelAnimationFrame(this.rAF);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-count-num {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
text-align: center;
}
</style>

View File

@ -0,0 +1,153 @@
<template>
<view class="u-divider" :style="{
height: height == 'auto' ? 'auto' : height + 'rpx',
backgroundColor: bgColor,
marginBottom: marginBottom + 'rpx',
marginTop: marginTop + 'rpx'
}" @tap="click">
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
<view v-if="useSlot" class="u-divider-text" :style="{
color: color,
fontSize: fontSize + 'rpx'
}"><slot /></view>
<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
</view>
</template>
<script>
/**
* divider 分割线
* @description 区隔内容的分割线一般用于页面底部"没有更多"的提示
* @tutorial https://www.uviewui.com/components/divider.html
* @property {String Number} half-width 文字左或右边线条宽度数值或百分比数值时单位为rpx
* @property {String} border-color 线条颜色优先级高于type默认#dcdfe6
* @property {String} color 文字颜色默认#909399
* @property {String Number} fontSize 字体大小单位rpx默认26
* @property {String} bg-color 整个divider的背景颜色默认呢#ffffff
* @property {String Number} height 整个divider的高度单位rpx默认40
* @property {String} type 将线条设置主题色默认primary
* @property {Boolean} useSlot 是否使用slot传入内容如果不传入中间不会有空隙默认true
* @property {String Number} margin-top 与前一个组件的距离单位rpx默认0
* @property {String Number} margin-bottom 与后一个组件的距离单位rpx0
* @event {Function} click divider组件被点击时触发
* @example <u-divider color="#fa3534">长河落日圆</u-divider>
*/
export default {
name: 'u-divider',
props: {
// divider线()rpx
halfWidth: {
type: [Number, String],
default: 150
},
// divider线
borderColor: {
type: String,
default: '#dcdfe6'
},
// primary|info|success|warning|error
type: {
type: String,
default: 'primary'
},
//
color: {
type: String,
default: '#909399'
},
// rpx
fontSize: {
type: [Number, String],
default: 26
},
// divider
bgColor: {
type: String,
default: '#ffffff'
},
// dividerrpx
height: {
type: [Number, String],
default: 'auto'
},
//
marginTop: {
type: [String, Number],
default: 0
},
//
marginBottom: {
type: [String, Number],
default: 0
},
// 使slotslot
useSlot: {
type: Boolean,
default: true
}
},
computed: {
lineStyle() {
let style = {};
if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth;
else style.width = this.halfWidth + 'rpx';
// borderColortype
if(this.borderColor) style.borderColor = this.borderColor;
return style;
}
},
methods: {
click() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-divider {
width: 100%;
position: relative;
text-align: center;
@include vue-flex;
justify-content: center;
align-items: center;
overflow: hidden;
flex-direction: row;
}
.u-divider-line {
border-bottom: 1px solid $u-border-color;
transform: scale(1, 0.5);
transform-origin: center;
&--bordercolor--primary {
border-color: $u-type-primary;
}
&--bordercolor--success {
border-color: $u-type-success;
}
&--bordercolor--error {
border-color: $u-type-primary;
}
&--bordercolor--info {
border-color: $u-type-info;
}
&--bordercolor--warning {
border-color: $u-type-warning;
}
}
.u-divider-text {
white-space: nowrap;
padding: 0 16rpx;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
}
</style>

View File

@ -0,0 +1,132 @@
<template>
<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
<block v-if="!$slots.default && !$slots.$default">
<scroll-view scroll-y="true" :style="{
height: $u.addUnit(height)
}">
<view class="u-dropdown-item__options">
<u-cell-group>
<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
:key="index" :title-style="{
color: value == item.value ? activeColor : inactiveColor
}">
<u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon>
</u-cell-item>
</u-cell-group>
</view>
</scroll-view>
</block>
<slot v-else />
</view>
</template>
<script>
/**
* dropdown-item 下拉菜单
* @description 该组件一般用于向下展开菜单同时可切换多个选项卡的场景
* @tutorial http://uviewui.com/components/dropdown.html
* @property {String | Number} v-model 双向绑定选项卡选择值
* @property {String} title 菜单项标题
* @property {Array[Object]} options 选项数据如果传入了默认slot此参数无效
* @property {Boolean} disabled 是否禁用此选项卡默认false
* @property {String | Number} duration 选项卡展开和收起的过渡时间单位ms默认300
* @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)默认auto
* @example <u-dropdown-item title="标题"></u-dropdown-item>
*/
export default {
name: 'u-dropdown-item',
props: {
// value
value: {
type: [Number, String, Array],
default: ''
},
//
title: {
type: [String, Number],
default: ''
},
// slot
options: {
type: Array,
default () {
return []
}
},
//
disabled: {
type: Boolean,
default: false
},
//
height: {
type: [Number, String],
default: 'auto'
},
},
data() {
return {
active: false, //
activeColor: '#2979ff', //
inactiveColor: '#606266', //
}
},
computed: {
// propsu-dropdown
propsChange() {
return `${this.title}-${this.disabled}`;
}
},
watch: {
propsChange(n) {
// init()
//
if (this.parent) this.parent.init();
}
},
created() {
//
this.parent = false;
},
methods: {
init() {
// u-dropdown
let parent = this.$u.$parent.call(this, 'u-dropdown');
if (parent) {
this.parent = parent;
//
this.activeColor = parent.activeColor;
this.inactiveColor = parent.inactiveColor;
// thischildren()
// push
let exist = parent.children.find(val => {
return this === val;
})
if (!exist) parent.children.push(this);
if (parent.children.length == 1) this.active = true;
// childrentitlemenuList
parent.menuList.push({
title: this.title,
disabled: this.disabled
});
}
},
// cell
cellClick(value) {
// v-model
this.$emit('input', value);
// (u-dropdown)
this.parent.close();
// value
this.$emit('change', value);
}
},
mounted() {
this.init();
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
</style>

View File

@ -0,0 +1,298 @@
<template>
<view class="u-dropdown">
<view class="u-dropdown__menu" :style="{
height: $u.addUnit(height)
}" :class="{
'u-border-bottom': borderBottom
}">
<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)">
<view class="u-flex">
<text class="u-dropdown__menu__item__text" :style="{
color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
fontSize: $u.addUnit(titleSize)
}">{{item.title}}</text>
<view class="u-dropdown__menu__item__arrow" :class="{
'u-dropdown__menu__item__arrow--rotate': index === current
}">
<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
</view>
</view>
</view>
</view>
<view class="u-dropdown__content" :style="[contentStyle, {
transition: `opacity ${duration / 1000}s linear`,
top: $u.addUnit(height),
height: contentHeight + 'px'
}]"
@tap="maskClick" @touchmove.stop.prevent>
<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
<slot></slot>
</view>
<view class="u-dropdown__content__mask"></view>
</view>
</view>
</template>
<script>
/**
* dropdown 下拉菜单
* @description 该组件一般用于向下展开菜单同时可切换多个选项卡的场景
* @tutorial http://uviewui.com/components/dropdown.html
* @property {String} active-color 标题和选项卡选中的颜色默认#2979ff
* @property {String} inactive-color 标题和选项卡未选中的颜色默认#606266
* @property {Boolean} close-on-click-mask 点击遮罩是否关闭菜单默认true
* @property {Boolean} close-on-click-self 点击当前激活项标题是否关闭菜单默认true
* @property {String | Number} duration 选项卡展开和收起的过渡时间单位ms默认300
* @property {String | Number} height 标题菜单的高度单位任意默认80
* @property {String | Number} border-radius 菜单展开内容下方的圆角值单位任意默认0
* @property {Boolean} border-bottom 标题菜单是否显示下边框默认false
* @property {String | Number} title-size 标题的字体大小单位任意数值默认为rpx单位默认28
* @event {Function} open 下拉菜单被打开时触发
* @event {Function} close 下拉菜单被关闭时触发
* @example <u-dropdown></u-dropdown>
*/
export default {
name: 'u-dropdown',
props: {
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#606266'
},
//
closeOnClickMask: {
type: Boolean,
default: true
},
//
closeOnClickSelf: {
type: Boolean,
default: true
},
//
duration: {
type: [Number, String],
default: 300
},
// rpx
height: {
type: [Number, String],
default: 80
},
//
borderBottom: {
type: Boolean,
default: false
},
//
titleSize: {
type: [Number, String],
default: 28
},
//
borderRadius: {
type: [Number, String],
default: 0
},
// icon
menuIcon: {
type: String,
default: 'arrow-down'
},
//
menuIconSize: {
type: [Number, String],
default: 26
}
},
data() {
return {
showDropdown: true, // ,
menuList: [], //
active: false, //
// false""current0
// TX使===使==
current: 99999,
//
contentStyle: {
zIndex: -1,
opacity: 0
},
//
highlightIndex: 99999,
contentHeight: 0
}
},
computed: {
//
popupStyle() {
let style = {};
// Y100%
style.transform = `translateY(${this.active ? 0 : '-100%'})`
style['transition-duration'] = this.duration / 1000 + 's';
style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
return style;
}
},
created() {
// (u-dropdown-item)thisdata
this.children = [];
},
mounted() {
this.getContentHeight();
},
methods: {
init() {
// init
//
this.menuList = [];
this.children.map(child => {
child.init();
})
},
//
menuClick(index) {
//
if (this.menuList[index].disabled) return;
//
if (index === this.current && this.closeOnClickSelf) {
this.close();
//
setTimeout(() => {
this.children[index].active = false;
}, this.duration)
return;
}
this.open(index);
},
//
open(index) {
//
// this.highlightIndex = 9999;
//
this.contentStyle = {
zIndex: 11,
}
//
this.active = true;
this.current = index;
// v-if
// display: nonenvuedisplay
this.children.map((val, idx) => {
val.active = index == idx ? true : false;
})
this.$emit('open', this.current);
},
//
close() {
this.$emit('close', this.current);
// current
this.active = false;
this.current = 99999;
// 0
this.contentStyle = {
zIndex: -1,
opacity: 0
}
},
//
maskClick() {
//
if (!this.closeOnClickMask) return;
this.close();
},
//
highlight(index = undefined) {
this.highlightIndex = index !== undefined ? index : 99999;
},
//
getContentHeight() {
// dropdown
//
// this.$u.sys()uView
let windowHeight = this.$u.sys().windowHeight;
this.$uGetRect('.u-dropdown__menu').then(res => {
// dropdownH5uniappbug(bughx2.8.11)
// H5bugtop沿bottom
// H5uni
// bottonres.top
this.contentHeight = windowHeight - res.bottom;
})
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-dropdown {
flex: 1;
width: 100%;
position: relative;
&__menu {
@include vue-flex;
position: relative;
z-index: 11;
height: 80rpx;
&__item {
flex: 1;
@include vue-flex;
justify-content: center;
align-items: center;
&__text {
font-size: 28rpx;
color: $u-content-color;
}
&__arrow {
margin-left: 6rpx;
transition: transform .3s;
align-items: center;
@include vue-flex;
&--rotate {
transform: rotate(180deg);
}
}
}
}
&__content {
position: absolute;
z-index: 8;
width: 100%;
left: 0px;
bottom: 0;
overflow: hidden;
&__mask {
position: absolute;
z-index: 9;
background: rgba(0, 0, 0, .3);
width: 100%;
left: 0;
top: 0;
bottom: 0;
}
&__popup {
position: relative;
z-index: 10;
transition: all 0.3s;
transform: translate3D(0, -100%, 0);
overflow: hidden;
}
}
}
</style>

View File

@ -0,0 +1,193 @@
<template>
<view class="u-empty" v-if="show" :style="{
marginTop: marginTop + 'rpx'
}">
<u-icon
:name="src ? src : 'empty-' + mode"
:custom-style="iconStyle"
:label="text ? text : icons[mode]"
label-pos="bottom"
:label-color="color"
:label-size="fontSize"
:size="iconSize"
:color="iconColor"
margin-top="14"
></u-icon>
<view class="u-slot-wrap">
<slot name="bottom"></slot>
</view>
</view>
</template>
<script>
/**
* empty 内容为空
* @description 该组件用于需要加载内容但是加载的第一页数据就为空提示一个"没有内容"的场景 我们精心挑选了十几个场景的图标方便您使用
* @tutorial https://www.uviewui.com/components/empty.html
* @property {String} color 文字颜色默认#c0c4cc
* @property {String} text 文字提示默认无内容
* @property {String} src 自定义图标路径如定义mode参数会失效
* @property {String Number} font-size 提示文字的大小单位rpx默认28
* @property {String} mode 内置的图标见官网说明默认data
* @property {String Number} img-width 图标的宽度单位rpx默认240
* @property {String} img-height 图标的高度单位rpx默认auto
* @property {String Number} margin-top 组件距离上一个元素之间的距离默认0
* @property {Boolean} show 是否显示组件默认true
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
* @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
*/
export default {
name: "u-empty",
props: {
//
src: {
type: String,
default: ''
},
//
text: {
type: String,
default: ''
},
//
color: {
type: String,
default: '#c0c4cc'
},
//
iconColor: {
type: String,
default: '#c0c4cc'
},
//
iconSize: {
type: [String, Number],
default: 120
},
// rpx
fontSize: {
type: [String, Number],
default: 26
},
//
mode: {
type: String,
default: 'data'
},
// rpx
imgWidth: {
type: [String, Number],
default: 120
},
// rpx
imgHeight: {
type: [String, Number],
default: 'auto'
},
//
show: {
type: Boolean,
default: true
},
//
marginTop: {
type: [String, Number],
default: 0
},
iconStyle: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
icons: {
car: '购物车为空',
page: '页面不存在',
search: '没有搜索结果',
address: '没有收货地址',
wifi: '没有WiFi',
order: '订单为空',
coupon: '没有优惠券',
favor: '暂无收藏',
permission: '无权限',
history: '无历史记录',
news: '无新闻列表',
message: '消息列表为空',
list: '列表为空',
data: '数据为空'
},
// icons: [{
// icon: 'car',
// text: ''
// },{
// icon: 'page',
// text: ''
// },{
// icon: 'search',
// text: ''
// },{
// icon: 'address',
// text: ''
// },{
// icon: 'wifi',
// text: 'WiFi'
// },{
// icon: 'order',
// text: ''
// },{
// icon: 'coupon',
// text: ''
// },{
// icon: 'favor',
// text: ''
// },{
// icon: 'permission',
// text: ''
// },{
// icon: 'history',
// text: ''
// },{
// icon: 'news',
// text: ''
// },{
// icon: 'message',
// text: ''
// },{
// icon: 'list',
// text: ''
// },{
// icon: 'data',
// text: ''
// }],
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-empty {
@include vue-flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
}
.u-image {
margin-bottom: 20rpx;
}
.u-slot-wrap {
@include vue-flex;
justify-content: center;
align-items: center;
margin-top: 20rpx;
}
</style>

View File

@ -0,0 +1,384 @@
<template>
<view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }">
<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]">
<view class="u-label" :class="[required ? 'u-required' : '']" :style="{
justifyContent: justifyContent,
flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1'
}">
<view class="u-icon-wrap" v-if="icon">
<u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon>
</view>
<slot name="icon"></slot>
<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text>
</view>
<view class="fild-body">
<view class="u-flex-1 u-flex" :style="[inputWrapStyle]">
<textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value"
:placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength"
:focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"
@tap="fieldClick" />
<input
v-else
:style="[fieldStyle]"
:type="type"
class="u-flex-1 u-field__input-wrap"
:value="value"
:password="password || this.type === 'password'"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled"
:maxlength="inputMaxlength"
:focus="focus"
:confirmType="confirmType"
@focus="onFocus"
@blur="onBlur"
@input="onInput"
@confirm="onConfirm"
@tap="fieldClick"
/>
</view>
<u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @click="onClear"/>
<view class="u-button-wrap"><slot name="right" /></view>
<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" />
</view>
</view>
<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{
paddingLeft: labelWidth + 'rpx'
}">{{ errorMessage }}</view>
</view>
</template>
<script>
/**
* field 输入框
* @description 借助此组件可以实现表单的输入 "text""textarea"类型的此外借助uView的picker和actionSheet组件可以快速实现上拉菜单时间地区选择等 为表单解决方案的利器
* @tutorial https://www.uviewui.com/components/field.html
* @property {String} type 输入框的类型默认text
* @property {String} icon label左边的图标限uView的图标名称
* @property {Object} icon-style 左边图标的样式对象形式
* @property {Boolean} right-icon 输入框右边的图标名称限uView的图标名称默认false
* @property {Boolean} required 是否必填左边您显示红色"*"默认false
* @property {String} label 输入框左边的文字提示
* @property {Boolean} password 是否密码输入方式(用点替换文字)type为text时有效默认false
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容且获得焦点时才显示)点击可清空输入框内容默认true
* @property {Number String} label-width label的宽度单位rpx默认130
* @property {String} label-align label的文字对齐方式默认left
* @property {Object} field-style 自定义输入框的样式对象形式
* @property {Number | String} clear-size 清除图标的大小单位rpx默认30
* @property {String} input-align 输入框内容对齐方式默认left
* @property {Boolean} border-bottom 是否显示field的下边框默认true
* @property {Boolean} border-top 是否显示field的上边框默认false
* @property {String} icon-color 左边通过icon配置的图标的颜色默认#606266
* @property {Boolean} auto-height 是否自动增高输入区域type为textarea时有效默认true
* @property {String Boolean} error-message 显示的错误提示内容如果为空字符串或者false则不显示错误信息
* @property {String} placeholder 输入框的提示文字
* @property {String} placeholder-style placeholder的样式(内联样式字符串)"color: #ddd"
* @property {Boolean} focus 是否自动获得焦点默认false
* @property {Boolean} fixed 如果type为textarea且在一个"position:fixed"的区域需要指明为true默认false
* @property {Boolean} disabled 是否不可输入默认false
* @property {Number String} maxlength 最大输入长度设置为 -1 的时候不限制最大长度默认140
* @property {String} confirm-type 设置键盘右下角按钮的文字仅在type="text"时生效默认done
* @event {Function} input 输入框内容发生变化时触发
* @event {Function} focus 输入框获得焦点时触发
* @event {Function} blur 输入框失去焦点时触发
* @event {Function} confirm 点击完成按钮时触发
* @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发
* @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发这样设计是考虑到传递右边的图标一般都为需要弹出"picker"等操作时的场景点击倒三角图标理应发出此事件见上方说明
* @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
*/
export default {
name:"u-field",
props: {
icon: String,
rightIcon: String,
// arrowDirection: {
// type: String,
// default: 'right'
// },
required: Boolean,
label: String,
password: Boolean,
clearable: {
type: Boolean,
default: true
},
// rpx
labelWidth: {
type: [Number, String],
default: 130
},
// left|center|right
labelAlign: {
type: String,
default: 'left'
},
inputAlign: {
type: String,
default: 'left'
},
iconColor: {
type: String,
default: '#606266'
},
autoHeight: {
type: Boolean,
default: true
},
errorMessage: {
type: [String, Boolean],
default: ''
},
placeholder: String,
placeholderStyle: String,
focus: Boolean,
fixed: Boolean,
value: [Number, String],
type: {
type: String,
default: 'text'
},
disabled: {
type: Boolean,
default: false
},
maxlength: {
type: [Number, String],
default: 140
},
confirmType: {
type: String,
default: 'done'
},
// lable left-top-
labelPosition: {
type: String,
default: 'left'
},
//
fieldStyle: {
type: Object,
default() {
return {}
}
},
//
clearSize: {
type: [Number, String],
default: 30
},
// lable
iconStyle: {
type: Object,
default() {
return {}
}
},
//
borderTop: {
type: Boolean,
default: false
},
//
borderBottom: {
type: Boolean,
default: true
},
//
trim: {
type: Boolean,
default: true
}
},
data() {
return {
focused: false,
itemIndex: 0,
};
},
computed: {
inputWrapStyle() {
let style = {};
style.textAlign = this.inputAlign;
// lableleftinput
if(this.labelPosition == 'left') {
style.margin = `0 8rpx`;
} else {
// labletopinput
style.marginRight = `8rpx`;
}
return style;
},
rightIconStyle() {
let style = {};
if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)';
if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)';
else style.transform = 'roate(0deg)';
return style;
},
labelStyle() {
let style = {};
if(this.labelAlign == 'left') style.justifyContent = 'flext-start';
if(this.labelAlign == 'center') style.justifyContent = 'center';
if(this.labelAlign == 'right') style.justifyContent = 'flext-end';
return style;
},
// unicomputedstyle.justifyContent = 'center'
justifyContent() {
if(this.labelAlign == 'left') return 'flex-start';
if(this.labelAlign == 'center') return 'center';
if(this.labelAlign == 'right') return 'flex-end';
},
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength)
},
// label
fieldInnerStyle() {
let style = {};
if(this.labelPosition == 'left') {
style.flexDirection = 'row';
} else {
style.flexDirection = 'column';
}
return style;
}
},
methods: {
onInput(event) {
let value = event.detail.value;
//
if(this.trim) value = this.$u.trim(value);
this.$emit('input', value);
},
onFocus(event) {
this.focused = true;
this.$emit('focus', event);
},
onBlur(event) {
// 使@touchstarthx2.8.4
// @blur
setTimeout(() => {
this.focused = false;
}, 100)
this.$emit('blur', event);
},
onConfirm(e) {
this.$emit('confirm', e.detail.value);
},
onClear(event) {
this.$emit('input', '');
},
rightIconClick() {
this.$emit('right-icon-click');
this.$emit('click');
},
fieldClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-field {
font-size: 28rpx;
padding: 20rpx 28rpx;
text-align: left;
position: relative;
color: $u-main-color;
}
.u-field-inner {
@include vue-flex;
align-items: center;
}
.u-textarea-inner {
align-items: flex-start;
}
.u-textarea-class {
min-height: 96rpx;
width: auto;
font-size: 28rpx;
}
.fild-body {
@include vue-flex;
flex: 1;
align-items: center;
}
.u-arror-right {
margin-left: 8rpx;
}
.u-label-text {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
}
.u-label-left-gap {
margin-left: 6rpx;
}
.u-label-postion-top {
flex-direction: column;
align-items: flex-start;
}
.u-label {
width: 130rpx;
flex: 1 1 130rpx;
text-align: left;
position: relative;
@include vue-flex;
align-items: center;
}
.u-required::before {
content: '*';
position: absolute;
left: -16rpx;
font-size: 14px;
color: $u-type-error;
height: 9px;
line-height: 1;
}
.u-field__input-wrap {
position: relative;
overflow: hidden;
font-size: 28rpx;
height: 48rpx;
flex: 1;
width: auto;
}
.u-clear-icon {
@include vue-flex;
align-items: center;
}
.u-error-message {
color: $u-type-error;
font-size: 26rpx;
text-align: left;
}
.placeholder-style {
color: rgb(150, 151, 153);
}
.u-input-class {
font-size: 28rpx;
}
.u-button-wrap {
margin-left: 8rpx;
}
</style>

View File

@ -0,0 +1,431 @@
<template>
<view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}">
<view class="u-form-item__body" :style="{
flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
}">
<!-- 微信小程序中将一个参数设置空字符串结果会变成字符串"true" -->
<view class="u-form-item--left" :style="{
width: uLabelWidth,
flex: `0 0 ${uLabelWidth}`,
marginBottom: elLabelPosition == 'left' ? 0 : '10rpx',
}">
<!-- 为了块对齐 -->
<view class="u-form-item--left__content" v-if="required || leftIcon || label">
<!-- nvue不支持伪元素before -->
<text v-if="required" class="u-form-item--left__content--required">*</text>
<view class="u-form-item--left__content__icon" v-if="leftIcon">
<u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon>
</view>
<view class="u-form-item--left__content__label" :style="[elLabelStyle, {
'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end'
}]">
{{label}}
</view>
</view>
</view>
<view class="u-form-item--right u-flex">
<view class="u-form-item--right__content">
<view class="u-form-item--right__content__slot ">
<slot />
</view>
<view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon">
<u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon>
<slot name="right" />
</view>
</view>
</view>
</view>
<view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{
paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0',
}">{{validateMessage}}</view>
</view>
</template>
<script>
import Emitter from '../../libs/util/emitter.js';
import schema from '../../libs/util/async-validator';
//
schema.warning = function() {};
/**
* form-item 表单item
* @description 此组件一般用于表单场景可以配置Input输入框Select弹出框进行表单验证等
* @tutorial http://uviewui.com/components/form.html
* @property {String} label 左侧提示文字
* @property {Object} prop 表单域model对象的属性名在使用 validateresetFields 方法的情况下该属性是必填的
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
* @property {String} label-position 表单域提示文字的位置left-左侧top-上方
* @property {String Number} label-width 提示文字的宽度单位rpx默认90
* @property {Object} label-style lable的样式对象形式
* @property {String} label-align lable的对齐方式
* @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址
* @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址
* @property {Object} left-icon-style 左侧图标的样式对象形式
* @property {Object} right-icon-style 右侧图标的样式对象形式
* @property {Boolean} required 是否显示左边的"*"这里仅起展示作用如需校验必填请通过rules配置必填规则(默认false)
* @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item>
*/
export default {
name: 'u-form-item',
mixins: [Emitter],
inject: {
uForm: {
default () {
return null
}
}
},
props: {
// inputlabel
label: {
type: String,
default: ''
},
//
prop: {
type: String,
default: ''
},
// 线
borderBottom: {
type: [String, Boolean],
default: ''
},
// labelleft-top-
labelPosition: {
type: String,
default: ''
},
// labelrpx
labelWidth: {
type: [String, Number],
default: ''
},
// lable
labelStyle: {
type: Object,
default () {
return {}
}
},
// lable
labelAlign: {
type: String,
default: ''
},
//
rightIcon: {
type: String,
default: ''
},
//
leftIcon: {
type: String,
default: ''
},
//
leftIconStyle: {
type: Object,
default () {
return {}
}
},
//
rightIconStyle: {
type: Object,
default () {
return {}
}
},
// rules
required: {
type: Boolean,
default: false
}
},
data() {
return {
initialValue: '', //
// isRequired: false, // "*"propsrequiredrules
validateState: '', //
validateMessage: '', //
// message-border-input
errorType: ['message'],
fieldValue: '', // input
// computedthis.parentdata
parentData: {
borderBottom: true,
labelWidth: 90,
labelPosition: 'left',
labelStyle: {},
labelAlign: 'left',
}
};
},
watch: {
validateState(val) {
this.broadcastInputError();
},
// u-formerrorType
"uForm.errorType"(val) {
this.errorType = val;
this.broadcastInputError();
},
},
computed: {
// labelcomputed
uLabelWidth() {
// label('true')labelauto
return this.elLabelPosition == 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.$u.addUnit(this
.elLabelWidth)) : '100%';
},
showError() {
return type => {
// errorTypenonetoast
if (this.errorType.indexOf('none') >= 0) return false;
else if (this.errorType.indexOf(type) >= 0) return true;
else return false;
}
},
// label
elLabelWidth() {
// label90使(0)u-form
return (this.labelWidth != 0 || this.labelWidth != '') ? this.labelWidth : (this.parentData.labelWidth ? this.parentData
.labelWidth :
90);
},
// label
elLabelStyle() {
return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parentData.labelStyle ? this.parentData.labelStyle :
{});
},
// label
elLabelPosition() {
return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition :
'left');
},
// label
elLabelAlign() {
return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left');
},
// label线
elBorderBottom() {
// borderBottom使
return this.borderBottom !== '' ? this.borderBottom : this.parentData.borderBottom ? this.parentData.borderBottom :
true;
}
},
methods: {
broadcastInputError() {
// truefalsetrue
this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'));
},
// required
setRules() {
let that = this;
// "*"propsrequiredrules
// u-formu-form-item
// let rules = this.getRules();
// if (rules.length) {
// this.isRequired = rules.some(rule => {
// // undefined
// return rule.required;
// });
// }
// blur
this.$on('on-form-blur', that.onFieldBlur);
// change
this.$on('on-form-change', that.onFieldChange);
},
// u-formrulesu-form-item
getRules() {
//
let rules = this.parent.rules;
rules = rules ? rules[this.prop] : [];
//
return [].concat(rules || []);
},
// blur
onFieldBlur() {
this.validation('blur');
},
// change
onFieldChange() {
this.validation('change');
},
// rule
getFilteredRule(triggerType = '') {
let rules = this.getRules();
// triggerType
if (!triggerType) return rules;
// blurchange
// 使indexOftrigger['blur','change']
// trigger
return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1);
},
//
validation(trigger, callback = () => {}) {
//
this.fieldValue = this.parent.model[this.prop];
// blurchange
let rules = this.getFilteredRule(trigger);
// u-form
// count
if (!rules || rules.length === 0) {
return callback('');
}
//
this.validateState = 'validating';
// async-validator
let validator = new schema({
[this.prop]: rules
});
validator.validate({
[this.prop]: this.fieldValue
}, {
firstFields: true
}, (errors, fields) => {
//
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
//
callback(this.validateMessage);
});
},
// u-form-item
resetField() {
this.parent.model[this.prop] = this.initialValue;
// `success`
this.validateState = 'success';
}
},
// u-form
mounted() {
// provide/inject使created
this.parent = this.$u.$parent.call(this, 'u-form');
if (this.parent) {
// parentDataparentparentData
Object.keys(this.parentData).map(key => {
this.parentData[key] = this.parent[key];
});
// propuForm(u-form-input使uForm)
if (this.prop) {
//
this.parent.fields.push(this);
this.errorType = this.parent.errorType;
//
this.initialValue = this.fieldValue;
// $nextTicku-formrulesref
// $nextTickrefu-form
this.$nextTick(() => {
this.setRules();
})
}
}
},
// u-form
beforeDestroy() {
// prop
if (this.parent && this.prop) {
this.parent.fields.map((item, index) => {
if (item === this) this.parent.fields.splice(index, 1);
})
}
},
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-form-item {
@include vue-flex;
// align-items: flex-start;
padding: 20rpx 0;
font-size: 28rpx;
color: $u-main-color;
box-sizing: border-box;
line-height: $u-form-item-height;
flex-direction: column;
&__border-bottom--error:after {
border-color: $u-type-error;
}
&__body {
@include vue-flex;
}
&--left {
@include vue-flex;
align-items: center;
&__content {
position: relative;
@include vue-flex;
align-items: center;
padding-right: 10rpx;
flex: 1;
&__icon {
margin-right: 8rpx;
}
&--required {
position: absolute;
left: -16rpx;
vertical-align: middle;
color: $u-type-error;
padding-top: 6rpx;
}
&__label {
@include vue-flex;
align-items: center;
flex: 1;
}
}
}
&--right {
flex: 1;
&__content {
@include vue-flex;
align-items: center;
flex: 1;
&__slot {
flex: 1;
/* #ifndef MP */
@include vue-flex;
align-items: center;
/* #endif */
}
&__icon {
margin-left: 10rpx;
color: $u-light-color;
font-size: 30rpx;
}
}
}
&__message {
font-size: 24rpx;
line-height: 24rpx;
color: $u-type-error;
margin-top: 12rpx;
}
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<view class="u-form"><slot /></view>
</template>
<script>
/**
* form 表单
* @description 此组件一般用于表单场景可以配置Input输入框Select弹出框进行表单验证等
* @tutorial http://uviewui.com/components/form.html
* @property {Object} model 表单数据对象
* @property {Boolean} border-bottom 是否显示表单域的下划线边框
* @property {String} label-position 表单域提示文字的位置left-左侧top-上方
* @property {String Number} label-width 提示文字的宽度单位rpx默认90
* @property {Object} label-style lable的样式对象形式
* @property {String} label-align lable的对齐方式
* @property {Object} rules 通过ref设置见官网说明
* @property {Array} error-type 错误的提示方式数组形式见上方说明(默认['message'])
* @example <u-form :model="form" ref="uForm"></u-form>
*/
export default {
name: 'u-form',
props: {
// form
model: {
type: Object,
default() {
return {};
}
},
//
// rules: {
// type: [Object, Function, Array],
// default() {
// return {};
// }
// },
// message-border-input
// border-bottom-none-
errorType: {
type: Array,
default() {
return ['message', 'toast']
}
},
// 线
borderBottom: {
type: Boolean,
default: true
},
// labelleft-top-
labelPosition: {
type: String,
default: 'left'
},
// labelrpx
labelWidth: {
type: [String, Number],
default: 90
},
// lable
labelAlign: {
type: String,
default: 'left'
},
// lable
labelStyle: {
type: Object,
default() {
return {}
}
},
},
provide() {
return {
uForm: this
};
},
data() {
return {
rules: {}
};
},
created() {
// formu-form-item
// data
this.fields = [];
},
methods: {
setRules(rules) {
this.rules = rules;
},
// u-form-itemu-form-itemresetField()
resetFields() {
this.fields.map(field => {
field.resetField();
});
},
//
validate(callback) {
return new Promise(resolve => {
// u-form-item
let valid = true; //
let count = 0; //
let errorArr = []; //
this.fields.map(field => {
// u-form-itemvalidation
field.validation('', error => {
// u-form-item
if (error) {
valid = false;
errorArr.push(error);
}
// u-form-itempromisethen
if (++count === this.fields.length) {
resolve(valid); // promisethen
// toast
if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) {
this.$u.toast(errorArr[0]);
}
//
if (typeof callback == 'function') callback(valid);
}
});
});
});
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
</style>

View File

@ -0,0 +1,52 @@
<template>
<u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm">
<view class="u-update-content">
<rich-text :nodes="content"></rich-text>
</view>
</u-modal>
</template>
<script>
export default {
data() {
return {
show: false,
content: `
1. 修复badge组件的size参数无效问题<br>
2. 新增Modal模态框组件<br>
3. 新增压窗屏组件可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br>
4. 修复键盘组件在微信小程序上遮罩无效的问题
`,
}
},
onReady() {
this.show = true;
},
methods: {
cancel() {
this.closeModal();
},
confirm() {
this.closeModal();
},
closeModal() {
uni.navigateBack();
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-full-content {
background-color: #00C777;
}
.u-update-content {
font-size: 26rpx;
color: $u-content-color;
line-height: 1.7;
padding: 30rpx;
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<view class="u-gap" :style="[gapStyle]"></view>
</template>
<script>
/**
* gap 间隔槽
* @description 该组件一般用于内容块之间的用一个灰色块隔开的场景方便用户风格统一减少工作量
* @tutorial https://www.uviewui.com/components/gap.html
* @property {String} bg-color 背景颜色默认#f3f4f6
* @property {String Number} height 分割槽高度单位rpx默认30
* @property {String Number} margin-top 与前一个组件的距离单位rpx默认0
* @property {String Number} margin-bottom 与后一个组件的距离单位rpx0
* @example <u-gap height="80" bg-color="#bbb"></u-gap>
*/
export default {
name: "u-gap",
props: {
bgColor: {
type: String,
default: 'transparent ' //
},
//
height: {
type: [String, Number],
default: 30
},
//
marginTop: {
type: [String, Number],
default: 0
},
//
marginBottom: {
type: [String, Number],
default: 0
},
},
computed: {
gapStyle() {
return {
backgroundColor: this.bgColor,
height: this.height + 'rpx',
marginTop: this.marginTop + 'rpx',
marginBottom: this.marginBottom + 'rpx'
};
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
</style>

View File

@ -0,0 +1,126 @@
<template>
<view class="u-grid-item" :hover-class="parentData.hoverClass"
:hover-stay-time="200" @tap="click" :style="{
background: bgColor,
width: width,
}">
<view class="u-grid-item-box" :style="[customStyle]" :class="[parentData.border ? 'u-border-right u-border-bottom' : '']">
<slot />
</view>
</view>
</template>
<script>
/**
* gridItem 提示
* @description 宫格组件一般用于同时展示多个同类项目的场景可以给宫格的项目设置徽标组件(badge)或者图标等也可以扩展为左右滑动的轮播形式搭配u-grid使用
* @tutorial https://www.uviewui.com/components/grid.html
* @property {String} bg-color 宫格的背景颜色默认#ffffff
* @property {String Number} index 点击宫格时返回的值
* @property {Object} custom-style 自定义样式对象形式
* @event {Function} click 点击宫格触发
* @example <u-grid-item></u-grid-item>
*/
export default {
name: "u-grid-item",
props: {
//
bgColor: {
type: String,
default: '#ffffff'
},
// index
index: {
type: [Number, String],
default: ''
},
//
customStyle: {
type: Object,
default() {
return {
padding: '30rpx 0'
}
}
}
},
data() {
return {
parentData: {
hoverClass: '', //
col: 3, //
border: true, //
}
};
},
created() {
//
this.updateParentData();
// this.parentupdateParentData()
this.parent.children.push(this);
},
computed: {
// grid-item
width() {
return 100 / Number(this.parentData.col) + '%';
},
},
methods: {
//
updateParentData() {
// mixin
this.getParentData('u-grid');
},
click() {
this.$emit('click', this.index);
this.parent && this.parent.click(this.index);
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-grid-item {
box-sizing: border-box;
background: #fff;
@include vue-flex;
align-items: center;
justify-content: center;
position: relative;
flex-direction: column;
/* #ifdef MP */
position: relative;
float: left;
/* #endif */
}
.u-grid-item-hover {
background: #f7f7f7 !important;
}
.u-grid-marker-box {
position: absolute;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
line-height: 0;
}
.u-grid-marker-wrap {
position: absolute;
}
.u-grid-item-box {
padding: 30rpx 0;
@include vue-flex;
align-items: center;
justify-content: center;
flex-direction: column;
flex: 1;
width: 100%;
height: 100%;
}
</style>

View File

@ -0,0 +1,108 @@
<template>
<view class="u-grid" :class="{'u-border-top u-border-left': border}" :style="[gridStyle]"><slot /></view>
</template>
<script>
/**
* grid 宫格布局
* @description 宫格组件一般用于同时展示多个同类项目的场景可以给宫格的项目设置徽标组件(badge)或者图标等也可以扩展为左右滑动的轮播形式
* @tutorial https://www.uviewui.com/components/grid.html
* @property {String Number} col 宫格的列数默认3
* @property {Boolean} border 是否显示宫格的边框默认true
* @property {Boolean} hover-class 点击宫格的时候是否显示按下的灰色背景默认false
* @event {Function} click 点击宫格触发
* @example <u-grid :col="3" @click="click"></u-grid>
*/
export default {
name: 'u-grid',
props: {
//
col: {
type: [Number, String],
default: 3
},
//
border: {
type: Boolean,
default: true
},
//
align: {
type: String,
default: 'left'
},
// "none"
hoverClass: {
type: String,
default: 'u-hover-class'
}
},
data() {
return {
index: 0,
}
},
watch: {
//
parentData() {
if(this.children.length) {
this.children.map(child => {
// (u-radio)updateParentData()
typeof(child.updateParentData) == 'function' && child.updateParentData();
})
}
},
},
created() {
// childrendata
this.children = [];
},
computed: {
//
parentData() {
return [this.hoverClass, this.col, this.size, this.border];
},
//
gridStyle() {
let style = {};
switch(this.align) {
case 'left':
style.justifyContent = 'flex-start';
break;
case 'center':
style.justifyContent = 'center';
break;
case 'right':
style.justifyContent = 'flex-end';
break;
default: style.justifyContent = 'flex-start';
};
return style;
}
},
methods: {
click(index) {
this.$emit('click', index);
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-grid {
width: 100%;
/* #ifdef MP */
position: relative;
box-sizing: border-box;
overflow: hidden;
/* #endif */
/* #ifndef MP */
@include vue-flex;
flex-wrap: wrap;
align-items: center;
/* #endif */
}
</style>

View File

@ -0,0 +1,336 @@
<template>
<view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]">
<image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image>
<text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass"
@touchstart="touchstart">
<text v-if="showDecimalIcon" :style="[decimalIconStyle]" :class="decimalIconClass" :hover-class="hoverClass"
class="u-icon__decimal">
</text>
</text>
<!-- 这里进行空字符串判断如果仅仅是v-if="label"可能会出现传递0的时候结果也无法显示 -->
<text v-if="label !== ''" class="u-icon__label" :style="{
color: labelColor,
fontSize: $u.addUnit(labelSize),
marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0,
marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0,
marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0,
marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0,
}">{{ label }}
</text>
</view>
</template>
<script>
/**
* icon 图标
* @description 基于字体的图标集包含了大多数常见场景的图标
* @tutorial https://www.uviewui.com/components/icon.html
* @property {String} name 图标名称见示例图标集
* @property {String} color 图标颜色默认inherit
* @property {String | Number} size 图标字体大小单位rpx默认32
* @property {String | Number} label-size label字体大小单位rpx默认28
* @property {String} label 图标右侧的label文字默认28
* @property {String} label-pos label文字相对于图标的位置只能right或bottom默认right
* @property {String} label-color label字体颜色默认#606266
* @property {Object} custom-style icon的样式对象形式
* @property {String} custom-prefix 自定义字体图标库时需要写上此值
* @property {String | Number} margin-left label在右侧时与图标的距离单位rpx默认6
* @property {String | Number} margin-top label在下方时与图标的距离单位rpx默认6
* @property {String | Number} margin-bottom label在上方时与图标的距离单位rpx默认6
* @property {String | Number} margin-right label在左侧时与图标的距离单位rpx默认6
* @property {String} label-pos label相对于图标的位置只能right或bottom默认right
* @property {String} index 一个用于区分多个图标的值点击图标时通过click事件传出
* @property {String} hover-class 图标按下去的样式类用法同uni的view组件的hover-class参数详情见官网
* @property {String} width 显示图片小图标时的宽度
* @property {String} height 显示图片小图标时的高度
* @property {String} top 图标在垂直方向上的定位
* @property {String} top 图标在垂直方向上的定位
* @property {String} top 图标在垂直方向上的定位
* @property {Boolean} show-decimal-icon 是否为DecimalIcon
* @property {String} inactive-color 背景颜色可接受主题色仅Decimal时有效
* @property {String | Number} percent 显示的百分比仅Decimal时有效
* @event {Function} click 点击图标时触发
* @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
*/
export default {
name: 'u-icon',
props: {
//
name: {
type: String,
default: ''
},
//
color: {
type: String,
default: ''
},
// rpx
size: {
type: [Number, String],
default: 'inherit'
},
//
bold: {
type: Boolean,
default: false
},
// index
index: {
type: [Number, String],
default: ''
},
//
hoverClass: {
type: String,
default: ''
},
// 便
customPrefix: {
type: String,
default: 'uicon'
},
//
label: {
type: [String, Number],
default: ''
},
// label
labelPos: {
type: String,
default: 'right'
},
// label
labelSize: {
type: [String, Number],
default: '28'
},
// label
labelColor: {
type: String,
default: '#606266'
},
// label()
marginLeft: {
type: [String, Number],
default: '6'
},
// label()
marginTop: {
type: [String, Number],
default: '6'
},
// label()
marginRight: {
type: [String, Number],
default: '6'
},
// label()
marginBottom: {
type: [String, Number],
default: '6'
},
// mode
imgMode: {
type: String,
default: 'widthFix'
},
//
customStyle: {
type: Object,
default() {
return {}
}
},
//
width: {
type: [String, Number],
default: ''
},
//
height: {
type: [String, Number],
default: ''
},
//
top: {
type: [String, Number],
default: 0
},
// DecimalIcon
showDecimalIcon: {
type: Boolean,
default: false
},
// Decimal
inactiveColor: {
type: String,
default: '#ececec'
},
// Decimal
percent: {
type: [Number, String],
default: '50'
}
},
computed: {
customClass() {
let classes = []
classes.push(this.customPrefix + '-' + this.name)
// uViewu-iconfont
if (this.customPrefix == 'uicon') {
classes.push('u-iconfont')
} else {
classes.push(this.customPrefix)
}
//
if (this.showDecimalIcon && this.inactiveColor && this.$u.config.type.includes(this.inactiveColor)) {
classes.push('u-icon__icon--' + this.inactiveColor)
} else if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
// 使[a, b, c]
//
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
classes = classes.join(' ')
//#endif
return classes
},
iconStyle() {
let style = {}
style = {
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
fontWeight: this.bold ? 'bold' : 'normal',
//
top: this.$u.addUnit(this.top)
}
//
if (this.showDecimalIcon && this.inactiveColor && !this.$u.config.type.includes(this.inactiveColor)) {
style.color = this.inactiveColor
} else if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
return style
},
// name"/"
isImg() {
return this.name.indexOf('/') !== -1
},
imgStyle() {
let style = {}
// widthheight使使size
style.width = this.width ? this.$u.addUnit(this.width) : this.$u.addUnit(this.size)
style.height = this.height ? this.$u.addUnit(this.height) : this.$u.addUnit(this.size)
return style
},
decimalIconStyle() {
let style = {}
style = {
fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
fontWeight: this.bold ? 'bold' : 'normal',
//
top: this.$u.addUnit(this.top),
width: this.percent + '%'
}
//
if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
return style
},
decimalIconClass() {
let classes = []
classes.push(this.customPrefix + '-' + this.name)
// uViewu-iconfont
if (this.customPrefix == 'uicon') {
classes.push('u-iconfont')
} else {
classes.push(this.customPrefix)
}
//
if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
else classes.push('u-icon__icon--primary')
// 使[a, b, c]
//
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
classes = classes.join(' ')
//#endif
return classes
}
},
methods: {
click() {
this.$emit('click', this.index)
},
touchstart() {
this.$emit('touchstart', this.index)
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
@import '../../iconfont.css';
.u-icon {
display: inline-flex;
align-items: center;
&--left {
flex-direction: row-reverse;
align-items: center;
}
&--right {
flex-direction: row;
align-items: center;
}
&--top {
flex-direction: column-reverse;
justify-content: center;
}
&--bottom {
flex-direction: column;
justify-content: center;
}
&__icon {
position: relative;
&--primary {
color: $u-type-primary;
}
&--success {
color: $u-type-success;
}
&--error {
color: $u-type-error;
}
&--warning {
color: $u-type-warning;
}
&--info {
color: $u-type-info;
}
}
&__decimal {
position: absolute;
top: 0;
left: 0;
display: inline-block;
overflow: hidden;
}
&__img {
height: auto;
will-change: transform;
}
&__label {
line-height: 1;
}
}
</style>

View File

@ -0,0 +1,268 @@
<template>
<view class="u-image" @tap="onClick" :style="[wrapStyle, backgroundStyle]">
<image
v-if="!isError"
:src="src"
:mode="mode"
@error="onErrorHandler"
@load="onLoadHandler"
:lazy-load="lazyLoad"
class="u-image__image"
:show-menu-by-longpress="showMenuByLongpress"
:style="{
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius)
}"
></image>
<view
v-if="showLoading && loading"
class="u-image__loading"
:style="{
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
backgroundColor: this.bgColor
}"
>
<slot v-if="$slots.loading" name="loading" />
<u-icon v-else :name="loadingIcon" :width="width" :height="height"></u-icon>
</view>
<view
v-if="showError && isError && !loading"
class="u-image__error"
:style="{
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius)
}"
>
<slot v-if="$slots.error" name="error" />
<u-icon v-else :name="errorIcon" :width="width" :height="height"></u-icon>
</view>
</view>
</template>
<script>
/**
* Image 图片
* @description 此组件为uni-app的image组件的加强版在继承了原有功能外还支持淡入动画加载中加载失败提示圆角值和形状等
* @tutorial https://uviewui.com/components/image.html
* @property {String} src 图片地址
* @property {String} mode 裁剪模式见官网说明
* @property {String | Number} width 宽度单位任意如果为数值则为rpx单位默认100%
* @property {String | Number} height 高度单位任意如果为数值则为rpx单位默认 auto
* @property {String} shape 图片形状circle-圆形square-方形默认square
* @property {String | Number} border-radius 圆角值单位任意如果为数值则为rpx单位默认 0
* @property {Boolean} lazy-load 是否懒加载仅微信小程序App百度小程序字节跳动小程序有效默认 true
* @property {Boolean} show-menu-by-longpress 是否开启长按图片显示识别小程序码菜单仅微信小程序有效默认 false
* @property {String} loading-icon 加载中的图标或者小图片默认 photo
* @property {String} error-icon 加载失败的图标或者小图片默认 error-circle
* @property {Boolean} show-loading 是否显示加载中的图标或者自定义的slot默认 true
* @property {Boolean} show-error 是否显示加载错误的图标或者自定义的slot默认 true
* @property {Boolean} fade 是否需要淡入效果默认 true
* @property {String Number} width 传入图片路径时图片的宽度
* @property {String Number} height 传入图片路径时图片的高度
* @property {Boolean} webp 只支持网络资源只对微信小程序有效默认 false
* @property {String | Number} duration 搭配fade参数的过渡时间单位ms默认 500
* @event {Function} click 点击图片时触发
* @event {Function} error 图片加载失败时触发
* @event {Function} load 图片加载成功时触发
* @example <u-image width="100%" height="300rpx" :src="src"></u-image>
*/
export default {
name: 'u-image',
props: {
//
src: {
type: String,
default: ''
},
//
mode: {
type: String,
default: 'aspectFill'
},
//
width: {
type: [String, Number],
default: '100%'
},
//
height: {
type: [String, Number],
default: 'auto'
},
// circle-square-
shape: {
type: String,
default: 'square'
},
//
borderRadius: {
type: [String, Number],
default: 0
},
// App
lazyLoad: {
type: Boolean,
default: true
},
//
showMenuByLongpress: {
type: Boolean,
default: true
},
//
loadingIcon: {
type: String,
default: 'photo'
},
//
errorIcon: {
type: String,
default: 'error-circle'
},
// slot
showLoading: {
type: Boolean,
default: true
},
// slot
showError: {
type: Boolean,
default: true
},
//
fade: {
type: Boolean,
default: true
},
//
webp: {
type: Boolean,
default: false
},
// ms
duration: {
type: [String, Number],
default: 500
},
//
bgColor: {
type: String,
default: '#f3f4f6'
}
},
data() {
return {
//
isError: false,
//
loading: true,
//
opacity: 1,
// props
durationTime: this.duration,
// png
backgroundStyle: {}
};
},
watch: {
src: {
immediate: true,
handler (n) {
if(!n) {
// null''falseundefined
this.isError = true;
this.loading = false;
} else {
this.isError = false;
this.loading = true;
}
}
}
},
computed: {
wrapStyle() {
let style = {};
// addUnit()pxrpx
style.width = this.$u.addUnit(this.width);
style.height = this.$u.addUnit(this.height);
// 50%
style.borderRadius = this.shape == 'circle' ? '50%' : this.$u.addUnit(this.borderRadius);
// hidden
style.overflow = this.borderRadius > 0 ? 'hidden' : 'visible';
if (this.fade) {
style.opacity = this.opacity;
style.transition = `opacity ${Number(this.durationTime) / 1000}s ease-in-out`;
}
return style;
}
},
methods: {
//
onClick() {
this.$emit('click');
},
//
onErrorHandler(err) {
this.loading = false;
this.isError = true;
this.$emit('error', err);
},
// loading
onLoadHandler() {
this.loading = false;
this.isError = false;
this.$emit('load');
//
// fadepng
if (!this.fade) return this.removeBgColor();
// opacity1()0()1
this.opacity = 0;
// 00duration()
//
this.durationTime = 0;
// 50msH5
setTimeout(() => {
this.durationTime = this.duration;
this.opacity = 1;
setTimeout(() => {
this.removeBgColor();
}, this.durationTime);
}, 50);
},
//
removeBgColor() {
// png
this.backgroundStyle = {
backgroundColor: 'transparent'
};
}
}
};
</script>
<style scoped lang="scss">
@import '../../libs/css/style.components.scss';
.u-image {
position: relative;
transition: opacity 0.5s ease-in-out;
&__image {
width: 100%;
height: 100%;
}
&__loading,
&__error {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
background-color: $u-bg-color;
color: $u-tips-color;
font-size: 46rpx;
}
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸所以在外面套一个"壳" -->
<view>
<view class="u-index-anchor-wrapper" :id="$u.guid()" :style="[wrapperStyle]">
<view class="u-index-anchor " :class="[active ? 'u-index-anchor--active' : '']" :style="[customAnchorStyle]">
<slot v-if="useSlot" />
<block v-else>
<text>{{ index }}</text>
</block>
</view>
</view>
</view>
</template>
<script>
/**
* indexAnchor 索引列表锚点
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
* @property {Boolean} use-slot 是否使用自定义内容的插槽默认false
* @property {String Number} index 索引字符如果定义了use-slot此参数自动失效
* @property {Object} custStyle 自定义样式对象形式"{color: 'red'}"
* @event {Function} default 锚点位置显示内容默认为索引字符
* @example <u-index-anchor :index="item" />
*/
export default {
name: "u-index-anchor",
props: {
useSlot: {
type: Boolean,
default: false
},
index: {
type: String,
default: ''
},
customStyle: {
type: Object,
default () {
return {}
}
}
},
data() {
return {
active: false,
wrapperStyle: {},
anchorStyle: {}
}
},
created() {
this.parent = false;
},
mounted() {
this.parent = this.$u.$parent.call(this, 'u-index-list');
if(this.parent) {
this.parent.children.push(this);
this.parent.updateData();
}
},
computed: {
customAnchorStyle() {
return Object.assign(this.anchorStyle, this.customStyle);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-index-anchor {
box-sizing: border-box;
padding: 14rpx 24rpx;
color: #606266;
width: 100%;
font-weight: 500;
font-size: 28rpx;
line-height: 1.2;
background-color: rgb(245, 245, 245);
}
.u-index-anchor--active {
right: 0;
left: 0;
color: #2979ff;
background-color: #fff;
}
</style>

View File

@ -0,0 +1,315 @@
<template>
<!-- 支付宝小程序使用$u.getRect()获取组件的根元素尺寸所以在外面套一个"壳" -->
<view>
<view class="u-index-bar">
<slot />
<view v-if="showSidebar" class="u-index-bar__sidebar" @touchstart.stop.prevent="onTouchMove" @touchmove.stop.prevent="onTouchMove"
@touchend.stop.prevent="onTouchStop" @touchcancel.stop.prevent="onTouchStop">
<view v-for="(item, index) in indexList" :key="index" class="u-index-bar__index" :style="{zIndex: zIndex + 1, color: activeAnchorIndex === index ? activeColor : ''}"
:data-index="index">
{{ item }}
</view>
</view>
<view class="u-indexed-list-alert" v-if="touchmove && indexList[touchmoveIndex]" :style="{
zIndex: alertZIndex
}">
<text>{{indexList[touchmoveIndex]}}</text>
</view>
</view>
</view>
</template>
<script>
var indexList = function() {
var indexList = [];
var charCodeOfA = 'A'.charCodeAt(0);
for (var i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i));
}
return indexList;
};
/**
* indexList 索引列表
* @description 通过折叠面板收纳内容区域,搭配<u-index-anchor>使用
* @tutorial https://www.uviewui.com/components/indexList.html#indexanchor-props
* @property {Number String} scroll-top 当前滚动高度自定义组件无法获得滚动条事件所以依赖接入方传入
* @property {Array} index-list 索引字符列表数组默认A-Z
* @property {Number String} z-index 锚点吸顶时的层级默认965
* @property {Boolean} sticky 是否开启锚点自动吸顶默认true
* @property {Number String} offset-top 锚点自动吸顶时与顶部的距离默认0
* @property {String} highlight-color 锚点和右边索引字符高亮颜色默认#2979ff
* @event {Function} select 选中右边索引字符时触发
* @example <u-index-list :scrollTop="scrollTop"></u-index-list>
*/
export default {
name: "u-index-list",
props: {
sticky: {
type: Boolean,
default: true
},
zIndex: {
type: [Number, String],
default: ''
},
scrollTop: {
type: [Number, String],
default: 0,
},
offsetTop: {
type: [Number, String],
default: 0
},
indexList: {
type: Array,
default () {
return indexList()
}
},
activeColor: {
type: String,
default: '#2979ff'
}
},
created() {
// #ifdef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 44;
// #endif
// #ifndef H5
this.stickyOffsetTop = this.offsetTop ? uni.upx2px(this.offsetTop) : 0;
// #endif
// createdchildrendata
this.children = [];
},
data() {
return {
activeAnchorIndex: 0,
showSidebar: true,
// children: [],
touchmove: false,
touchmoveIndex: 0,
}
},
watch: {
scrollTop() {
this.updateData()
}
},
computed: {
// toastz-index
alertZIndex() {
return this.$u.zIndex.toast;
}
},
methods: {
updateData() {
this.timer && clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.showSidebar = !!this.children.length;
this.setRect().then(() => {
this.onScroll();
});
}, 0);
},
setRect() {
return Promise.all([
this.setAnchorsRect(),
this.setListRect(),
this.setSiderbarRect()
]);
},
setAnchorsRect() {
return Promise.all(this.children.map((anchor, index) => anchor
.$uGetRect('.u-index-anchor-wrapper')
.then((rect) => {
Object.assign(anchor, {
height: rect.height,
top: rect.top
});
})));
},
setListRect() {
return this.$uGetRect('.u-index-bar').then((rect) => {
Object.assign(this, {
height: rect.height,
top: rect.top + this.scrollTop
});
});
},
setSiderbarRect() {
return this.$uGetRect('.u-index-bar__sidebar').then(rect => {
this.sidebar = {
height: rect.height,
top: rect.top
};
});
},
getActiveAnchorIndex() {
const {
children
} = this;
const {
sticky
} = this;
for (let i = this.children.length - 1; i >= 0; i--) {
const preAnchorHeight = i > 0 ? children[i - 1].height : 0;
const reachTop = sticky ? preAnchorHeight : 0;
if (reachTop >= children[i].top) {
return i;
}
}
return -1;
},
onScroll() {
const {
children = []
} = this;
if (!children.length) {
return;
}
const {
sticky,
stickyOffsetTop,
zIndex,
scrollTop,
activeColor
} = this;
const active = this.getActiveAnchorIndex();
this.activeAnchorIndex = active;
if (sticky) {
let isActiveAnchorSticky = false;
if (active !== -1) {
isActiveAnchorSticky =
children[active].top <= 0;
}
children.forEach((item, index) => {
if (index === active) {
let wrapperStyle = '';
let anchorStyle = {
color: `${activeColor}`
};
if (isActiveAnchorSticky) {
wrapperStyle = {
height: `${children[index].height}px`
};
anchorStyle = {
position: 'fixed',
top: `${stickyOffsetTop}px`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
};
}
item.active = active;
item.wrapperStyle = wrapperStyle;
item.anchorStyle = anchorStyle;
} else if (index === active - 1) {
const currentAnchor = children[index];
const currentOffsetTop = currentAnchor.top;
const targetOffsetTop = index === children.length - 1 ?
this.top :
children[index + 1].top;
const parentOffsetHeight = targetOffsetTop - currentOffsetTop;
const translateY = parentOffsetHeight - currentAnchor.height;
const anchorStyle = {
position: 'relative',
transform: `translate3d(0, ${translateY}px, 0)`,
zIndex: `${zIndex ? zIndex : this.$u.zIndex.indexListSticky}`,
color: `${activeColor}`
};
item.active = active;
item.anchorStyle = anchorStyle;
} else {
item.active = false;
item.anchorStyle = '';
item.wrapperStyle = '';
}
});
}
},
onTouchMove(event) {
this.touchmove = true;
const sidebarLength = this.children.length;
const touch = event.touches[0];
const itemHeight = this.sidebar.height / sidebarLength;
let clientY = 0;
clientY = touch.clientY;
let index = Math.floor((clientY - this.sidebar.top) / itemHeight);
if (index < 0) {
index = 0;
} else if (index > sidebarLength - 1) {
index = sidebarLength - 1;
}
this.touchmoveIndex = index;
this.scrollToAnchor(index);
},
onTouchStop() {
this.touchmove = false;
this.scrollToAnchorIndex = null;
},
scrollToAnchor(index) {
if (this.scrollToAnchorIndex === index) {
return;
}
this.scrollToAnchorIndex = index;
const anchor = this.children.find((item) => item.index === this.indexList[index]);
if (anchor) {
this.$emit('select', anchor.index);
uni.pageScrollTo({
duration: 0,
scrollTop: anchor.top + this.scrollTop
});
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-index-bar {
position: relative
}
.u-index-bar__sidebar {
position: fixed;
top: 50%;
right: 0;
@include vue-flex;
flex-direction: column;
text-align: center;
transform: translateY(-50%);
user-select: none;
z-index: 99;
}
.u-index-bar__index {
font-weight: 500;
padding: 8rpx 18rpx;
font-size: 22rpx;
line-height: 1
}
.u-indexed-list-alert {
position: fixed;
width: 120rpx;
height: 120rpx;
right: 90rpx;
top: 50%;
margin-top: -60rpx;
border-radius: 24rpx;
font-size: 50rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.65);
@include vue-flex;
justify-content: center;
align-items: center;
padding: 0;
z-index: 9999999;
}
.u-indexed-list-alert text {
line-height: 50rpx;
}
</style>

View File

@ -0,0 +1,394 @@
<template>
<view
class="u-input"
:class="{
'u-input--border': border,
'u-input--error': validateState
}"
:style="{
padding: `0 ${border ? 20 : 0}rpx`,
borderColor: borderColor,
textAlign: inputAlign
}"
@tap.stop="inputClick"
>
<textarea
v-if="type == 'textarea'"
class="u-input__input u-input__textarea"
:style="[getStyle]"
:value="defaultValue"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled"
:maxlength="inputMaxlength"
:fixed="fixed"
:focus="focus"
:autoHeight="autoHeight"
:selection-end="uSelectionEnd"
:selection-start="uSelectionStart"
:cursor-spacing="getCursorSpacing"
:show-confirm-bar="showConfirmbar"
@input="handleInput"
@blur="handleBlur"
@focus="onFocus"
@confirm="onConfirm"
/>
<input
v-else
class="u-input__input"
:type="type == 'password' ? 'text' : type"
:style="[getStyle]"
:value="defaultValue"
:password="type == 'password' && !showPassword"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled || type === 'select'"
:maxlength="inputMaxlength"
:focus="focus"
:confirmType="confirmType"
:cursor-spacing="getCursorSpacing"
:selection-end="uSelectionEnd"
:selection-start="uSelectionStart"
:show-confirm-bar="showConfirmbar"
:adjust-position="adjustPosition"
@focus="onFocus"
@blur="handleBlur"
@input="handleInput"
@confirm="onConfirm"
/>
<view class="u-input__right-icon u-flex">
<view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
<u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
</view>
<view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
</view>
<view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
'u-input__right-icon--select--reverse': selectOpen
}">
<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
</view>
</view>
</view>
</template>
<script>
import Emitter from '../../libs/util/emitter.js';
/**
* input 输入框
* @description 此组件为一个输入框默认没有边框和样式是专门为配合表单组件u-form而设计的利用它可以快速实现表单验证输入内容下拉选择等功能
* @tutorial http://uviewui.com/components/input.html
* @property {String} type 模式选择见官网说明
* @property {Boolean} clearable 是否显示右侧的清除图标(默认true)
* @property {} v-model 用于双向绑定输入框的值
* @property {String} input-align 输入框文字的对齐方式(默认left)
* @property {String} placeholder placeholder显示值(默认 '请输入内容')
* @property {Boolean} disabled 是否禁用输入框(默认false)
* @property {String Number} maxlength 输入框的最大可输入长度(默认140)
* @property {String Number} selection-start 光标起始位置自动聚焦时有效需与selection-end搭配使用默认-1
* @property {String Number} maxlength 光标结束位置自动聚焦时有效需与selection-start搭配使用默认-1
* @property {String Number} cursor-spacing 指定光标与键盘的距离单位px(默认0)
* @property {String} placeholderStyle placeholder的样式字符串形式"color: red;"(默认 "color: #c0c4cc;")
* @property {String} confirm-type 设置键盘右下角按钮的文字仅在type为text时生效(默认done)
* @property {Object} custom-style 自定义输入框的样式对象形式
* @property {Boolean} focus 是否自动获得焦点(默认false)
* @property {Boolean} fixed 如果type为textarea且在一个"position:fixed"的区域需要指明为true(默认false)
* @property {Boolean} password-icon type为password时是否显示右侧的密码查看图标(默认true)
* @property {Boolean} border 是否显示边框(默认false)
* @property {String} border-color 输入框的边框颜色(默认#dcdfe6)
* @property {Boolean} auto-height 是否自动增高输入区域type为textarea时有效(默认true)
* @property {String Number} height 高度单位rpx(text类型时为70textarea时为100)
* @example <u-input v-model="value" :type="type" :border="border" />
*/
export default {
name: 'u-input',
mixins: [Emitter],
props: {
value: {
type: [String, Number],
default: ''
},
// textareatextnumber
type: {
type: String,
default: 'text'
},
inputAlign: {
type: String,
default: 'left'
},
placeholder: {
type: String,
default: '请输入内容'
},
disabled: {
type: Boolean,
default: false
},
maxlength: {
type: [Number, String],
default: 140
},
placeholderStyle: {
type: String,
default: 'color: #c0c4cc;'
},
confirmType: {
type: String,
default: 'done'
},
//
customStyle: {
type: Object,
default() {
return {};
}
},
// textarea position:fixed fixed true
fixed: {
type: Boolean,
default: false
},
//
focus: {
type: Boolean,
default: false
},
//
passwordIcon: {
type: Boolean,
default: true
},
// input|textarea
border: {
type: Boolean,
default: false
},
//
borderColor: {
type: String,
default: '#dcdfe6'
},
autoHeight: {
type: Boolean,
default: true
},
// type=selectselect
// open-close-
selectOpen: {
type: Boolean,
default: false
},
// rpx
height: {
type: [Number, String],
default: ''
},
//
clearable: {
type: Boolean,
default: true
},
// px
cursorSpacing: {
type: [Number, String],
default: 0
},
// selection-end使
selectionStart: {
type: [Number, String],
default: -1
},
// selection-start使
selectionEnd: {
type: [Number, String],
default: -1
},
//
trim: {
type: Boolean,
default: true
},
//
showConfirmbar:{
type:Boolean,
default:true
},
// uni-apptrue
adjustPosition: {
type: Boolean,
default: true
}
},
data() {
return {
defaultValue: this.value,
inputHeight: 70, // input
textareaHeight: 100, // textarea
validateState: false, // input
focused: false, //
showPassword: false, //
lastValue: '', // @input@input
};
},
watch: {
value(nVal, oVal) {
this.defaultValue = nVal;
// select(inputdisabled@input)@input
if(nVal != oVal && this.type == 'select') this.handleInput({
detail: {
value: nVal
}
})
},
},
computed: {
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength);
},
getStyle() {
let style = {};
// typeinputtextare
style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
style = Object.assign(style, this.customStyle);
return style;
},
//
getCursorSpacing() {
return Number(this.cursorSpacing);
},
//
uSelectionStart() {
return String(this.selectionStart);
},
//
uSelectionEnd() {
return String(this.selectionEnd);
}
},
created() {
// u-form-item
this.$on('on-form-item-error', this.onFormItemError);
},
methods: {
/**
* change 事件
* @param event
*/
handleInput(event) {
let value = event.detail.value;
//
if(this.trim) value = this.$u.trim(value);
// vue return
this.$emit('input', value);
// model
this.defaultValue = value;
// u-form-itemthis.$emit('input')
// u-form-item
// 使this.$nextTick
setTimeout(() => {
// bug()@input
// #ifdef MP-TOUTIAO
if(this.$u.trim(value) == this.lastValue) return ;
this.lastValue = value;
// #endif
// u-form-item
this.dispatch('u-form-item', 'on-form-change', value);
}, 40)
},
/**
* blur 事件
* @param event
*/
handleBlur(event) {
// 使@touchstarthx2.8.4
// @blur
let value = event.detail.value;
setTimeout(() => {
this.focused = false;
}, 100)
// vue return
this.$emit('blur', value);
setTimeout(() => {
// bug()@input
// #ifdef MP-TOUTIAO
if(this.$u.trim(value) == this.lastValue) return ;
this.lastValue = value;
// #endif
// u-form-item
this.dispatch('u-form-item', 'on-form-blur', value);
}, 40)
},
onFormItemError(status) {
this.validateState = status;
},
onFocus(event) {
this.focused = true;
this.$emit('focus');
},
onConfirm(e) {
this.$emit('confirm', e.detail.value);
},
onClear(event) {
this.$emit('input', '');
},
inputClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-input {
position: relative;
flex: 1;
@include vue-flex;
&__input {
//height: $u-form-item-height;
font-size: 28rpx;
color: $u-main-color;
flex: 1;
}
&__textarea {
width: auto;
font-size: 28rpx;
color: $u-main-color;
padding: 10rpx 0;
line-height: normal;
flex: 1;
}
&--border {
border-radius: 6rpx;
border-radius: 4px;
border: 1px solid $u-form-item-border-color;
}
&--error {
border-color: $u-type-error!important;
}
&__right-icon {
&__item {
margin-left: 10rpx;
}
&--select {
transition: transform .4s;
&--reverse {
transform: rotate(-180deg);
}
}
}
}
</style>

View File

@ -0,0 +1,217 @@
<template>
<u-popup class="" :mask="mask" :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
:safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :zIndex="uZIndex">
<slot />
<view class="u-tooltip" v-if="tooltip">
<view class="u-tooltip-item u-tooltip-cancel" hover-class="u-tooltip-cancel-hover" @tap="onCancel">
{{cancelBtn ? cancelText : ''}}
</view>
<view v-if="showTips" class="u-tooltip-item u-tooltip-tips">
{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}
</view>
<view v-if="confirmBtn" @tap="onConfirm" class="u-tooltip-item u-tooltips-submit" hover-class="u-tooltips-submit-hover">
{{confirmBtn ? confirmText : ''}}
</view>
</view>
<block v-if="mode == 'number' || mode == 'card'">
<u-number-keyboard :random="random" @backspace="backspace" @change="change" :mode="mode" :dotEnabled="dotEnabled"></u-number-keyboard>
</block>
<block v-else>
<u-car-keyboard :random="random" @backspace="backspace" @change="change"></u-car-keyboard>
</block>
</u-popup>
</template>
<script>
/**
* keyboard 键盘
* @description 此为uViw自定义的键盘面板内含了数字键盘车牌号键身份证号键盘3中模式都有可以打乱按键顺序的选项
* @tutorial https://www.uviewui.com/components/keyboard.html
* @property {String} mode 键盘类型见官网基本使用的说明默认number
* @property {Boolean} dot-enabled 是否显示"."按键只在mode=number时有效默认true
* @property {Boolean} tooltip 是否显示键盘顶部工具条默认true
* @property {String} tips 工具条中间的提示文字见上方基本使用的说明如不需要请传""空字符
* @property {Boolean} cancel-btn 是否显示工具条左边的"取消"按钮默认true
* @property {Boolean} confirm-btn 是否显示工具条右边的"完成"按钮默认true
* @property {Boolean} mask 是否显示遮罩默认true
* @property {String} confirm-text 确认按钮的文字
* @property {String} cancel-text 取消按钮的文字
* @property {Number String} z-index 弹出键盘的z-index值默认1075
* @property {Boolean} random 是否打乱键盘按键的顺序默认false
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配默认false
* @property {Boolean} mask-close-able 是否允许点击遮罩收起键盘默认true
* @event {Function} change 按键被点击(不包含退格键被点击)
* @event {Function} cancel 键盘顶部工具条左边的"取消"按钮被点击
* @event {Function} confirm 键盘顶部工具条右边的"完成"按钮被点击
* @event {Function} backspace 键盘退格键被点击
* @example <u-keyboard mode="number" v-model="show"></u-keyboard>
*/
export default {
name: "u-keyboard",
props: {
// number-card-car-
mode: {
type: String,
default: 'number'
},
// "."
dotEnabled: {
type: Boolean,
default: true
},
//
tooltip: {
type: Boolean,
default: true
},
//
showTips: {
type: Boolean,
default: true
},
//
tips: {
type: String,
default: ''
},
// ""
cancelBtn: {
type: Boolean,
default: true
},
// ""
confirmBtn: {
type: Boolean,
default: true
},
//
random: {
type: Boolean,
default: false
},
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: false
},
//
maskCloseAble: {
type: Boolean,
default: true
},
//
value: {
type: Boolean,
default: false
},
//
mask: {
type: Boolean,
default: true
},
// z-index
zIndex: {
type: [Number, String],
default: ''
},
//
cancelText: {
type: String,
default: '取消'
},
//
confirmText: {
type: String,
default: '确认'
}
},
data() {
return {
//show: false
}
},
computed: {
uZIndex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
methods: {
change(e) {
this.$emit('change', e);
},
//
popupClose() {
// inputpropsvalue
this.$emit('input', false);
},
//
onConfirm() {
this.popupClose();
this.$emit('confirm');
},
//
onCancel() {
this.popupClose();
this.$emit('cancel');
},
// 退
backspace() {
this.$emit('backspace');
},
//
// close() {
// this.show = false;
// },
// //
// open() {
// this.show = true;
// }
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-keyboard {
position: relative;
z-index: 1003;
}
.u-tooltip {
@include vue-flex;
justify-content: space-between;
}
.u-tooltip-item {
color: #333333;
flex: 0 0 33.333333%;
text-align: center;
padding: 20rpx 10rpx;
font-size: 28rpx;
}
.u-tooltips-submit {
text-align: right;
flex-grow: 1;
flex-wrap: 0;
padding-right: 40rpx;
color: $u-type-primary;
}
.u-tooltip-cancel {
text-align: left;
flex-grow: 1;
flex-wrap: 0;
padding-left: 40rpx;
color: #888888;
}
.u-tooltips-submit-hover {
color: $u-type-success;
}
.u-tooltip-cancel-hover {
color: #333333;
}
</style>

View File

@ -0,0 +1,244 @@
<template>
<view class="u-wrap" :style="{
opacity: Number(opacity),
borderRadius: borderRadius + 'rpx',
// time,duration(prop)
transition: `opacity ${time / 1000}s ease-in-out`
}"
:class="'u-lazy-item-' + elIndex">
<view :class="'u-lazy-item-' + elIndex">
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" v-if="!isError" class="u-lazy-item"
:src="isShow ? image : loadingImg" :mode="imgMode" @load="imgLoaded" @error="loadError" @tap="clickImg"></image>
<image :style="{borderRadius: borderRadius + 'rpx', height: imgHeight}" class="u-lazy-item error" v-else :src="errorImg"
:mode="imgMode" @load="errorImgLoaded" @tap="clickImg"></image>
</view>
</view>
</template>
<script>
/**
* lazyLoad 懒加载
* @description 懒加载使用的场景为页面有很多图片时APP会同时加载所有的图片导致页面卡顿各个位置的图片出现前后不一致等.
* @tutorial https://www.uviewui.com/components/lazyLoad.html
* @property {String Number} index 用户自定义值在事件触发时回调用以区分是哪个图片
* @property {String} image 图片路径
* @property {String} loading-img 预加载时的占位图
* @property {String} error-img 图片加载出错时的占位图
* @property {String} threshold 触发加载时的位置见上方说明单位 rpx默认300
* @property {String Number} duration 图片加载成功时淡入淡出时间单位ms默认
* @property {String} effect 图片加载成功时淡入淡出的css动画效果默认ease-in-out
* @property {Boolean} is-effect 图片加载成功时是否启用淡入淡出效果默认true
* @property {String Number} border-radius 图片圆角值单位rpx默认0
* @property {String Number} height 图片高度注意实际高度可能受img-mode参数影响默认450
* @property {String Number} mg-mode 图片的裁剪模式详见image组件裁剪模式默认widthFix
* @event {Function} click 点击图片时触发
* @event {Function} load 图片加载成功时触发
* @event {Function} error 图片加载失败时触发
* @example <u-lazy-load :image="image" :loading-img="loadingImg" :error-img="errorImg"></u-lazy-load>
*/
export default {
name: 'u-lazy-load',
props: {
index: {
type: [Number, String]
},
//
image: {
type: String,
default: ''
},
//
imgMode: {
type: String,
default: 'widthFix'
},
//
loadingImg: {
type: String,
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM0QjNBQjkyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM0QjNBQkEyQUQ2MTFFQTlCNUQ4RTIzNDE5RUIxNjciPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzRCM0FCNzJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzRCM0FCODJBRDYxMUVBOUI1RDhFMjM0MTlFQjE2NyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtRHfPcAAAAzUExURZWVldfX18PDw62trZubm9zc3Li4uKGhoebm5tLS0uHh4c3Nzaenp729vcjIyLKysuvr6141L40AAAcXSURBVHja7NzZlqpGAEBR5lG0//9rIw7IJKJi4or7PGTdtN10wr5SVAEGf/qqArsAiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAIiIAAERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAgAgJEQIAICBABERAg+nmQFMi5Jis+sIniED23jSzIgLTtg2D//iYme/8QBM/9lQ+CAEhbNLM3N9hEHAThX7GPCiBfAxK1b51kD+R7QMLjXg7iCsgWIPUh7pfVozG791oeBPngm48G583uW5GkBvI+SBaM2xXDn1oqum423bX/mgF5FySc2cv93Voug9TdZotsggnkBZB2NzbhrSY5HnoG07jei8dvzsJB/c3W60SALILE46+WCztsbhPR7R2VJq0ukEcT49nyy8QhaKcRa3fYHZD4+ufqOJAcgDz8/59vtw1I3QP5K6JsOG0vm3hce4I8LQp/BaRZGJC3AAn7IKOKXbC+7EdA5vdmmVwOLksgRThqOqiH4XEGsht+peoPUE8U/jJIO5OLH4GEwUslV5G0PTBG5Uiw/Y2jyigO3l9HAHKv9PYb82LloH74dZBoBUgar+l48NsNvtD0fkez9iwrAvIYZDRCl+Xs149Hm/KZmQ+QjUCiO1ei4ru7EsgnQYrkznlQb7thCuRfAzlOAPN72427P4VA/i2Q/DKT/Ls/VR8fvIBsDZIuz7TPF6TCbnk4GJkB2RokejTjuE7/unlgCuSTIO0Cy+Plp6vDfnQlBchy8QtjSHVd3EgmK1bHLm+H6+nXYbz2DuQRSPnqoL7vvq0u70on4zvxgCyWD3b9UyDVdW24PaWaiGTnFZJwPIQAebDpIKheBIm7n124ZthMJipAlkqHO+IZkP1tbfzOJark/A7MgKyvvl60fRqkvXfhuow+t9+q00+0/yyBrK8ZngOtBzldhw2X9tvpNGty0gvkmbPeJ0Cy/r09s/stbmfo0yMWkEdjevgKyOn2t2pxv7UXoibTdCDLje9/Ww1ymqzn87dbp92242ZmMRjI8hASvwKSLq4udqN6ksw8nxXN3tszD9L8Gkg+2mFrQYql5az4tvFj5xOx4VwnSdeBtGdyPwUytxK77pBVlNHdO7OK3rh/eTPUvdutT3fO52tuHMqD4N7llv8pyOQQ//w19YVDfX27+Sfuby9/6nau4pdA8vEdOZuChEH/quHt0Jg+IRJ/5+PrHwKZXfjbDiS73Zo7mu5UkzX7uTsXe0e/7nC3ePf1O69+BUg2XDfZCqSqOu7rGVf8cHBe8zhC2b61dtUHXv0OkGo6ZL4JkpbRYXdUaFevivx2M/1GIOctNh949TtAoumQ+TpIHMX54CJu+8BDd8FkE5BqcZh/59XvAClmTvKfB0nDqIlHo3T70SftyW1eX9dXtgQJqs1f/Q6QaOa/7wmQKtxH8eiGoCRuovODIO3VxOMmruZbHrLyD7z6DSDtGyT7ew1kf9hNn07c986JTovzzem0Id9wUG+Vk/IDr34DSNR7huZJkMFT6vEhqrPx/j5cnlZML8N6/PAzh9Y99Flm5Yde/c9BquDOkvkKkMP58dA4qi9vivE8JOvGz/j8FokfPpr288+pH2ZPOZrLmeGD+7KOh6dqYWJ48ki7yUg0tz0go/fv/LLddfV3sgOLJyaGPY/zrSlh1a36Arkzoue9CyG35ze6E6/dzO2Ga0EGHqdRJIkfn9/8OEjTW8Vq91ZWh39FeehWA7Nu9ft8CpUEk1WWOyDF0OPyEU2Pnzf/bZC0P6IPzmAvu7KauQBVrgKpJ0tG2arHzX8e5Pb3PezNs/PrX+3JMyCLn9XXf37tPFHvt09WfCDDjx+yyn1/p1V11j7GnB/q3leLuVva79S/tzed+db08YpF4uOZtmz/9oXWMq6BCAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAERECACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiACAgQAQEiIEAEBIiAALELvqt/BBgACqVeUBXxcCkAAAAASUVORK5CYII='
},
//
errorImg: {
type: String,
default: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdDMjhENDYyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdDMjhENDcyQUQ2MTFFQTlDQ0VBODgxQjFFOEEyMEMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0MyOEQ0NDJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0MyOEQ0NTJBRDYxMUVBOUNDRUE4ODFCMUU4QTIwQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhLwhikAAAAzUExURZWVldfX162trcPDw5ubm7i4uNzc3Obm5s3NzaGhoeHh4cjIyKenp9LS0r29vbKysuvr67sDMEkAAAlpSURBVHja7NzpYqMgAIVRUVHc8/5PO66R1WAbOzX97q+ZtDEpR0AWTR7kVyWhCAAhgABCAAGEAAIIAQQQAggBBBACCCAEEEAIIIAQQAgggBBAACGAAEIAAYQAQgABhAACCAEEEAIIIAQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAsqeX5QWHKIcs/Ptl03lfL4zDFPWfBGmSpPn+IZzSH5KkCL5B+n+oklwz6Iz//R2QzFOabzhEmiRirAmZt/bl0w/dpMbLqeeo4wEdpC7zR5WAPKziHKtO7ql+ReKvIa9BxgNaL5ZtEkpeAGIVp5jKJa09xVo9vgSSzQcszdYvmOqjQNSQ6pHK6rO1n1Xj32788miwHLaZz1Tl9i/yayDlYJ/60/+lp8GSY7OY1B8E4p55bWmfquFk22GLuUUxi78cX+m+BjL2GLkhMrV+/muS6Sfic0CEp5T1Yu2OQdTzsKV0MJV73KVjroyTffxfuv5Tf3fd6iLT9wz8YdVHgUzF2Is9/Xhi5sYJqP1w/GUpjOiHVbaI0w2L+pg3GZzvtokcgHxWDXHaiy78l3sPke01qphamT5c+dqyeAGSumdL/mkggauTam0e3L/mPEiqtzKDbl0Z1Wn8xOa4ySo8X/7TQIJnY/seEKWf12UmC72CKP9xYjr19RPT7NNA+oMO+R0gwmlotAry+C6I0f59ch8yXVQOr0BKYcXt1IUYRyCt+Ur9HGsrQKI79WY9sY9ARPKlzFOFdb41ioD8b5Bp+mqeeRKAxINkESBFGpOpKhgv9OuYpH8A8l4Qa3qp60Kl2/k+rG2sWafuuyCBafb2j4JkgZUob3nWcmicpkxEgmTLLGejTxnWSWCi8lPmsk6DlIHFJv24ojiYyYoGacwL8zXTLEAVaDI/Ybb3NIgKDSv2oXpmHkvNs+PTpMASEdlk7fOZeRk37fwJ6yGnQarQsGIfqqcvx43rTOXY6jf7uKXdRzdLDRPbjIrx1cIj3Kr4KyBFezzgUGuR5893qkOQ19fR2uVBaU+r16LphJNOiatK7PeBZK/Kb+tUn71rcQjSvARpghfH/yG/D2RetTuI3N5QrMWdP46brP7FmKZ//CGQ9At9SL01DLkzY/Vs8Z97fQZ7gelw7jHqCz+/Wile5J4g3Vc79eb5a6oLSue+Ve83gaSv2jp5PxCzjzwFUm9zw9MllSMil1kS4d2E9SaQ1xNo9wMxx0+nQNLnew/WDHvveMAHYm08mofl3TFI/8pD3Q6kMAv6DIi2jTCwRJUvNdDYrrJum9oHhusCbWALonwxBRk1vXMnEGWuT5wAmfYuVGUYpJ7fUZujCN92hvzwWlrFgxSfANKb10DxIMbShnfrynyZZV30imA7P43ArXXHbvBVkTCIuGy25AdBrHmNeBCpL214QdLp9LZarG3IMWrmW0ehtuO7F2PS09UcgqS3B7FKPhpknrStD0HGF/vQRne37LwLG8EbHT4WxN7/Fg0yD9Yr/3br4nnstA+0Il6QxzdBmg8A6a2/IRbkcK9h/uzV8zywF/oSkOyageCPglRWgcWClHnEzs9q/t/SENVXgFijlsq3VtXdCsRp4qObrLLLgjuzSq3fX89ZZW6AfxNIzF6X9FYgThN/fk093KkvHX/hbWd+DqS/FUhlf+G3gohEXzVs3g9iDluWoaW8fL73QhB34u+tIHIf19nLuF4Q98a09Eynnl56q+ePgEhnX+dbQOp6H5XnJ0ACd8dFgkwf12nTOTcEqd2pom+CFF02TIPw6dKmrLS5qOtBpo8b5quUtrwrSGbuqPkeSJqllTFHO02NPxdMrm+y5LKdWyWXjw4vA5nGEtnjuyCFyHqNYvEolzmASm3zK1Eg5zr13lhqV1tlksnVw8Pkwgri7O07AVKLJkutRYw87bPlRpBpNXE8xGb+fhBlvEGrGPLqViu5sILIx9dAmqF1705sxF4M8+R8P5dOdQwi12fMnATpjJ2JSt/POIvU9wPJEs/jduJAjLvU0yFT0i64Yb1bsVi79dA4pEy3TzoHMq2O7Re4vXm5O9+l290NpE4CU+YRIMNye2iaqbVS2AUnn2fsekthYKReVNutVedA5juttyIXrT38mOds+ps9DWhwL7GWc61/DVKPzVN9UHDarf1icU98IOU8tm6L031Iq63t1tKzj3fe/FCpO4F0/i0Z2+yvA1KeGBjqj1qYx8/zoxpKZ1Yl367I1k+sfcft/QPy9csXy/32qX1qLZsrryG5BGQaRj0vc/b7N54XXq293TCLB5HO42Fy517obW19b+qjl3CHp0fdLJcWvmdy1etESi/uAdJrs1hTaUklHuW8qSDdC3UfXVR5cnD3rAFSSqtFb7z7eapErx7rC739jCXfbK3aWiipjXo8UbmxXPa7QQq9R289j2Gr88N7Ag5AlHPRKc37pNZv0CZtX1tVMG6rm8qW1/KlCgQvcMss933ybwXZz3dReW5yce4ByZtHFIhwT9kmjxg8BzbKDUe1PB9edBJqSN7/KM1LmqyuMZ5BpeTUw1aD/uDI0relPfSHa/Wn8Pxq1BNfxy/h3IdwOJqIKumb9CHvTqMefyY82RoQAgggBBBACCCAEEAAIYAQQAAhgABCAAGEAAIIAYQAAggBBBACCCAEEEAIIAQQQAgggBBAACGAAEIAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAEEEAIIAQQQAgggBBBACCCAEEAIIIAQQAAhgABCAAGEAEIAAYQAAggBBBACCCAEEAIIIAQQQAgggBBAACGAEEAAIYAAQgABhAACCAGEAAIIAQQQAgggBBBACCAEEEAIIIAQQAAhgABCACGAAEIAAYQAAggBBBACCAEEEAIIIAQQQAggfyL/BBgA8PgLdH0TBtkAAAAASUVORK5CYII='
},
// rpx
// ()
threshold: {
type: [Number, String],
default: 100
},
//
duration: {
type: [Number, String],
default: 500
},
// 线
// linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(n,n,n,n);
effect: {
type: String,
default: 'ease-in-out'
},
// 使
isEffect: {
type: Boolean,
default: true
},
//
borderRadius: {
type: [Number, String],
default: 0
},
// rpx
height: {
type: [Number, String],
default: '450'
}
},
data() {
return {
isShow: false,
opacity: 1,
time: this.duration,
loadStatus: '', //
isError: false, //
elIndex: this.$u.guid()
}
},
computed: {
// thresholdrpxpx
getThreshold() {
// thresholdthis.threshold
let thresholdPx = uni.upx2px(Math.abs(this.threshold));
return this.threshold < 0 ? -thresholdPx : thresholdPx;
},
// auto%
imgHeight() {
return this.$u.addUnit(this.height);
}
},
created() {
// data
this.observer = {};
},
watch: {
isShow(nVal) {
//
if (!this.isEffect) return;
this.time = 0;
// opacity1()0()1
this.opacity = 0;
// 30msH5
setTimeout(() => {
this.time = this.duration;
this.opacity = 1;
}, 30)
},
// isError
image(n) {
if(!n) {
// null''undefined
this.isError = true;
} else {
this.init();
this.isError = false;
}
}
},
methods: {
//
init() {
this.isError = false;
this.loadStatus = '';
},
// ,loadlazy-loading-loaded-
clickImg() {
let whichImg = '';
// isShowfalse
if (this.isShow == false) whichImg = 'lazyImg';
// isErrortrue
// ~
else if (this.isError == true) whichImg = 'errorImg';
//
else whichImg = 'realImg';
// index
this.$emit('click', this.index);
},
// isShow
imgLoaded() {
//
if (this.loadStatus == '') {
this.loadStatus = 'lazyed';
}
//
else if (this.loadStatus == 'lazyed') {
this.loadStatus = 'loaded';
this.$emit('load', this.index);
}
},
//
errorImgLoaded() {
this.$emit('error', this.index);
},
//
loadError() {
this.isError = true;
},
disconnectObserver(observerName) {
const observer = this[observerName];
observer && observer.disconnect();
},
},
beforeDestroy() {
//
//observer.disconnect();
},
mounted() {
// uOnReachBottommixin.js
this.$nextTick(() => {
uni.$once('uOnReachBottom', () => {
if (!this.isShow) this.isShow = true;
});
})
// mounted30ms
setTimeout(() => {
// uni.createIntersectionObserverthis.createIntersectionObserver
this.disconnectObserver('contentObserver');
const contentObserver = uni.createIntersectionObserver(this);
//
// https://blog.csdn.net/qq_25324335/article/details/83687695
contentObserver.relativeToViewport({
bottom: this.getThreshold,
}).observe('.u-lazy-item-' + this.elIndex, (res) => {
if (res.intersectionRatio > 0) {
//
this.isShow = true;
//
this.disconnectObserver('contentObserver');
}
})
this.contentObserver = contentObserver;
}, 30)
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-wrap {
background-color: #eee;
overflow: hidden;
}
.u-lazy-item {
width: 100%;
//
transform: transition3d(0, 0, 0);
//
will-change: transform;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
}
</style>

View File

@ -0,0 +1,147 @@
<template>
<view class="u-progress" :style="{
borderRadius: round ? '100rpx' : 0,
height: height + 'rpx',
backgroundColor: inactiveColor
}">
<view :class="[
type ? `u-type-${type}-bg` : '',
striped ? 'u-striped' : '',
striped && stripedActive ? 'u-striped-active' : ''
]" class="u-active" :style="[progressStyle]">
<slot v-if="$slots.default || $slots.$default" />
<block v-else-if="showPercent">
{{percent + '%'}}
</block>
</view>
</view>
</template>
<script>
/**
* lineProgress 线型进度条
* @description 展示操作或任务的当前进度比如上传文件是一个线形的进度条
* @tutorial https://www.uviewui.com/components/lineProgress.html
* @property {String Number} percent 进度条百分比值为数值类型0-100
* @property {Boolean} round 进度条两端是否为半圆默认true
* @property {String} type 如设置active-color值将会失效
* @property {String} active-color 进度条激活部分的颜色默认#19be6b
* @property {String} inactive-color 进度条的底色默认#ececec
* @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值默认true
* @property {String Number} height 进度条的高度单位rpx默认28
* @property {Boolean} striped 是否显示进度条激活部分的条纹默认false
* @property {Boolean} striped-active 条纹是否具有动态效果默认false
* @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
*/
export default {
name: "u-line-progress",
props: {
//
round: {
type: Boolean,
default: true
},
//
type: {
type: String,
default: ''
},
//
activeColor: {
type: String,
default: '#19be6b'
},
inactiveColor: {
type: String,
default: '#ececec'
},
//
percent: {
type: Number,
default: 0
},
//
showPercent: {
type: Boolean,
default: true
},
// rpx
height: {
type: [Number, String],
default: 28
},
//
striped: {
type: Boolean,
default: false
},
//
stripedActive: {
type: Boolean,
default: false
}
},
data() {
return {
}
},
computed: {
progressStyle() {
let style = {};
style.width = this.percent + '%';
if(this.activeColor) style.backgroundColor = this.activeColor;
return style;
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-progress {
overflow: hidden;
height: 15px;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
width: 100%;
border-radius: 100rpx;
}
.u-active {
width: 0;
height: 100%;
align-items: center;
@include vue-flex;
justify-items: flex-end;
justify-content: space-around;
font-size: 20rpx;
color: #ffffff;
transition: all 0.4s ease;
}
.u-striped {
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-size: 39px 39px;
}
.u-striped-active {
animation: progress-stripes 2s linear infinite;
}
@keyframes progress-stripes {
0% {
background-position: 0 0;
}
100% {
background-position: 39px 0;
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<view class="u-line" :style="[lineStyle]">
</view>
</template>
<script>
/**
* line 线条
* @description 此组件一般用于显示一根线条用于分隔内容块有横向和竖向两种模式且能设置0.5px线条使用也很简单
* @tutorial https://www.uviewui.com/components/line.html
* @property {String} color 线条的颜色(默认#e4e7ed)
* @property {String} length 长度竖向时表现为高度横向时表现为长度可以为百分比带rpx单位的值等
* @property {String} direction 线条的方向row-横向col-竖向(默认row)
* @property {String} border-style 线条的类型solid-实线dashed-方形虚线dotted-圆点虚线(默认solid)
* @property {Boolean} hair-line 是否显示细线条(默认true)
* @property {String} margin 线条与上下左右元素的间距字符串形式"30rpx"
* @example <u-line color="red"></u-line>
*/
export default {
name: 'u-line',
props: {
color: {
type: String,
default: '#e4e7ed'
},
// rpx
length: {
type: String,
default: '100%'
},
// 线col-row-
direction: {
type: String,
default: 'row'
},
//
hairLine: {
type: Boolean,
default: true
},
// 线"30rpx""20rpx 30rpx"
margin: {
type: String,
default: '0'
},
// 线solid-线dashed-线dotted-线
borderStyle: {
type: String,
default: 'solid'
}
},
computed: {
lineStyle() {
let style = {};
style.margin = this.margin;
// 线1pxtransform0.5px
if(this.direction == 'row') {
// nvue
style.borderBottomWidth = '1px';
style.borderBottomStyle = this.borderStyle;
style.width = this.$u.addUnit(this.length);
if(this.hairLine) style.transform = 'scaleY(0.5)';
} else {
// 线1pxtransform0.5px
style.borderLeftWidth = '1px';
style.borderLeftStyle = this.borderStyle;
style.height = this.$u.addUnit(this.length);
if(this.hairLine) style.transform = 'scaleX(0.5)';
}
style.borderColor = this.color;
return style;
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-line {
vertical-align: middle;
}
</style>

View File

@ -0,0 +1,89 @@
<template>
<text class="u-link" @tap.stop="openLink" :style="{
color: color,
fontSize: fontSize + 'rpx',
borderBottom: underLine ? `1px solid ${lineColor ? lineColor : color}` : 'none',
paddingBottom: underLine ? '0rpx' : '0'
}">
<slot></slot>
</text>
</template>
<script>
/**
* link 超链接
* @description 该组件为超链接组件在不同平台有不同表现形式在APP平台会通过plus环境打开内置浏览器在小程序中把链接复制到粘贴板同时提示信息在H5中通过window.open打开链接
* @tutorial https://www.uviewui.com/components/link.html
* @property {String} color 文字颜色默认#606266
* @property {String Number} font-size 字体大小单位rpx默认28
* @property {Boolean} under-line 是否显示下划线默认false
* @property {String} href 跳转的链接要带上http(s)
* @property {String} line-color 下划线颜色默认同color参数颜色
* @property {String} mp-tips 各个小程序平台把链接复制到粘贴板后的提示语默认链接已复制请在浏览器打开
* @example <u-link href="http://www.uviewui.com">蜀道难难于上青天</u-link>
*/
export default {
name: "u-link",
props: {
//
color: {
type: String,
default: '#2979ff'
},
// rpx
fontSize: {
type: [String, Number],
default: 28
},
// 线
underLine: {
type: Boolean,
default: false
},
//
href: {
type: String,
default: ''
},
//
mpTips: {
type: String,
default: '链接已复制,请在浏览器打开'
},
// 线
lineColor: {
type: String,
default: ''
}
},
methods: {
openLink() {
// #ifdef APP-PLUS
plus.runtime.openURL(this.href)
// #endif
// #ifdef H5
window.open(this.href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: this.href,
success: () => {
uni.hideToast();
this.$nextTick(() => {
this.$u.toast(this.mpTips);
})
}
});
// #endif
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-link {
line-height: 1;
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<view class="u-loading-page">
</view>
</template>
<script>
export default {
props: {
},
data() {
return {
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,103 @@
<template>
<view v-if="show" class="u-loading" :class="mode == 'circle' ? 'u-loading-circle' : 'u-loading-flower'" :style="[cricleStyle]">
</view>
</template>
<script>
/**
* loading 加载动画
* @description 警此组件为一个小动画目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景
* @tutorial https://www.uviewui.com/components/loading.html
* @property {String} mode 模式选择见官网说明默认circle
* @property {String} color 动画活动区域的颜色只对 mode = flower 模式有效默认#c7c7c7
* @property {String Number} size 加载图标的大小单位rpx默认34
* @property {Boolean} show 是否显示动画默认true
* @example <u-loading mode="circle"></u-loading>
*/
export default {
name: "u-loading",
props: {
//
mode: {
type: String,
default: 'circle'
},
//
color: {
type: String,
default: '#c7c7c7'
},
// rpx
size: {
type: [String, Number],
default: '34'
},
//
show: {
type: Boolean,
default: true
}
},
computed: {
//
cricleStyle() {
let style = {};
style.width = this.size + 'rpx';
style.height = this.size + 'rpx';
if (this.mode == 'circle') style.borderColor = `#e4e4e4 #e4e4e4 #e4e4e4 ${this.color ? this.color : '#c7c7c7'}`;
return style;
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-loading-circle {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
vertical-align: middle;
width: 28rpx;
height: 28rpx;
background: 0 0;
border-radius: 50%;
border: 2px solid;
border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
animation: u-circle 1s linear infinite;
}
.u-loading-flower {
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
-webkit-animation: a 1s steps(12) infinite;
animation: u-flower 1s steps(12) infinite;
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
background-size: 100%;
}
@keyframes u-flower {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn);
}
}
@-webkit-keyframes u-circle {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,203 @@
<template>
<view class="u-load-more-wrap" :style="{
backgroundColor: bgColor,
marginBottom: marginBottom + 'rpx',
marginTop: marginTop + 'rpx',
height: $u.addUnit(height)
}">
<u-line color="#d4d4d4" length="50"></u-line>
<!-- 加载中和没有更多的状态才显示两边的横线 -->
<view :class="status == 'loadmore' || status == 'nomore' ? 'u-more' : ''" class="u-load-more-inner">
<view class="u-loadmore-icon-wrap">
<u-loading class="u-loadmore-icon" :color="iconColor" :mode="iconType == 'circle' ? 'circle' : 'flower'" :show="status == 'loading' && icon"></u-loading>
</view>
<!-- 如果没有更多的状态下显示内容为dot粗点加载特定样式 -->
<view class="u-line-1" :style="[loadTextStyle]" :class="[(status == 'nomore' && isDot == true) ? 'u-dot-text' : 'u-more-text']" @tap="loadMore">
{{ showText }}
</view>
</view>
<u-line color="#d4d4d4" length="50"></u-line>
</view>
</template>
<script>
/**
* loadmore 加载更多
* @description 此组件一般用于标识页面底部加载数据时的状态
* @tutorial https://www.uviewui.com/components/loadMore.html
* @property {String} status 组件状态默认loadmore
* @property {String} bg-color 组件背景颜色在页面是非白色时会用到默认#ffffff
* @property {Boolean} icon 加载中时是否显示图标默认true
* @property {String} icon-type 加载中时的图标类型默认circle
* @property {String} icon-color icon-type为circle时有效加载中的动画图标的颜色默认#b7b7b7
* @property {Boolean} is-dot status为nomore时内容显示为一个"●"默认false
* @property {String} color 字体颜色默认#606266
* @property {String Number} margin-top 到上一个相邻元素的距离
* @property {String Number} margin-bottom 到下一个相邻元素的距离
* @property {Object} load-text 自定义显示的文字见上方说明示例
* @event {Function} loadmore status为loadmore时点击组件会发出此事件
* @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" />
*/
export default {
name: "u-loadmore",
props: {
//
bgColor: {
type: String,
default: 'transparent'
},
//
icon: {
type: Boolean,
default: true
},
//
fontSize: {
type: String,
default: '28'
},
//
color: {
type: String,
default: '#606266'
},
// loadmore-loading-nomore-
status: {
type: String,
default: 'loadmore'
},
// flower-circle-
iconType: {
type: String,
default: 'circle'
},
//
loadText: {
type: Object,
default () {
return {
loadmore: '加载更多',
loading: '正在加载...',
nomore: '没有更多了'
}
}
},
//
isDot: {
type: Boolean,
default: false
},
//
iconColor: {
type: String,
default: '#b7b7b7'
},
//
marginTop: {
type: [String, Number],
default: 0
},
//
marginBottom: {
type: [String, Number],
default: 0
},
// rpx
height: {
type: [String, Number],
default: 'auto'
}
},
data() {
return {
//
dotText: "●"
}
},
computed: {
//
loadTextStyle() {
return {
color: this.color,
fontSize: this.fontSize + 'rpx',
position: 'relative',
zIndex: 1,
backgroundColor: this.bgColor,
//
}
},
//
cricleStyle() {
return {
borderColor: `#e5e5e5 #e5e5e5 #e5e5e5 ${this.circleColor}`
}
},
//
// base64
flowerStyle() {
return {
}
},
//
showText() {
let text = '';
if(this.status == 'loadmore') text = this.loadText.loadmore;
else if(this.status == 'loading') text = this.loadText.loading;
else if(this.status == 'nomore' && this.isDot) text = this.dotText;
else text = this.loadText.nomore;
return text;
}
},
methods: {
loadMore() {
//
if(this.status == 'loadmore') this.$emit('loadmore');
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
/* #ifdef MP */
// mp.scssu-lineflex: 1
// (u-line)使
u-line {
flex: none;
}
/* #endif */
.u-load-more-wrap {
@include vue-flex;
justify-content: center;
align-items: center;
}
.u-load-more-inner {
@include vue-flex;
justify-content: center;
align-items: center;
padding: 0 12rpx;
}
.u-more {
position: relative;
@include vue-flex;
justify-content: center;
}
.u-dot-text {
font-size: 28rpx;
}
.u-loadmore-icon-wrap {
margin-right: 8rpx;
}
.u-loadmore-icon {
@include vue-flex;
align-items: center;
justify-content: center;
}
</style>

View File

@ -0,0 +1,123 @@
<template>
<view class="u-mask" hover-stop-propagation :style="[maskStyle, zoomStyle]" @tap="click" @touchmove.stop.prevent="() => {}" :class="{
'u-mask-zoom': zoom,
'u-mask-show': show
}">
<slot />
</view>
</template>
<script>
/**
* mask 遮罩
* @description 创建一个遮罩层用于强调特定的页面元素并阻止用户对遮罩下层的内容进行操作一般用于弹窗场景
* @tutorial https://www.uviewui.com/components/mask.html
* @property {Boolean} show 是否显示遮罩默认false
* @property {String Number} z-index z-index 层级默认1070
* @property {Object} custom-style 自定义样式对象见上方说明
* @property {String Number} duration 动画时长单位毫秒默认300
* @property {Boolean} zoom 是否使用scale对遮罩进行缩放默认true
* @property {Boolean} mask-click-able 遮罩是否可点击为false时点击不会发送click事件默认true
* @event {Function} click mask-click-able为true时点击遮罩发送此事件
* @example <u-mask :show="show" @click="show = false"></u-mask>
*/
export default {
name: "u-mask",
props: {
//
show: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: [Number, String],
default: ''
},
//
customStyle: {
type: Object,
default () {
return {}
}
},
// 使使zoomscale
zoom: {
type: Boolean,
default: true
},
// ms
duration: {
type: [Number, String],
default: 300
},
//
maskClickAble: {
type: Boolean,
default: true
}
},
data() {
return {
zoomStyle: {
transform: ''
},
scale: 'scale(1.2, 1.2)'
}
},
watch: {
show(n) {
if(n && this.zoom) {
// scale1(1.2)
this.zoomStyle.transform = 'scale(1, 1)';
} else if(!n && this.zoom) {
// scale1.2(1)
this.zoomStyle.transform = this.scale;
}
}
},
computed: {
maskStyle() {
let style = {};
style.backgroundColor = "rgba(0, 0, 0, 0.6)";
if(this.show) style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask;
else style.zIndex = -1;
style.transition = `all ${this.duration / 1000}s ease-in-out`;
//
if (Object.keys(this.customStyle).length) style = {
...style,
...this.customStyle
};
return style;
}
},
methods: {
click() {
if (!this.maskClickAble) return;
this.$emit('click');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
opacity: 0;
transition: transform 0.3s;
}
.u-mask-show {
opacity: 1;
}
.u-mask-zoom {
transform: scale(1.2, 1.2);
}
</style>

View File

@ -0,0 +1,311 @@
<template>
<view class="u-char-box">
<view class="u-char-flex">
<input :disabled="disabledKeyboard" :value="valueModel" type="number" :focus="focus" :maxlength="maxlength" class="u-input" @input="getVal"/>
<view v-for="(item, index) in loopCharArr" :key="index">
<view :class="[breathe && charArrLength == index ? 'u-breathe' : '', 'u-char-item',
charArrLength === index && mode == 'box' ? 'u-box-active' : '',
mode === 'box' ? 'u-box' : '']" :style="{
fontWeight: bold ? 'bold' : 'normal',
fontSize: fontSize + 'rpx',
width: width + 'rpx',
height: width + 'rpx',
color: inactiveColor,
borderColor: charArrLength === index && mode == 'box' ? activeColor : inactiveColor
}">
<view class="u-placeholder-line" :style="{
display: charArrLength === index ? 'block' : 'none',
height: width * 0.5 +'rpx'
}"
v-if="mode !== 'middleLine'"
></view>
<view v-if="mode === 'middleLine' && charArrLength <= index" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-middle-line-active' : '']"
class="u-middle-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
<view v-if="mode === 'bottomLine'" :class="[breathe && charArrLength == index ? 'u-breathe' : '', charArrLength === index ? 'u-buttom-line-active' : '']"
class="u-bottom-line" :style="{height: bold ? '4px' : '2px', background: charArrLength === index ? activeColor : inactiveColor}"></view>
<block v-if="!dotFill"> {{ charArr[index] ? charArr[index] : ''}}</block>
<block v-else>
<text class="u-dot">{{ charArr[index] ? '●' : ''}}</text>
</block>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* messageInput 验证码输入框
* @description 该组件一般用于验证用户短信验证码的场景也可以结合uView的键盘组件使用
* @tutorial https://www.uviewui.com/components/messageInput.html
* @property {String Number} maxlength 输入字符个数默认4
* @property {Boolean} dot-fill 是否用圆点填充默认false
* @property {String} mode 模式选择见上方"基本使用"说明默认box
* @property {String Number} value 预置值
* @property {Boolean} breathe 是否开启呼吸效果见上方说明默认true
* @property {Boolean} focus 是否自动获取焦点默认false
* @property {Boolean} bold 字体和输入横线是否加粗默认true
* @property {String Number} font-size 字体大小单位rpx默认60
* @property {String} active-color 当前激活输入框的样式默认#2979ff
* @property {String} inactive-color 非激活输入框的样式文字颜色同此值默认#606266
* @property {String | Number} width 输入框宽度单位rpx高等于宽默认80
* @property {Boolean} disabled-keyboard 禁止点击输入框唤起系统键盘默认false
* @event {Function} change 输入内容发生改变时触发具体见官网说明
* @event {Function} finish 输入字符个数达maxlength值时触发见官网说明
* @example <u-message-input mode="bottomLine"></u-message-input>
*/
export default {
name: "u-message-input",
props: {
//
maxlength: {
type: [Number, String],
default: 4
},
//
dotFill: {
type: Boolean,
default: false
},
// box-bottomLine-线middleLine-线
mode: {
type: String,
default: "box"
},
//
value: {
type: [String, Number],
default: ''
},
// item
breathe: {
type: Boolean,
default: true
},
//
focus: {
type: Boolean,
default: false
},
//
bold: {
type: Boolean,
default: false
},
//
fontSize: {
type: [String, Number],
default: 60
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#606266'
},
// rpx
width: {
type: [Number, String],
default: '80'
},
// true
disabledKeyboard: {
type: Boolean,
default: false
}
},
watch: {
// maxlength: {
// // truemaxlengthcreated
// immediate: true,
// handler(val) {
// this.maxlength = Number(val);
// }
// },
value: {
immediate: true,
handler(val) {
//
val = String(val);
//
this.valueModel = val.substring(0, this.maxlength);
}
},
},
data() {
return {
valueModel: ""
}
},
computed: {
//
animationClass() {
return (index) => {
if (this.breathe && this.charArr.length == index) return 'u-breathe';
else return '';
}
},
//
charArr() {
return this.valueModel.split('');
},
charArrLength() {
return this.charArr.length;
},
// v-for
loopCharArr() {
return new Array(this.maxlength);
}
},
methods: {
getVal(e) {
let {
value
} = e.detail
this.valueModel = value;
// maxlengthinputmaxlength
if (String(value).length > this.maxlength) return;
// maxlengthchangefinish
this.$emit('change', value);
if (String(value).length == this.maxlength) {
this.$emit('finish', value);
}
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
@keyframes breathe {
0% {
opacity: 0.3;
}
50% {
opacity: 1;
}
100% {
opacity: 0.3;
}
}
.u-char-box {
text-align: center;
}
.u-char-flex {
@include vue-flex;
justify-content: center;
flex-wrap: wrap;
position: relative;
}
.u-input {
position: absolute;
top: 0;
left: -100%;
width: 200%;
height: 100%;
text-align: left;
z-index: 9;
opacity: 0;
background: none;
}
.u-char-item {
position: relative;
width: 90rpx;
height: 90rpx;
margin: 10rpx 10rpx;
font-size: 60rpx;
font-weight: bold;
color: $u-main-color;
line-height: 90rpx;
@include vue-flex;
justify-content: center;
align-items: center;
}
.u-middle-line {
border: none;
}
.u-box {
box-sizing: border-box;
border: 2rpx solid #cccccc;
border-radius: 6rpx;
}
.u-box-active {
overflow: hidden;
animation-timing-function: ease-in-out;
animation-duration: 1500ms;
animation-iteration-count: infinite;
animation-direction: alternate;
border: 2rpx solid $u-type-primary;
}
.u-middle-line-active {
background: $u-type-primary;
}
.u-breathe {
animation: breathe 2s infinite ease;
}
.u-placeholder-line {
/* #ifndef APP-NVUE */
display: none;
/* #endif */
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 2rpx;
height: 40rpx;
background: #333333;
animation: twinkling 1.5s infinite ease;
}
.u-animation-breathe {
animation-name: breathe;
}
.u-dot {
font-size: 34rpx;
line-height: 34rpx;
}
.u-middle-line {
height: 4px;
background: #000000;
width: 80%;
position: absolute;
border-radius: 2px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.u-buttom-line-active {
background: $u-type-primary;
}
.u-bottom-line {
height: 4px;
background: #000000;
width: 80%;
position: absolute;
border-radius: 2px;
bottom: 0;
left: 50%;
transform: translate(-50%);
}
</style>

View File

@ -0,0 +1,283 @@
<template>
<view>
<u-popup :zoom="zoom" mode="center" :popup="false" :z-index="uZIndex" v-model="value" :length="width"
:mask-close-able="maskCloseAble" :border-radius="borderRadius" @close="popupClose" :negative-top="negativeTop">
<view class="u-model">
<view v-if="showTitle" class="u-model__title u-line-1" :style="[titleStyle]">{{ title }}</view>
<view class="u-model__content">
<view :style="[contentStyle]" v-if="$slots.default || $slots.$default">
<slot />
</view>
<view v-else class="u-model__content__message" :style="[contentStyle]">{{ content }}</view>
</view>
<view class="u-model__footer u-border-top" v-if="showCancelButton || showConfirmButton">
<view v-if="showCancelButton" :hover-stay-time="100" hover-class="u-model__btn--hover" class="u-model__footer__button"
:style="[cancelBtnStyle]" @tap="cancel">
{{cancelText}}
</view>
<view v-if="showConfirmButton || $slots['confirm-button']" :hover-stay-time="100" :hover-class="asyncClose ? 'none' : 'u-model__btn--hover'"
class="u-model__footer__button hairline-left" :style="[confirmBtnStyle]" @tap="confirm">
<slot v-if="$slots['confirm-button']" name="confirm-button"></slot>
<block v-else>
<u-loading mode="circle" :color="confirmColor" v-if="loading"></u-loading>
<block v-else>
{{confirmText}}
</block>
</block>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
/**
* modal 模态框
* @description 弹出模态框常用于消息提示消息确认在当前页面内完成特定的交互操作
* @tutorial https://www.uviewui.com/components/modal.html
* @property {Boolean} value 是否显示模态框
* @property {String | Number} z-index 层级
* @property {String} title 模态框标题默认"提示"
* @property {String | Number} width 模态框宽度默认600
* @property {String} content 模态框内容默认"内容"
* @property {Boolean} show-title 是否显示标题默认true
* @property {Boolean} async-close 是否异步关闭只对确定按钮有效默认false
* @property {Boolean} show-confirm-button 是否显示确认按钮默认true
* @property {Stringr | Number} negative-top modal往上偏移的值
* @property {Boolean} show-cancel-button 是否显示取消按钮默认false
* @property {Boolean} mask-close-able 是否允许点击遮罩关闭modal默认false
* @property {String} confirm-text 确认按钮的文字内容默认"确认"
* @property {String} cancel-text 取消按钮的文字内容默认"取消"
* @property {String} cancel-color 取消按钮的颜色默认"#606266"
* @property {String} confirm-color 确认按钮的文字内容默认"#2979ff"
* @property {String | Number} border-radius 模态框圆角值单位rpx默认16
* @property {Object} title-style 自定义标题样式对象形式
* @property {Object} content-style 自定义内容样式对象形式
* @property {Object} cancel-style 自定义取消按钮样式对象形式
* @property {Object} confirm-style 自定义确认按钮样式对象形式
* @property {Boolean} zoom 是否开启缩放模式默认true
* @event {Function} confirm 确认按钮被点击
* @event {Function} cancel 取消按钮被点击
* @example <u-modal :src="title" :content="content"></u-modal>
*/
export default {
name: 'u-modal',
props: {
// Modal
value: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: [Number, String],
default: ''
},
//
title: {
type: [String],
default: '提示'
},
// (rpx)auto
width: {
type: [Number, String],
default: 600
},
//
content: {
type: String,
default: '内容'
},
//
showTitle: {
type: Boolean,
default: true
},
//
showConfirmButton: {
type: Boolean,
default: true
},
//
showCancelButton: {
type: Boolean,
default: false
},
//
confirmText: {
type: String,
default: '确认'
},
//
cancelText: {
type: String,
default: '取消'
},
//
confirmColor: {
type: String,
default: '#2979ff'
},
//
cancelColor: {
type: String,
default: '#606266'
},
//
borderRadius: {
type: [Number, String],
default: 16
},
//
titleStyle: {
type: Object,
default () {
return {}
}
},
//
contentStyle: {
type: Object,
default () {
return {}
}
},
//
cancelStyle: {
type: Object,
default () {
return {}
}
},
//
confirmStyle: {
type: Object,
default () {
return {}
}
},
//
zoom: {
type: Boolean,
default: true
},
//
asyncClose: {
type: Boolean,
default: false
},
// modal
maskCloseAble: {
type: Boolean,
default: false
},
// margin-top
negativeTop: {
type: [String, Number],
default: 0
}
},
data() {
return {
loading: false, //
}
},
computed: {
cancelBtnStyle() {
return Object.assign({
color: this.cancelColor
}, this.cancelStyle);
},
confirmBtnStyle() {
return Object.assign({
color: this.confirmColor
}, this.confirmStyle);
},
uZIndex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
watch: {
// v-modelfalseloading
//
value(n) {
if (n === true) this.loading = false;
}
},
methods: {
confirm() {
//
if (this.asyncClose) {
this.loading = true;
} else {
this.$emit('input', false);
}
this.$emit('confirm');
},
cancel() {
this.$emit('cancel');
this.$emit('input', false);
// popup
// ""modal
setTimeout(() => {
this.loading = false;
}, 300);
},
// modalv-modelfalsemodal
popupClose() {
this.$emit('input', false);
},
//
clearLoading() {
this.loading = false;
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-model {
height: auto;
overflow: hidden;
font-size: 32rpx;
background-color: #fff;
&__btn--hover {
background-color: rgb(230, 230, 230);
}
&__title {
padding-top: 48rpx;
font-weight: 500;
text-align: center;
color: $u-main-color;
}
&__content {
&__message {
padding: 48rpx;
font-size: 30rpx;
text-align: center;
color: $u-content-color;
}
}
&__footer {
@include vue-flex;
&__button {
flex: 1;
height: 100rpx;
line-height: 100rpx;
font-size: 32rpx;
box-sizing: border-box;
cursor: pointer;
text-align: center;
border-radius: 4rpx;
}
}
}
</style>

View File

@ -0,0 +1,315 @@
<template>
<view class="">
<view class="u-navbar" :style="[navbarStyle]" :class="{ 'u-navbar-fixed': isFixed, 'u-border-bottom': borderBottom }">
<view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="u-navbar-inner" :style="[navbarInnerStyle]">
<view class="u-back-wrap" v-if="isBack" @tap="goBack">
<view class="u-icon-wrap">
<u-icon :name="backIconName" :color="backIconColor" :size="backIconSize"></u-icon>
</view>
<view class="u-icon-wrap u-back-text u-line-1" v-if="backText" :style="[backTextStyle]">{{ backText }}</view>
</view>
<view class="u-navbar-content-title" v-if="title" :style="[titleStyle]">
<view
class="u-title u-line-1"
:style="{
color: titleColor,
fontSize: titleSize + 'rpx',
fontWeight: titleBold ? 'bold' : 'normal'
}">
{{ title }}
</view>
</view>
<view class="u-slot-content">
<slot></slot>
</view>
<view class="u-slot-right">
<slot name="right"></slot>
</view>
</view>
</view>
<!-- 解决fixed定位后导航栏塌陷的问题 -->
<view class="u-navbar-placeholder" v-if="isFixed && !immersive" :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"></view>
</view>
</template>
<script>
//
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// (API)
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
/**
* navbar 自定义导航栏
* @description 此组件一般用于在特殊情况下需要自定义导航栏的时候用到一般建议使用uniapp自带的导航栏
* @tutorial https://www.uviewui.com/components/navbar.html
* @property {String Number} height 导航栏高度(不包括状态栏高度在内内部自动加上)注意这里的单位是px默认44
* @property {String} back-icon-color 左边返回图标的颜色默认#606266
* @property {String} back-icon-name 左边返回图标的名称只能为uView自带的图标默认arrow-left
* @property {String Number} back-icon-size 左边返回图标的大小单位rpx默认30
* @property {String} back-text 返回图标右边的辅助提示文字
* @property {Object} back-text-style 返回图标右边的辅助提示文字的样式对象形式默认{ color: '#606266' }
* @property {String} title 导航栏标题如设置为空字符将会隐藏标题占位区域
* @property {String Number} title-width 导航栏标题的最大宽度内容超出会以省略号隐藏单位rpx默认250
* @property {String} title-color 标题的颜色默认#606266
* @property {String Number} title-size 导航栏标题字体大小单位rpx默认32
* @property {Function} custom-back 自定义返回逻辑方法
* @property {String Number} z-index 固定在顶部时的z-index值默认980
* @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字默认true
* @property {Object} background 导航栏背景设置见官网说明默认{ background: '#ffffff' }
* @property {Boolean} is-fixed 导航栏是否固定在顶部默认true
* @property {Boolean} immersive 沉浸式允许fixed定位后导航栏塌陷仅fixed定位下生效默认false
* @property {Boolean} border-bottom 导航栏底部是否显示下边框如定义了较深的背景颜色可取消此值默认true
* @example <u-navbar back-text="" title="剑未配妥,出门已是江湖"></u-navbar>
*/
export default {
name: "u-navbar",
props: {
// pxrpx
height: {
type: [String, Number],
default: ''
},
//
backIconColor: {
type: String,
default: '#606266'
},
//
backIconName: {
type: String,
default: 'nav-back'
},
// rpx
backIconSize: {
type: [String, Number],
default: '44'
},
//
backText: {
type: String,
default: ''
},
//
backTextStyle: {
type: Object,
default () {
return {
color: '#606266'
}
}
},
//
title: {
type: String,
default: ''
},
// rpx
titleWidth: {
type: [String, Number],
default: '250'
},
//
titleColor: {
type: String,
default: '#606266'
},
//
titleBold: {
type: Boolean,
default: false
},
//
titleSize: {
type: [String, Number],
default: 32
},
isBack: {
type: [Boolean, String],
default: true
},
// 线
background: {
type: Object,
default () {
return {
background: '#ffffff'
}
}
},
//
isFixed: {
type: Boolean,
default: true
},
// fixedfixed
immersive: {
type: Boolean,
default: false
},
//
borderBottom: {
type: Boolean,
default: true
},
zIndex: {
type: [String, Number],
default: ''
},
//
customBack: {
type: Function,
default: null
}
},
data() {
return {
menuButtonInfo: menuButtonInfo,
statusBarHeight: systemInfo.statusBarHeight
};
},
computed: {
//
navbarInnerStyle() {
let style = {};
//
style.height = this.navbarHeight + 'px';
// //
// #ifdef MP
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
style.marginRight = rightButtonWidth + 'px';
// #endif
return style;
},
//
navbarStyle() {
let style = {};
style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.navbar;
//
Object.assign(style, this.background);
return style;
},
//
titleStyle() {
let style = {};
// #ifndef MP
style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
// #endif
// #ifdef MP
// 使
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth +
'px';
// #endif
style.width = uni.upx2px(this.titleWidth) + 'px';
return style;
},
//
navbarHeight() {
// #ifdef APP-PLUS || H5
return this.height ? this.height : 44;
// #endif
// #ifdef MP
// = + ()
// (px)
// return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//
let height = systemInfo.platform == 'ios' ? 44 : 48;
return this.height ? this.height : height;
// #endif
}
},
created() {},
methods: {
goBack() {
//
if (typeof this.customBack === 'function') {
// (H5)customBack()thisthis
// bind()thisthis.customBack()this
this.customBack.bind(this.$u.$parent.call(this))();
} else {
uni.navigateBack();
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-navbar {
width: 100%;
}
.u-navbar-fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
}
.u-status-bar {
width: 100%;
}
.u-navbar-inner {
@include vue-flex;
justify-content: space-between;
position: relative;
align-items: center;
}
.u-back-wrap {
@include vue-flex;
align-items: center;
flex: 1;
flex-grow: 0;
padding: 14rpx 14rpx 14rpx 24rpx;
}
.u-back-text {
padding-left: 4rpx;
font-size: 30rpx;
}
.u-navbar-content-title {
@include vue-flex;
align-items: center;
justify-content: center;
flex: 1;
position: absolute;
left: 0;
right: 0;
height: 60rpx;
text-align: center;
flex-shrink: 0;
}
.u-navbar-centent-slot {
flex: 1;
}
.u-title {
line-height: 60rpx;
font-size: 32rpx;
flex: 1;
}
.u-navbar-right {
flex: 1;
@include vue-flex;
align-items: center;
justify-content: flex-end;
}
.u-slot-content {
flex: 1;
@include vue-flex;
align-items: center;
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,272 @@
<template>
<view class="u-notice-bar-wrap" v-if="isShow" :style="{
borderRadius: borderRadius + 'rpx',
}">
<block v-if="mode == 'horizontal' && isCircular">
<u-row-notice
:type="type"
:color="color"
:bgColor="bgColor"
:list="list"
:volumeIcon="volumeIcon"
:moreIcon="moreIcon"
:volumeSize="volumeSize"
:closeIcon="closeIcon"
:mode="mode"
:fontSize="fontSize"
:speed="speed"
:playState="playState"
:padding="padding"
@getMore="getMore"
@close="close"
@click="click"
></u-row-notice>
</block>
<block v-if="mode == 'vertical' || (mode == 'horizontal' && !isCircular)">
<u-column-notice
:type="type"
:color="color"
:bgColor="bgColor"
:list="list"
:volumeIcon="volumeIcon"
:moreIcon="moreIcon"
:closeIcon="closeIcon"
:mode="mode"
:volumeSize="volumeSize"
:disable-touch="disableTouch"
:fontSize="fontSize"
:duration="duration"
:playState="playState"
:padding="padding"
@getMore="getMore"
@close="close"
@click="click"
@end="end"
></u-column-notice>
</block>
</view>
</template>
<script>
/**
* noticeBar 滚动通知
* @description 该组件用于滚动通告场景有多种模式可供选择
* @tutorial https://www.uviewui.com/components/noticeBar.html
* @property {Array} list 滚动内容数组形式见上方说明
* @property {String} type 显示的主题默认warning
* @property {Boolean} volume-icon 是否显示小喇叭图标默认true
* @property {Boolean} more-icon 是否显示右边的向右箭头默认false
* @property {Boolean} close-icon 是否显示关闭图标默认false
* @property {Boolean} autoplay 是否自动播放默认true
* @property {String} color 文字颜色
* @property {String Number} bg-color 背景颜色
* @property {String} mode 滚动模式默认horizontal
* @property {Boolean} show 是否显示默认true
* @property {String Number} font-size 字体大小单位rpx默认28
* @property {String Number} volume-size 左边喇叭的大小默认34
* @property {String Number} duration 滚动周期时长只对步进模式有效横向衔接模式无效单位ms默认2000
* @property {String Number} speed 水平滚动时的滚动速度即每秒移动多少距离只对水平衔接方式有效单位rpx默认160
* @property {String Number} font-size 字体大小单位rpx默认28
* @property {Boolean} is-circular mode为horizontal时指明是否水平衔接滚动默认true
* @property {String} play-state 播放状态play - 播放paused - 暂停默认play
* @property {String Nubmer} border-radius 通知栏圆角默认为0
* @property {String Nubmer} padding 内边距字符串与普通的内边距css写法一直默认"18rpx 24rpx"
* @property {Boolean} no-list-hidden 列表为空时是否显示组件默认false
* @property {Boolean} disable-touch 是否禁止通过手动滑动切换通知只有mode = vertical或者mode = horizontal且is-circular = false时有效默认true
* @event {Function} click 点击通告文字触发只有mode = vertical或者mode = horizontal且is-circular = false时有效
* @event {Function} close 点击右侧关闭图标触发
* @event {Function} getMore 点击右侧向右图标触发
* @event {Function} end 列表的消息每次被播放一个周期时触发只有mode = vertical或者mode = horizontal且is-circular = false时有效
* @example <u-notice-bar :more-icon="true" :list="list"></u-notice-bar>
*/
export default {
name: "u-notice-bar",
props: {
//
list: {
type: Array,
default() {
return [];
}
},
// success|error|primary|info|warning
type: {
type: String,
default: 'warning'
},
//
volumeIcon: {
type: Boolean,
default: true
},
//
volumeSize: {
type: [Number, String],
default: 34
},
//
moreIcon: {
type: Boolean,
default: false
},
//
closeIcon: {
type: Boolean,
default: false
},
//
autoplay: {
type: Boolean,
default: true
},
// 使
color: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: ''
},
// horizontal-vertical-
mode: {
type: String,
default: 'horizontal'
},
//
show: {
type: Boolean,
default: true
},
// rpx
fontSize: {
type: [Number, String],
default: 28
},
// ms
duration: {
type: [Number, String],
default: 2000
},
// rpx
speed: {
type: [Number, String],
default: 160
},
//
// swiper
isCircular: {
type: Boolean,
default: true
},
// play-paused-
playState: {
type: String,
default: 'play'
},
//
// HX2.6.11App 2.5.5+H5 2.5.5+
disableTouch: {
type: Boolean,
default: true
},
//
borderRadius: {
type: [Number, String],
default: 0
},
//
padding: {
type: [Number, String],
default: '18rpx 24rpx'
},
// list
noListHidden: {
type: Boolean,
default: true
}
},
computed: {
// showfalsenoListHiddentruelist
isShow() {
if(this.show == false || (this.noListHidden == true && this.list.length == 0)) return false;
else return true;
}
},
methods: {
//
click(index) {
this.$emit('click', index);
},
//
close() {
this.$emit('close');
},
//
getMore() {
this.$emit('getMore');
},
//
end() {
this.$emit('end');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-notice-bar-wrap {
overflow: hidden;
}
.u-notice-bar {
padding: 18rpx 24rpx;
overflow: hidden;
}
.u-direction-row {
@include vue-flex;
align-items: center;
justify-content: space-between;
}
.u-left-icon {
@include vue-flex;
align-items: center;
}
.u-notice-box {
flex: 1;
@include vue-flex;
overflow: hidden;
margin-left: 12rpx;
}
.u-right-icon {
margin-left: 12rpx;
@include vue-flex;
align-items: center;
}
.u-notice-content {
line-height: 1;
white-space: nowrap;
font-size: 26rpx;
animation: u-loop-animation 10s linear infinite both;
text-align: right;
//
padding-left: 100%;
}
@keyframes u-loop-animation {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-100%, 0, 0);
}
}
</style>

View File

@ -0,0 +1,363 @@
<template>
<view class="u-numberbox">
<view class="u-icon-minus" @touchstart.stop.prevent="btnTouchStart('minus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal <= min }"
:style="{
background: bgColor,
height: inputHeight + 'rpx',
color: color
}">
<u-icon name="minus" :size="size"></u-icon>
</view>
<input :disabled="disabledInput || disabled" :cursor-spacing="getCursorSpacing" :class="{ 'u-input-disabled': disabled }"
v-model="inputVal" class="u-number-input" @blur="onBlur" @focus="onFocus"
type="digit" :style="{
color: color,
fontSize: size + 'rpx',
background: bgColor,
height: inputHeight + 'rpx',
width: inputWidth + 'rpx'
}" />
<view class="u-icon-plus" @touchstart.stop.prevent="btnTouchStart('plus')" @touchend.stop.prevent="clearTimer" :class="{ 'u-icon-disabled': disabled || inputVal >= max }"
:style="{
background: bgColor,
height: inputHeight + 'rpx',
color: color
}">
<u-icon name="plus" :size="size"></u-icon>
</view>
</view>
</template>
<script>
/**
* numberBox 步进器
* @description 该组件一般用于商城购物选择物品数量的场景注意该输入框只能输入大于或等于0的整数不支持小数输入
* @tutorial https://www.uviewui.com/components/numberBox.html
* @property {Number} value 输入框初始值默认1
* @property {String} bg-color 输入框和按钮的背景颜色默认#F2F3F5
* @property {Number} min 用户可输入的最小值默认0
* @property {Number} max 用户可输入的最大值默认99999
* @property {Number} step 步长每次加或减的值默认1
* @property {Boolean} disabled 是否禁用操作禁用后无法加减或手动修改输入框的值默认false
* @property {Boolean} disabled-input 是否禁止输入框手动输入值默认false
* @property {Boolean} positive-integer 是否只能输入正整数默认true
* @property {String | Number} size 输入框文字和按钮字体大小单位rpx默认26
* @property {String} color 输入框文字和加减按钮图标的颜色默认#323233
* @property {String | Number} input-width 输入框宽度单位rpx默认80
* @property {String | Number} input-height 输入框和按钮的高度单位rpx默认50
* @property {String | Number} index 事件回调时用以区分当前发生变化的是哪个输入框
* @property {Boolean} long-press 是否开启长按连续递增或递减(默认true)
* @property {String | Number} press-time 开启长按触发后每触发一次需要多久单位ms(默认250)
* @property {String | Number} cursor-spacing 指定光标于键盘的距离避免键盘遮挡输入框单位rpx默认200
* @event {Function} change 输入框内容发生变化时触发对象形式
* @event {Function} blur 输入框失去焦点时触发对象形式
* @event {Function} minus 点击减少按钮时触发(按钮可点击情况下)对象形式
* @event {Function} plus 点击增加按钮时触发(按钮可点击情况下)对象形式
* @example <u-number-box :min="1" :max="100"></u-number-box>
*/
export default {
name: "u-number-box",
props: {
//
value: {
type: Number,
default: 1
},
//
bgColor: {
type: String,
default: '#F2F3F5'
},
//
min: {
type: Number,
default: 0
},
//
max: {
type: Number,
default: 99999
},
//
step: {
type: Number,
default: 1
},
//
disabled: {
type: Boolean,
default: false
},
// inputrpx
size: {
type: [Number, String],
default: 26
},
//
color: {
type: String,
default: '#323233'
},
// inputrpx
inputWidth: {
type: [Number, String],
default: 80
},
// inputrpx
inputHeight: {
type: [Number, String],
default: 50
},
// index使numberbox使forindex
index: {
type: [Number, String],
default: ''
},
// disabledOR
// disabledfalsedisabledInputtrue
disabledInput: {
type: Boolean,
default: false
},
//
cursorSpacing: {
type: [Number, String],
default: 100
},
//
longPress: {
type: Boolean,
default: true
},
//
pressTime: {
type: [Number, String],
default: 250
},
// 0()
positiveInteger: {
type: Boolean,
default: true
}
},
watch: {
value(v1, v2) {
// valueinputVal
if(!this.changeFromInner) {
this.inputVal = v1;
// inputValthis.handleChange()changeFromInnertrue
// this.$nextTick
// changeFromInnerfalse
this.$nextTick(function(){
this.changeFromInner = false;
})
}
},
inputVal(v1, v2) {
//
if (v1 == '') return;
let value = 0;
// minmax使
let tmp = this.$u.test.number(v1);
if (tmp && v1 >= this.min && v1 <= this.max) value = v1;
else value = v2;
// 0
if(this.positiveInteger) {
// 0
if(v1 < 0 || String(v1).indexOf('.') !== -1) {
value = v2;
// input使$nextTick
this.$nextTick(() => {
this.inputVal = v2;
})
}
}
// change
this.handleChange(value, 'change');
}
},
data() {
return {
inputVal: 1, // 使propsvalueprops
timer: null, //
changeFromInner: false, //
innerChangeTimer: null, //
};
},
created() {
this.inputVal = Number(this.value);
},
computed: {
getCursorSpacing() {
// px
return Number(uni.upx2px(this.cursorSpacing));
}
},
methods: {
// 退
btnTouchStart(callback) {
// clearTimer
this[callback]();
//
if (!this.longPress) return;
clearInterval(this.timer); //
this.timer = null;
this.timer = setInterval(() => {
//
this[callback]();
}, this.pressTime);
},
clearTimer() {
this.$nextTick(() => {
clearInterval(this.timer);
this.timer = null;
})
},
minus() {
this.computeVal('minus');
},
plus() {
this.computeVal('plus');
},
//
calcPlus(num1, num2) {
let baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.toString().split('.')[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split('.')[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2; //
return ((num1 * baseNum + num2 * baseNum) / baseNum).toFixed(precision);
},
//
calcMinus(num1, num2) {
let baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.toString().split('.')[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.toString().split('.')[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
let precision = baseNum1 >= baseNum2 ? baseNum1 : baseNum2;
return ((num1 * baseNum - num2 * baseNum) / baseNum).toFixed(precision);
},
computeVal(type) {
uni.hideKeyboard();
if (this.disabled) return;
let value = 0;
//
if (type === 'minus') {
value = this.calcMinus(this.inputVal, this.step);
} else if (type === 'plus') {
value = this.calcPlus(this.inputVal, this.step);
}
//
if (value < this.min || value > this.max) {
return;
}
this.inputVal = value;
this.handleChange(value, type);
},
//
onBlur(event) {
let val = 0;
let value = event.detail.value;
// 0-90min
// props min0
if (!/(^\d+$)/.test(value) || value[0] == 0) val = this.min;
val = +value;
if (val > this.max) {
val = this.max;
} else if (val < this.min) {
val = this.min;
}
this.$nextTick(() => {
this.inputVal = val;
})
this.handleChange(val, 'blur');
},
//
onFocus() {
this.$emit('focus');
},
handleChange(value, type) {
if (this.disabled) return;
//
if(this.innerChangeTimer) {
clearTimeout(this.innerChangeTimer);
this.innerChangeTimer = null;
}
// inputv-model
this.changeFromInner = true;
// changeFromInner
// value
this.innerChangeTimer = setTimeout(() => {
this.changeFromInner = false;
}, 150);
this.$emit('input', Number(value));
this.$emit(type, {
// Number
value: Number(value),
index: this.index
})
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-numberbox {
display: inline-flex;
align-items: center;
}
.u-number-input {
position: relative;
text-align: center;
padding: 0;
margin: 0 6rpx;
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-icon-plus,
.u-icon-minus {
width: 60rpx;
@include vue-flex;
justify-content: center;
align-items: center;
}
.u-icon-plus {
border-radius: 0 8rpx 8rpx 0;
}
.u-icon-minus {
border-radius: 8rpx 0 0 8rpx;
}
.u-icon-disabled {
color: #c8c9cc !important;
background: #f7f8fa !important;
}
.u-input-disabled {
color: #c8c9cc !important;
background-color: #f2f3f5 !important;
}
</style>

View File

@ -0,0 +1,158 @@
<template>
<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
<view class="u-keyboard-grids">
<view
class="u-keyboard-grids-item"
:class="[btnBgGray(index) ? 'u-bg-gray' : '', index <= 2 ? 'u-border-top' : '', index < 9 ? 'u-border-bottom' : '', (index + 1) % 3 != 0 ? 'u-border-right' : '']"
:style="[itemStyle(index)]"
v-for="(item, index) in numList"
:key="index"
:hover-class="hoverClass(index)"
:hover-stay-time="100"
@tap="keyboardClick(item)">
<view class="u-keyboard-grids-btn">{{ item }}</view>
</view>
<view class="u-keyboard-grids-item u-bg-gray" hover-class="u-hover-class" :hover-stay-time="100" @touchstart.stop="backspaceClick"
@touchend="clearTimer">
<view class="u-keyboard-back u-keyboard-grids-btn">
<u-icon name="backspace" :size="38" :bold="true"></u-icon>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
// number-card-
mode: {
type: String,
default: 'number'
},
// "."
dotEnabled: {
type: Boolean,
default: true
},
//
random: {
type: Boolean,
default: false
}
},
data() {
return {
backspace: 'backspace', // 退
dot: '.', //
timer: null, //
cardX: 'X' // X
};
},
computed: {
//
numList() {
let tmp = [];
if (!this.dotEnabled && this.mode == 'number') {
if (!this.random) {
return [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
} else {
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);
}
} else if (this.dotEnabled && this.mode == 'number') {
if (!this.random) {
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];
} else {
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);
}
} else if (this.mode == 'card') {
if (!this.random) {
return [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];
} else {
return this.$u.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);
}
}
},
// &&&&index9
itemStyle() {
return index => {
let style = {};
if (this.mode == 'number' && !this.dotEnabled && index == 9) style.flex = '0 0 66.6666666666%';
return style;
};
},
// &&&&
btnBgGray() {
return index => {
if (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && this.dotEnabled))) return true;
else return false;
};
},
hoverClass() {
return index => {
if (!this.random && index == 9 && (this.mode == 'number' && this.dotEnabled || this.mode == 'card')) return 'u-hover-class';
else return 'u-keyboard-hover';
}
}
},
methods: {
// 退
backspaceClick() {
this.$emit('backspace');
clearInterval(this.timer); //
this.timer = null;
this.timer = setInterval(() => {
this.$emit('backspace');
}, 250);
},
clearTimer() {
clearInterval(this.timer);
this.timer = null;
},
//
keyboardClick(val) {
//
if (this.dotEnabled && val != this.dot && val != this.cardX) val = Number(val);
this.$emit('change', val);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-keyboard {
position: relative;
z-index: 1003;
}
.u-keyboard-grids {
@include vue-flex;
flex-wrap: wrap;
}
.u-keyboard-grids-item {
flex: 0 0 33.3333333333%;
text-align: center;
font-size: 50rpx;
color: #333;
@include vue-flex;
align-items: center;
justify-content: center;
height: 110rpx;
font-weight: 500;
}
.u-bg-gray {
background-color: $u-border-color;
}
.u-keyboard-back {
font-size: 36rpx;
}
.u-keyboard-hover {
background-color: #e7e6eb;
}
</style>

View File

@ -0,0 +1,100 @@
const cfg = require('./config.js'),
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
function CssHandler(tagStyle) {
var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
for (var item in tagStyle)
styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
this.styles = styles;
}
CssHandler.prototype.getStyle = function(data) {
this.styles = new parser(data, this.styles).parse();
}
CssHandler.prototype.match = function(name, attrs) {
var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
if (attrs.class) {
var items = attrs.class.split(' ');
for (var i = 0, item; item = items[i]; i++)
if (tmp = this.styles['.' + item])
matched += tmp + ';';
}
if (tmp = this.styles['#' + attrs.id])
matched += tmp + ';';
return matched;
}
module.exports = CssHandler;
function parser(data, init) {
this.data = data;
this.floor = 0;
this.i = 0;
this.list = [];
this.res = init;
this.state = this.Space;
}
parser.prototype.parse = function() {
for (var c; c = this.data[this.i]; this.i++)
this.state(c);
return this.res;
}
parser.prototype.section = function() {
return this.data.substring(this.start, this.i);
}
// 状态机
parser.prototype.Space = function(c) {
if (c == '.' || c == '#' || isLetter(c)) {
this.start = this.i;
this.state = this.Name;
} else if (c == '/' && this.data[this.i + 1] == '*')
this.Comment();
else if (!cfg.blankChar[c] && c != ';')
this.state = this.Ignore;
}
parser.prototype.Comment = function() {
this.i = this.data.indexOf('*/', this.i) + 1;
if (!this.i) this.i = this.data.length;
this.state = this.Space;
}
parser.prototype.Ignore = function(c) {
if (c == '{') this.floor++;
else if (c == '}' && !--this.floor) {
this.list = [];
this.state = this.Space;
}
}
parser.prototype.Name = function(c) {
if (cfg.blankChar[c]) {
this.list.push(this.section());
this.state = this.NameSpace;
} else if (c == '{') {
this.list.push(this.section());
this.Content();
} else if (c == ',') {
this.list.push(this.section());
this.Comma();
} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
this.state = this.Ignore;
}
parser.prototype.NameSpace = function(c) {
if (c == '{') this.Content();
else if (c == ',') this.Comma();
else if (!cfg.blankChar[c]) this.state = this.Ignore;
}
parser.prototype.Comma = function() {
while (cfg.blankChar[this.data[++this.i]]);
if (this.data[this.i] == '{') this.Content();
else {
this.start = this.i--;
this.state = this.Name;
}
}
parser.prototype.Content = function() {
this.start = ++this.i;
if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
var content = this.section();
for (var i = 0, item; item = this.list[i++];)
if (this.res[item]) this.res[item] += ';' + content;
else this.res[item] = content;
this.list = [];
this.state = this.Space;
}

View File

@ -0,0 +1,580 @@
/**
* html 解析器
* @tutorial https://github.com/jin-yufeng/Parser
* @version 20201029
* @author JinYufeng
* @listens MIT
*/
const cfg = require('./config.js'),
blankChar = cfg.blankChar,
CssHandler = require('./CssHandler.js'),
windowWidth = uni.getSystemInfoSync().windowWidth;
var emoji;
function MpHtmlParser(data, options = {}) {
this.attrs = {};
this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
this.data = data;
this.domain = options.domain;
this.DOM = [];
this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
this.options = options;
this.state = this.Text;
this.STACK = [];
// 工具函数
this.bubble = () => {
for (var i = this.STACK.length, item; item = this.STACK[--i];) {
if (cfg.richOnlyTags[item.name]) return false;
item.c = 1;
}
return true;
}
this.decode = (val, amp) => {
var i = -1,
j, en;
while (1) {
if ((i = val.indexOf('&', i + 1)) == -1) break;
if ((j = val.indexOf(';', i + 2)) == -1) break;
if (val[i + 1] == '#') {
en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
} else {
en = val.substring(i + 1, j);
if (cfg.entities[en] || en == amp)
val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
}
}
return val;
}
this.getUrl = url => {
if (url[0] == '/') {
if (url[1] == '/') url = this.options.prot + ':' + url;
else if (this.domain) url = this.domain + url;
} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
url = this.domain + '/' + url;
return url;
}
this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
this.section = () => this.data.substring(this.start, this.i);
this.parent = () => this.STACK[this.STACK.length - 1];
this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
}
MpHtmlParser.prototype.parse = function() {
if (emoji) this.data = emoji.parseEmoji(this.data);
for (var c; c = this.data[this.i]; this.i++)
this.state(c);
if (this.state == this.Text) this.setText();
while (this.STACK.length) this.popNode(this.STACK.pop());
return this.DOM;
}
// 设置属性
MpHtmlParser.prototype.setAttr = function() {
var name = this.attrName.toLowerCase(),
val = this.attrVal;
if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
else if (val) {
if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
}
this.attrVal = '';
while (blankChar[this.data[this.i]]) this.i++;
if (this.isClose()) this.setNode();
else {
this.start = this.i;
this.state = this.AttrName;
}
}
// 设置文本节点
MpHtmlParser.prototype.setText = function() {
var back, text = this.section();
if (!text) return;
text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
if (back) {
this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
let j = this.start + text.length;
for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
return;
}
if (!this.pre) {
// 合并空白符
var flag, tmp = [];
for (let i = text.length, c; c = text[--i];)
if (!blankChar[c]) {
tmp.unshift(c);
if (!flag) flag = 1;
} else {
if (tmp[0] != ' ') tmp.unshift(' ');
if (c == '\n' && flag == void 0) flag = 0;
}
if (flag == 0) return;
text = tmp.join('');
}
this.siblings().push({
type: 'text',
text: this.decode(text)
});
}
// 设置元素节点
MpHtmlParser.prototype.setNode = function() {
var node = {
name: this.tagName.toLowerCase(),
attrs: this.attrs
},
close = cfg.selfClosingTags[node.name];
if (this.options.nodes.length) node.type = 'node';
this.attrs = {};
if (!cfg.ignoreTags[node.name]) {
// 处理属性
var attrs = node.attrs,
style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
styleObj = {};
if (attrs.id) {
if (this.options.compress & 1) attrs.id = void 0;
else if (this.options.useAnchor) this.bubble();
}
if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
switch (node.name) {
case 'a':
case 'ad': // #ifdef APP-PLUS
case 'iframe':
// #endif
this.bubble();
break;
case 'font':
if (attrs.color) {
styleObj['color'] = attrs.color;
attrs.color = void 0;
}
if (attrs.face) {
styleObj['font-family'] = attrs.face;
attrs.face = void 0;
}
if (attrs.size) {
var size = parseInt(attrs.size);
if (size < 1) size = 1;
else if (size > 7) size = 7;
var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
styleObj['font-size'] = map[size - 1];
attrs.size = void 0;
}
break;
case 'embed':
// #ifndef APP-PLUS
var src = node.attrs.src || '',
type = node.attrs.type || '';
if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
node.name = 'video';
else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
'.aac'))
node.name = 'audio';
else break;
if (node.attrs.autostart)
node.attrs.autoplay = 'T';
node.attrs.controls = 'T';
// #endif
// #ifdef APP-PLUS
this.bubble();
break;
// #endif
case 'video':
case 'audio':
if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
else this[`${node.name}Num`]++;
if (node.name == 'video') {
if (this.videoNum > 3)
node.lazyLoad = 1;
if (attrs.width) {
styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
attrs.width = void 0;
}
if (attrs.height) {
styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
attrs.height = void 0;
}
}
if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T';
attrs.source = [];
if (attrs.src) {
attrs.source.push(attrs.src);
attrs.src = void 0;
}
this.bubble();
break;
case 'td':
case 'th':
if (attrs.colspan || attrs.rowspan)
for (var k = this.STACK.length, item; item = this.STACK[--k];)
if (item.name == 'table') {
item.flag = 1;
break;
}
}
if (attrs.align) {
if (node.name == 'table') {
if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto';
else styleObj['float'] = attrs.align;
} else styleObj['text-align'] = attrs.align;
attrs.align = void 0;
}
// 压缩 style
var styles = style.split(';');
style = '';
for (var i = 0, len = styles.length; i < len; i++) {
var info = styles[i].split(':');
if (info.length < 2) continue;
let key = info[0].trim().toLowerCase(),
value = info.slice(1).join(':').trim();
if (value[0] == '-' || value.includes('safe'))
style += `;${key}:${value}`;
else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
styleObj[key] = value;
}
if (node.name == 'img') {
if (attrs.src && !attrs.ignore) {
if (this.bubble())
attrs.i = (this.imgNum++).toString();
else attrs.ignore = 'T';
}
if (attrs.ignore) {
style += ';-webkit-touch-callout:none';
styleObj['max-width'] = '100%';
}
var width;
if (styleObj.width) width = styleObj.width;
else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : parseFloat(attrs.width) + 'px';
if (width) {
styleObj.width = width;
attrs.width = '100%';
if (parseInt(width) > windowWidth) {
styleObj.height = '';
if (attrs.height) attrs.height = void 0;
}
}
if (styleObj.height) {
attrs.height = styleObj.height;
styleObj.height = '';
} else if (attrs.height && !attrs.height.includes('%'))
attrs.height = parseFloat(attrs.height) + 'px';
}
for (var key in styleObj) {
var value = styleObj[key];
if (!value) continue;
if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
// 填充链接
if (value.includes('url')) {
var j = value.indexOf('(');
if (j++ != -1) {
while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
value = value.substr(0, j) + this.getUrl(value.substr(j));
}
}
// 转换 rpx
else if (value.includes('rpx'))
value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px');
else if (key == 'white-space' && value.includes('pre') && !close)
this.pre = node.pre = true;
style += `;${key}:${value}`;
}
style = style.substr(1);
if (style) attrs.style = style;
if (!close) {
node.children = [];
if (node.name == 'pre' && cfg.highlight) {
this.remove(node);
this.pre = node.pre = true;
}
this.siblings().push(node);
this.STACK.push(node);
} else if (!cfg.filter || cfg.filter(node, this) != false)
this.siblings().push(node);
} else {
if (!close) this.remove(node);
else if (node.name == 'source') {
var parent = this.parent();
if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
parent.attrs.source.push(node.attrs.src);
} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
}
if (this.data[this.i] == '/') this.i++;
this.start = this.i + 1;
this.state = this.Text;
}
// 移除标签
MpHtmlParser.prototype.remove = function(node) {
var name = node.name,
j = this.i;
// 处理 svg
var handleSvg = () => {
var src = this.data.substring(j, this.i + 1);
node.attrs.xmlns = 'http://www.w3.org/2000/svg';
for (var key in node.attrs) {
if (key == 'viewbox') src = ` viewBox="${node.attrs.viewbox}"` + src;
else if (key != 'style') src = ` ${key}="${node.attrs[key]}"` + src;
}
src = '<svg' + src;
var parent = this.parent();
if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
this.siblings().push({
name: 'img',
attrs: {
src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
style: node.attrs.style,
ignore: 'T'
}
})
}
if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++);
while (1) {
if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
if (name == 'pre' || name == 'svg') this.i = j;
else this.i = this.data.length;
return;
}
this.start = (this.i += 2);
while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
if (this.section().toLowerCase() == name) {
// 代码块高亮
if (name == 'pre') {
this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data
.substr(this.i - 5);
return this.i = j;
} else if (name == 'style')
this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
else if (name == 'title')
this.DOM.title = this.data.substring(j + 1, this.i - 7);
if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
if (name == 'svg') handleSvg();
return;
}
}
}
// 节点出栈处理
MpHtmlParser.prototype.popNode = function(node) {
// 空白符处理
if (node.pre) {
node.pre = this.pre = void 0;
for (let i = this.STACK.length; i--;)
if (this.STACK[i].pre)
this.pre = true;
}
var siblings = this.siblings(),
len = siblings.length,
childs = node.children;
if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
return siblings.pop();
var attrs = node.attrs;
// 替换一些标签名
if (cfg.blockTags[node.name]) node.name = 'div';
else if (!cfg.trustTags[node.name]) node.name = 'span';
// 处理列表
if (node.c && (node.name == 'ul' || node.name == 'ol')) {
if ((node.attrs.style || '').includes('list-style:none')) {
for (let i = 0, child; child = childs[i++];)
if (child.name == 'li')
child.name = 'div';
} else if (node.name == 'ul') {
var floor = 1;
for (let i = this.STACK.length; i--;)
if (this.STACK[i].name == 'ul') floor++;
if (floor != 1)
for (let i = childs.length; i--;)
childs[i].floor = floor;
} else {
for (let i = 0, num = 1, child; child = childs[i++];)
if (child.name == 'li') {
child.type = 'ol';
child.num = ((num, type) => {
if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
if (type == 'i' || type == 'I') {
num = (num - 1) % 99 + 1;
var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
if (type == 'i') return res.toLowerCase();
return res;
}
return num;
})(num++, attrs.type) + '.';
}
}
}
// 处理表格
if (node.name == 'table') {
var padding = parseFloat(attrs.cellpadding),
spacing = parseFloat(attrs.cellspacing),
border = parseFloat(attrs.border);
if (node.c) {
if (isNaN(padding)) padding = 2;
if (isNaN(spacing)) spacing = 2;
}
if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
if (node.flag && node.c) {
// 有 colspan 或 rowspan 且含有链接的表格转为 grid 布局实现
attrs.style = `${attrs.style || ''};${spacing ? `;grid-gap:${spacing}px` : ';border-left:0;border-top:0'}`;
var row = 1,
col = 1,
colNum,
trs = [],
children = [],
map = {};
(function f(ns) {
for (var i = 0; i < ns.length; i++) {
if (ns[i].name == 'tr') trs.push(ns[i]);
else f(ns[i].children || []);
}
})(node.children)
for (let i = 0; i < trs.length; i++) {
for (let j = 0, td; td = trs[i].children[j]; j++) {
if (td.name == 'td' || td.name == 'th') {
while (map[row + '.' + col]) col++;
var cell = {
name: 'div',
c: 1,
attrs: {
style: (td.attrs.style || '') + (border ? `;border:${border}px solid gray` + (spacing ? '' :
';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '')
},
children: td.children
}
if (td.attrs.colspan) {
cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + parseInt(td.attrs.colspan));
if (!td.attrs.rowspan) cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + 1);
col += parseInt(td.attrs.colspan) - 1;
}
if (td.attrs.rowspan) {
cell.attrs.style += ';grid-row-start:' + row + ';grid-row-end:' + (row + parseInt(td.attrs.rowspan));
if (!td.attrs.colspan) cell.attrs.style += ';grid-column-start:' + col + ';grid-column-end:' + (col + 1);
for (var k = 1; k < td.attrs.rowspan; k++) map[(row + k) + '.' + col] = 1;
}
children.push(cell);
col++;
}
}
if (!colNum) {
colNum = col - 1;
attrs.style += `;grid-template-columns:repeat(${colNum},auto)`
}
col = 1;
row++;
}
node.children = children;
} else {
attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
if (border || padding)
(function f(ns) {
for (var i = 0, n; n = ns[i]; i++) {
if (n.name == 'th' || n.name == 'td') {
if (border) n.attrs.style = `border:${border}px solid gray;${n.attrs.style || ''}`;
if (padding) n.attrs.style = `padding:${padding}px;${n.attrs.style || ''}`;
} else f(n.children || []);
}
})(childs)
}
if (this.options.autoscroll) {
var table = Object.assign({}, node);
node.name = 'div';
node.attrs = {
style: 'overflow:scroll'
}
node.children = [table];
}
}
this.CssHandler.pop && this.CssHandler.pop(node);
// 自动压缩
if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
siblings[len - 1] = childs[0];
}
// 状态机
MpHtmlParser.prototype.Text = function(c) {
if (c == '<') {
var next = this.data[this.i + 1],
isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
if (isLetter(next)) {
this.setText();
this.start = this.i + 1;
this.state = this.TagName;
} else if (next == '/') {
this.setText();
if (isLetter(this.data[++this.i + 1])) {
this.start = this.i + 1;
this.state = this.EndTag;
} else this.Comment();
} else if (next == '!' || next == '?') {
this.setText();
this.Comment();
}
}
}
MpHtmlParser.prototype.Comment = function() {
var key;
if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
else key = '>';
if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
else this.i += key.length - 1;
this.start = this.i + 1;
this.state = this.Text;
}
MpHtmlParser.prototype.TagName = function(c) {
if (blankChar[c]) {
this.tagName = this.section();
while (blankChar[this.data[this.i]]) this.i++;
if (this.isClose()) this.setNode();
else {
this.start = this.i;
this.state = this.AttrName;
}
} else if (this.isClose()) {
this.tagName = this.section();
this.setNode();
}
}
MpHtmlParser.prototype.AttrName = function(c) {
if (c == '=' || blankChar[c] || this.isClose()) {
this.attrName = this.section();
if (blankChar[c])
while (blankChar[this.data[++this.i]]);
if (this.data[this.i] == '=') {
while (blankChar[this.data[++this.i]]);
this.start = this.i--;
this.state = this.AttrValue;
} else this.setAttr();
}
}
MpHtmlParser.prototype.AttrValue = function(c) {
if (c == '"' || c == "'") {
this.start++;
if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
this.attrVal = this.section();
this.i++;
} else {
for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
this.attrVal = this.section();
}
this.setAttr();
}
MpHtmlParser.prototype.EndTag = function(c) {
if (blankChar[c] || c == '>' || c == '/') {
var name = this.section().toLowerCase();
for (var i = this.STACK.length; i--;)
if (this.STACK[i].name == name) break;
if (i != -1) {
var node;
while ((node = this.STACK.pop()).name != name) this.popNode(node);
this.popNode(node);
} else if (name == 'p' || name == 'br')
this.siblings().push({
name,
attrs: {}
});
this.i = this.data.indexOf('>', this.i);
this.start = this.i + 1;
if (this.i == -1) this.i = this.data.length;
else this.state = this.Text;
}
}
module.exports = MpHtmlParser;

View File

@ -0,0 +1,80 @@
/* 配置文件 */
var cfg = {
// 出错占位图
errorImg: null,
// 过滤器函数
filter: null,
// 代码高亮函数
highlight: null,
// 文本处理函数
onText: null,
// 实体编码列表
entities: {
quot: '"',
apos: "'",
semi: ';',
nbsp: '\xA0',
ensp: '\u2002',
emsp: '\u2003',
ndash: '',
mdash: '—',
middot: '·',
lsquo: '',
rsquo: '',
ldquo: '“',
rdquo: '”',
bull: '•',
hellip: '…'
},
blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
// 块级标签,将被转为 div
blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
// 将被移除的标签
ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'),
// 只能被 rich-text 显示的标签
richOnlyTags: makeMap('a,colgroup,fieldset,legend'),
// 自闭合的标签
selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
// 信任的标签
trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
// 默认的标签样式
userAgentStyles: {
address: 'font-style:italic',
big: 'display:inline;font-size:1.2em',
blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
caption: 'display:table-caption;text-align:center',
center: 'text-align:center',
cite: 'font-style:italic',
dd: 'margin-left:40px',
mark: 'background-color:yellow',
pre: 'font-family:monospace;white-space:pre;overflow:scroll',
s: 'text-decoration:line-through',
small: 'display:inline;font-size:0.8em',
u: 'text-decoration:underline'
}
}
function makeMap(str) {
var map = Object.create(null),
list = str.split(',');
for (var i = list.length; i--;)
map[list[i]] = true;
return map;
}
// #ifdef MP-WEIXIN
if (wx.canIUse('editor')) {
cfg.blockTags.pre = void 0;
cfg.ignoreTags.rp = true;
Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby'));
Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby'));
}
// #endif
// #ifdef APP-PLUS
cfg.ignoreTags.iframe = void 0;
Object.assign(cfg.trustTags, makeMap('embed,iframe'));
// #endif
module.exports = cfg;

View File

@ -0,0 +1,22 @@
var inline = {
abbr: 1,
b: 1,
big: 1,
code: 1,
del: 1,
em: 1,
i: 1,
ins: 1,
label: 1,
q: 1,
small: 1,
span: 1,
strong: 1,
sub: 1,
sup: 1
}
module.exports = {
use: function(item) {
return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
}
}

View File

@ -0,0 +1,505 @@
<template>
<view :class="'interlayer '+(c||'')" :style="s">
<block v-for="(n, i) in nodes" v-bind:key="i">
<!--图片-->
<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap.stop="imgtap">
<rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
<image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
:show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
@error="error" />
</view>
<!--文本-->
<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
<!--#ifndef MP-BAIDU-->
<text v-else-if="n.name=='br'">\n</text>
<!--#endif-->
<!--视频-->
<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id"
:class="'_video '+(n.attrs.class||'')" :style="n.attrs.style" :data-i="i" @tap.stop="_loadVideo" />
<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0"
:controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]"
:unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
<!--音频-->
<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
:src="n.attrs.source[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio" @error.native="error"
@play.native="play" />
<!--链接-->
<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
:data-attrs="n.attrs" @tap.stop="linkpress">
<trees class="_span" c="_span" :nodes="n.children" />
</view>
<!--广告-->
<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
<!--列表-->
<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'">
<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
<view v-else class="_ul-bef">
<view v-if="n.floor%3==0" class="_ul-p1"></view>
<view v-else-if="n.floor%3==2" class="_ul-p2" />
<view v-else class="_ul-p1" style="border-radius:50%"></view>
</view>
<trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
</view>
<!--表格-->
<view v-else-if="n.name=='table'&&n.c&&n.flag" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:grid'">
<trees v-for="(cell,n) in n.children" v-bind:key="n" :class="cell.attrs.class" :c="cell.attrs.class" :style="cell.attrs.style"
:s="cell.attrs.style" :nodes="cell.children" />
</view>
<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
<view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
<view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
<trees v-if="tr.name=='td'" :nodes="tr.children" />
<trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
:s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
</view>
</view>
</view>
<!--#ifdef APP-PLUS-->
<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
:width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
<!--#endif-->
<!--富文本-->
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
<!--#endif-->
<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
<!--#endif-->
<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
:style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
</block>
</view>
</template>
<script module="handler" lang="wxs" src="./handler.wxs"></script>
<script>
global.Parser = {};
import trees from './trees'
const errorImg = require('../libs/config.js').errorImg;
export default {
components: {
trees
},
name: 'trees',
data() {
return {
ctrl: [],
placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
errorImg,
loadVideo: typeof plus == 'undefined',
// #ifndef MP-ALIPAY
c: '',
s: ''
// #endif
}
},
props: {
nodes: Array,
lazyLoad: Boolean,
loading: String,
// #ifdef MP-ALIPAY
c: String,
s: String
// #endif
},
mounted() {
for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
this.init();
},
// #ifdef APP-PLUS
beforeDestroy() {
this.observer && this.observer.disconnect();
},
// #endif
methods: {
init() {
for (var i = this.nodes.length, n; n = this.nodes[--i];) {
if (n.name == 'img') {
this.top.imgList.setItem(n.attrs.i, n.attrs['original-src'] || n.attrs.src);
// #ifdef APP-PLUS
if (this.lazyLoad && !this.observer) {
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
top: 500,
bottom: 500
});
setTimeout(() => {
this.observer.observe('._img', res => {
if (res.intersectionRatio) {
for (var j = this.nodes.length; j--;)
if (this.nodes[j].name == 'img')
this.$set(this.ctrl, j, 1);
this.observer.disconnect();
}
})
}, 0)
}
// #endif
} else if (n.name == 'video' || n.name == 'audio') {
var ctx;
if (n.name == 'video') {
ctx = uni.createVideoContext(n.attrs.id
// #ifndef MP-BAIDU
, this
// #endif
);
} else if (this.$refs[n.attrs.id])
ctx = this.$refs[n.attrs.id][0];
if (ctx) {
ctx.id = n.attrs.id;
this.top.videoContexts.push(ctx);
}
}
}
// #ifdef APP-PLUS
// APP video
setTimeout(() => {
this.loadVideo = true;
}, 1000)
// #endif
},
play(e) {
var contexts = this.top.videoContexts;
if (contexts.length > 1 && this.top.autopause)
for (var i = contexts.length; i--;)
if (contexts[i].id != e.currentTarget.dataset.id)
contexts[i].pause();
},
imgtap(e) {
var attrs = e.currentTarget.dataset.attrs;
if (!attrs.ignore) {
var preview = true,
data = {
id: e.target.id,
src: attrs.src,
ignore: () => preview = false
};
global.Parser.onImgtap && global.Parser.onImgtap(data);
this.top.$emit('imgtap', data);
if (preview) {
var urls = this.top.imgList,
current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
uni.previewImage({
current,
urls
})
}
}
},
loadImg(e) {
var i = e.currentTarget.dataset.i;
if (this.lazyLoad && !this.ctrl[i]) {
// #ifdef QUICKAPP-WEBVIEW
this.$set(this.ctrl, i, 0);
this.$nextTick(function() {
// #endif
// #ifndef APP-PLUS
this.$set(this.ctrl, i, 1);
// #endif
// #ifdef QUICKAPP-WEBVIEW
})
// #endif
} else if (this.loading && this.ctrl[i] != 2) {
// #ifdef QUICKAPP-WEBVIEW
this.$set(this.ctrl, i, 0);
this.$nextTick(function() {
// #endif
this.$set(this.ctrl, i, 2);
// #ifdef QUICKAPP-WEBVIEW
})
// #endif
}
},
linkpress(e) {
var jump = true,
attrs = e.currentTarget.dataset.attrs;
attrs.ignore = () => jump = false;
global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
this.top.$emit('linkpress', attrs);
if (jump) {
// #ifdef MP
if (attrs['app-id']) {
return uni.navigateToMiniProgram({
appId: attrs['app-id'],
path: attrs.path
})
}
// #endif
if (attrs.href) {
if (attrs.href[0] == '#') {
if (this.top.useAnchor)
this.top.navigateTo({
id: attrs.href.substring(1)
})
} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
// #ifdef APP-PLUS
plus.runtime.openWeb(attrs.href);
// #endif
// #ifndef APP-PLUS
uni.setClipboardData({
data: attrs.href,
success: () =>
uni.showToast({
title: '链接已复制'
})
})
// #endif
} else
uni.navigateTo({
url: attrs.href,
fail() {
uni.switchTab({
url: attrs.href,
})
}
})
}
}
},
error(e) {
var target = e.currentTarget,
source = target.dataset.source,
i = target.dataset.i;
if (source == 'video' || source == 'audio') {
// source
var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
if (index < this.nodes[i].attrs.source.length)
this.$set(this.ctrl, i, index);
if (e.detail.__args__)
e.detail = e.detail.__args__[0];
} else if (errorImg && source == 'img') {
this.top.imgList.setItem(target.dataset.index, errorImg);
this.$set(this.ctrl, i, 3);
}
this.top && this.top.$emit('error', {
source,
target,
errMsg: e.detail.errMsg
});
},
_loadVideo(e) {
this.$set(this.ctrl, e.target.dataset.i, 0);
}
}
}
</script>
<style>
/* 在这里引入自定义样式 */
/* 链接和图片效果 */
._a {
display: inline;
padding: 1.5px 0 1.5px 0;
color: #366092;
word-break: break-all;
}
._hover {
text-decoration: underline;
opacity: 0.7;
}
._img {
display: inline-block;
max-width: 100%;
overflow: hidden;
}
/* #ifdef MP-WEIXIN */
:host {
display: inline;
}
/* #endif */
/* #ifndef MP-ALIPAY || APP-PLUS */
.interlayer {
display: inherit;
flex-direction: inherit;
flex-wrap: inherit;
align-content: inherit;
align-items: inherit;
justify-content: inherit;
width: 100%;
white-space: inherit;
}
/* #endif */
._b,
._strong {
font-weight: bold;
}
/* #ifndef MP-ALIPAY */
._blockquote,
._div,
._p,
._ol,
._ul,
._li {
display: block;
}
/* #endif */
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
display: block;
width: 100%;
height: 360px;
margin-top: -360px;
opacity: 0;
}
._ins {
text-decoration: underline;
}
._li {
flex: 1;
width: 0;
}
._ol-bef {
width: 36px;
margin-right: 5px;
text-align: right;
}
._ul-bef {
display: block;
margin: 0 12px 0 23px;
line-height: normal;
}
._ol-bef,
._ul-bef {
flex: none;
user-select: none;
}
._ul-p1 {
display: inline-block;
width: 0.3em;
height: 0.3em;
overflow: hidden;
line-height: 0.3em;
}
._ul-p2 {
display: inline-block;
width: 0.23em;
height: 0.23em;
border: 0.05em solid black;
border-radius: 50%;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #endif */
/* #ifdef MP-WEIXIN || MP-QQ */
.__bdo,
.__bdi,
.__ruby,
.__rt {
display: inline-block;
}
/* #endif */
._video {
position: relative;
display: inline-block;
width: 300px;
height: 225px;
background-color: black;
}
._video::after {
position: absolute;
top: 50%;
left: 50%;
margin: -15px 0 0 -15px;
content: '';
border-color: transparent transparent transparent white;
border-style: solid;
border-width: 15px 0 15px 30px;
}
</style>

View File

@ -0,0 +1,645 @@
<template>
<view>
<slot v-if="!nodes.length" />
<!--#ifdef APP-PLUS-NVUE-->
<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
<!--#endif-->
<!--#ifndef APP-PLUS-NVUE-->
<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
<!--#ifdef H5 || MP-360-->
<div :id="'rtf'+uid"></div>
<!--#endif-->
<!--#ifndef H5 || MP-360-->
<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
<!--#endif-->
</view>
<!--#endif-->
</view>
</template>
<script>
var search;
// #ifndef H5 || APP-PLUS-NVUE || MP-360
import trees from './libs/trees';
var cache = {},
// #ifdef MP-WEIXIN || MP-TOUTIAO
fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
// #endif
Parser = require('./libs/MpHtmlParser.js');
var dom;
// cache key
function hash(str) {
for (var i = str.length, val = 5381; i--;)
val += (val << 5) + str.charCodeAt(i);
return val;
}
// #endif
// #ifdef H5 || APP-PLUS-NVUE || MP-360
var {
windowWidth,
platform
} = uni.getSystemInfoSync(),
cfg = require('./libs/config.js');
// #endif
// #ifdef APP-PLUS-NVUE
var weexDom = weex.requireModule('dom');
// #endif
/**
* Parser 富文本组件
* @tutorial https://github.com/jin-yufeng/Parser
* @property {String} html 富文本数据
* @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
* @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
* @property {Number} compress 压缩等级
* @property {String} domain 图片视频等链接的主域名
* @property {Boolean} lazyLoad 是否开启图片懒加载
* @property {String} loadingImg 图片加载完成前的占位图
* @property {Boolean} selectable 是否开启长按复制
* @property {Object} tagStyle 标签的默认样式
* @property {Boolean} showWithAnimation 是否使用渐显动画
* @property {Boolean} useAnchor 是否使用锚点
* @property {Boolean} useCache 是否缓存解析结果
* @event {Function} parse 解析完成事件
* @event {Function} load dom 加载完成事件
* @event {Function} ready 所有图片加载完毕事件
* @event {Function} error 错误事件
* @event {Function} imgtap 图片点击事件
* @event {Function} linkpress 链接点击事件
* @author JinYufeng
* @version 20201029
* @listens MIT
*/
export default {
name: 'parser',
data() {
return {
// #ifdef H5 || MP-360
uid: this._uid,
// #endif
// #ifdef APP-PLUS-NVUE
height: 1,
// #endif
// #ifndef APP-PLUS-NVUE
showAm: '',
// #endif
nodes: []
}
},
// #ifndef H5 || APP-PLUS-NVUE || MP-360
components: {
trees
},
// #endif
props: {
html: String,
autopause: {
type: Boolean,
default: true
},
autoscroll: Boolean,
autosetTitle: {
type: Boolean,
default: true
},
// #ifndef H5 || APP-PLUS-NVUE || MP-360
compress: Number,
loadingImg: String,
useCache: Boolean,
// #endif
domain: String,
lazyLoad: Boolean,
selectable: Boolean,
tagStyle: Object,
showWithAnimation: Boolean,
useAnchor: Boolean
},
watch: {
html(html) {
this.setContent(html);
}
},
created() {
//
this.imgList = [];
this.imgList.each = function(f) {
for (var i = 0, len = this.length; i < len; i++)
this.setItem(i, f(this[i], i, this));
}
this.imgList.setItem = function(i, src) {
if (i == void 0 || !src) return;
// #ifndef MP-ALIPAY || APP-PLUS
//
if (src.indexOf('http') == 0 && this.includes(src)) {
var newSrc = src.split('://')[0];
for (var j = newSrc.length, c; c = src[j]; j++) {
if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
}
newSrc += src.substr(j);
return this[i] = newSrc;
}
// #endif
this[i] = src;
// data src
if (src.includes('data:image')) {
var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
if (!info) return;
// #ifdef MP-WEIXIN || MP-TOUTIAO
filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
fs && fs.writeFile({
filePath,
data: info[3],
encoding: info[2],
success: () => this[i] = filePath
})
// #endif
// #ifdef APP-PLUS
filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
var bitmap = new plus.nativeObj.Bitmap();
bitmap.loadBase64Data(src, () => {
bitmap.save(filePath, {}, () => {
bitmap.clear()
this[i] = filePath;
})
})
// #endif
}
}
},
mounted() {
// #ifdef H5 || MP-360
this.document = document.getElementById('rtf' + this._uid);
// #endif
// #ifndef H5 || APP-PLUS-NVUE || MP-360
if (dom) this.document = new dom(this);
// #endif
if (search) this.search = args => search(this, args);
// #ifdef APP-PLUS-NVUE
this.document = this.$refs.web;
setTimeout(() => {
// #endif
if (this.html) this.setContent(this.html);
// #ifdef APP-PLUS-NVUE
}, 30)
// #endif
},
beforeDestroy() {
// #ifdef H5 || MP-360
if (this._observer) this._observer.disconnect();
// #endif
this.imgList.each(src => {
// #ifdef APP-PLUS
if (src && src.includes('_doc')) {
plus.io.resolveLocalFileSystemURL(src, entry => {
entry.remove();
});
}
// #endif
// #ifdef MP-WEIXIN || MP-TOUTIAO
if (src && src.includes(uni.env.USER_DATA_PATH))
fs && fs.unlink({
filePath: src
})
// #endif
})
clearInterval(this._timer);
},
methods: {
//
setContent(html, append) {
// #ifdef APP-PLUS-NVUE
if (!html)
return this.height = 1;
if (append)
this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
"';document.getElementById('parser').appendChild(b)");
else {
html =
'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>html,body{width:100%;height:100%;overflow:hidden}body{margin:0}</style><base href="' +
this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}document.body.onclick=function(){e({action:"click"})},' +
(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight})},50);\x3c/script>';
if (platform == 'android') html = html.replace(/%/g, '%25');
this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
}
this.$refs.web.evalJs(
'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=1;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var a,c=document.getElementsByTagName("img"),s=[],i=0==c.length,d=0,l=0,g=0;a=c[l];l++)parseInt(a.style.width||a.getAttribute("width"))>' +
windowWidth + '&&(a.style.height="auto"),a.onload=function(){++d==c.length&&(i=!0)},a.onerror=function(){++d==c.length&&(i=!0),' + (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
'e({action:"error",source:"img",target:this})},a.hasAttribute("ignore")||"A"==a.parentElement.nodeName||(a.i=g++,s.push(a.getAttribute("original-src")||a.src||a.getAttribute("data-src")),a.onclick=function(t){t.stopPropagation(),e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var u,m=document.getElementsByTagName("a"),f=0;u=m[f];f++)u.onclick=function(m){m.stopPropagation();var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var h,y=document.getElementsByTagName("video"),v=0;h=y[v];v++)h.style.maxWidth="100%",h.onerror=function(){e({action:"error",source:"video",target:this})}' +
(this.autopause ? ',h.onplay=function(){for(var e,t=0;e=y[t];t++)e!=this&&e.pause()}' : '') +
';for(var _,p=document.getElementsByTagName("audio"),w=0;_=p[w];w++)_.onerror=function(){e({action:"error",source:"audio",target:this})};' +
(this.autoscroll ? 'for(var T,E=document.getElementsByTagName("table"),B=0;T=E[B];B++){var N=document.createElement("div");N.style.overflow="scroll",T.parentNode.replaceChild(N,T),N.appendChild(T)}' : '') +
'var x=document.getElementById("parser");clearInterval(window.timer),window.timer=setInterval(function(){i&&clearInterval(window.timer),e({action:"ready",ready:i,height:x.scrollHeight})},350)'
)
this.nodes = [1];
// #endif
// #ifdef H5 || MP-360
if (!html) {
if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
return;
}
var div = document.createElement('div');
if (!append) {
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
this.rtf = div;
} else {
if (!this.rtf) this.rtf = div;
else this.rtf.appendChild(div);
}
div.innerHTML = this._handleHtml(html, append);
for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
style.setAttribute('scoped', 'true');
}
//
if (!this._observer && this.lazyLoad && IntersectionObserver) {
this._observer = new IntersectionObserver(changes => {
for (let item, i = 0; item = changes[i++];) {
if (item.isIntersecting) {
item.target.src = item.target.getAttribute('data-src');
item.target.removeAttribute('data-src');
this._observer.unobserve(item.target);
}
}
}, {
rootMargin: '500px 0px 500px 0px'
})
}
var _ts = this;
//
var title = this.rtf.getElementsByTagName('title');
if (title.length && this.autosetTitle)
uni.setNavigationBarTitle({
title: title[0].innerText
})
// domain
var fill = target => {
var src = target.getAttribute('src');
if (this.domain && src) {
if (src[0] == '/') {
if (src[1] == '/')
target.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
else target.src = this.domain + src;
} else if (!src.includes('://') && src.indexOf('data:') != 0) target.src = this.domain + '/' + src;
}
}
//
this.imgList.length = 0;
var imgs = this.rtf.getElementsByTagName('img');
for (let i = 0, j = 0, img; img = imgs[i]; i++) {
if (parseInt(img.style.width || img.getAttribute('width')) > windowWidth)
img.style.height = 'auto';
fill(img);
if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
img.i = j++;
_ts.imgList.push(img.getAttribute('original-src') || img.src || img.getAttribute('data-src'));
img.onclick = function(e) {
e.stopPropagation();
var preview = true;
this.ignore = () => preview = false;
_ts.$emit('imgtap', this);
if (preview) {
uni.previewImage({
current: this.i,
urls: _ts.imgList
});
}
}
}
img.onerror = function() {
if (cfg.errorImg)
_ts.imgList[this.i] = this.src = cfg.errorImg;
_ts.$emit('error', {
source: 'img',
target: this
});
}
if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
img.setAttribute('data-src', img.src);
img.removeAttribute('src');
this._observer.observe(img);
}
}
//
var links = this.rtf.getElementsByTagName('a');
for (var link of links) {
link.onclick = function(e) {
e.stopPropagation();
var jump = true,
href = this.getAttribute('href');
_ts.$emit('linkpress', {
href,
ignore: () => jump = false
});
if (jump && href) {
if (href[0] == '#') {
if (_ts.useAnchor) {
_ts.navigateTo({
id: href.substr(1)
})
}
} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
return true;
else
uni.navigateTo({
url: href
})
}
return false;
}
}
//
var videos = this.rtf.getElementsByTagName('video');
_ts.videoContexts = videos;
for (let video, i = 0; video = videos[i++];) {
fill(video);
video.style.maxWidth = '100%';
video.onerror = function() {
_ts.$emit('error', {
source: 'video',
target: this
});
}
video.onplay = function() {
if (_ts.autopause)
for (let item, i = 0; item = _ts.videoContexts[i++];)
if (item != this) item.pause();
}
}
//
var audios = this.rtf.getElementsByTagName('audio');
for (var audio of audios) {
fill(audio);
audio.onerror = function() {
_ts.$emit('error', {
source: 'audio',
target: this
});
}
}
//
if (this.autoscroll) {
var tables = this.rtf.getElementsByTagName('table');
for (var table of tables) {
let div = document.createElement('div');
div.style.overflow = 'scroll';
table.parentNode.replaceChild(div, table);
div.appendChild(table);
}
}
if (!append) this.document.appendChild(this.rtf);
this.$nextTick(() => {
this.nodes = [1];
this.$emit('load');
});
setTimeout(() => this.showAm = '', 500);
// #endif
// #ifndef APP-PLUS-NVUE
// #ifndef H5 || MP-360
var nodes;
if (!html) return this.nodes = [];
var parser = new Parser(html, this);
//
if (this.useCache) {
var hashVal = hash(html);
if (cache[hashVal])
nodes = cache[hashVal];
else {
nodes = parser.parse();
cache[hashVal] = nodes;
}
} else nodes = parser.parse();
this.$emit('parse', nodes);
if (append) this.nodes = this.nodes.concat(nodes);
else this.nodes = nodes;
if (nodes.length && nodes.title && this.autosetTitle)
uni.setNavigationBarTitle({
title: nodes.title
})
if (this.imgList) this.imgList.length = 0;
this.videoContexts = [];
this.$nextTick(() => {
(function f(cs) {
for (var i = cs.length; i--;) {
if (cs[i].top) {
cs[i].controls = [];
cs[i].init();
f(cs[i].$children);
}
}
})(this.$children)
this.$emit('load');
})
// #endif
var height;
clearInterval(this._timer);
this._timer = setInterval(() => {
// #ifdef H5 || MP-360
this.rect = this.rtf.getBoundingClientRect();
// #endif
// #ifndef H5 || MP-360
uni.createSelectorQuery().in(this)
.select('#_top').boundingClientRect().exec(res => {
if (!res) return;
this.rect = res[0];
// #endif
if (this.rect.height == height) {
this.$emit('ready', this.rect)
clearInterval(this._timer);
}
height = this.rect.height;
// #ifndef H5 || MP-360
});
// #endif
}, 350);
if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
// #endif
},
//
getText(ns = this.nodes) {
var txt = '';
// #ifdef APP-PLUS-NVUE
txt = this._text;
// #endif
// #ifdef H5 || MP-360
txt = this.rtf.innerText;
// #endif
// #ifndef H5 || APP-PLUS-NVUE || MP-360
for (var i = 0, n; n = ns[i++];) {
if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
.replace(/&amp;/g, '&');
else if (n.type == 'br') txt += '\n';
else {
//
var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
'0' && n.name[1] < '7');
if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
if (n.children) txt += this.getText(n.children);
if (block && txt[txt.length - 1] != '\n') txt += '\n';
else if (n.name == 'td' || n.name == 'th') txt += '\t';
}
}
// #endif
return txt;
},
//
in (obj) {
if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
},
navigateTo(obj) {
if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
// #ifdef APP-PLUS-NVUE
if (!obj.id)
weexDom.scrollToElement(this.$refs.web);
else
this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
obj.success && obj.success();
// #endif
// #ifndef APP-PLUS-NVUE
var d = ' ';
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
d = '>>>';
// #endif
var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
else selector.selectViewport().scrollOffset();
selector.exec(res => {
if (!res[0]) return obj.fail && obj.fail('Label not found')
var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
else uni.pageScrollTo({
scrollTop,
duration: 300
})
obj.success && obj.success();
})
// #endif
},
//
getVideoContext(id) {
// #ifndef APP-PLUS-NVUE
if (!id) return this.videoContexts;
else
for (var i = this.videoContexts.length; i--;)
if (this.videoContexts[i].id == id) return this.videoContexts[i];
// #endif
},
// #ifdef H5 || APP-PLUS-NVUE || MP-360
_handleHtml(html, append) {
if (!append) {
// tag-style userAgentStyles
var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
for (var item in cfg.userAgentStyles)
style += `${item}{${cfg.userAgentStyles[item]}}`;
for (item in this.tagStyle)
style += `${item}{${this.tagStyle[item]}}`;
style += '</style>';
html = style + html;
}
// rpx
if (html.includes('rpx'))
html = html.replace(/[0-9.]+\s*rpx/g, $ => (parseFloat($) * windowWidth / 750) + 'px');
return html;
},
// #endif
// #ifdef APP-PLUS-NVUE
_message(e) {
// web-view
var d = e.detail.data[0];
switch (d.action) {
case 'load':
this.$emit('load');
this.height = d.height;
this._text = d.text;
break;
case 'getTitle':
if (this.autosetTitle)
uni.setNavigationBarTitle({
title: d.title
})
break;
case 'getImgList':
this.imgList.length = 0;
for (var i = d.imgList.length; i--;)
this.imgList.setItem(i, d.imgList[i]);
break;
case 'preview':
var preview = true;
d.img.ignore = () => preview = false;
this.$emit('imgtap', d.img);
if (preview)
uni.previewImage({
current: d.img.i,
urls: this.imgList
})
break;
case 'linkpress':
var jump = true,
href = d.href;
this.$emit('linkpress', {
href,
ignore: () => jump = false
})
if (jump && href) {
if (href[0] == '#') {
if (this.useAnchor)
weexDom.scrollToElement(this.$refs.web, {
offset: d.offset
})
} else if (href.includes('://'))
plus.runtime.openWeb(href);
else
uni.navigateTo({
url: href
})
}
break;
case 'error':
if (d.source == 'img' && cfg.errorImg)
this.imgList.setItem(d.target.i, cfg.errorImg);
this.$emit('error', {
source: d.source,
target: d.target
})
break;
case 'ready':
this.height = d.height;
if (d.ready) uni.createSelectorQuery().in(this).select('#_top').boundingClientRect().exec(res => {
this.rect = res[0];
this.$emit('ready', res[0]);
})
break;
case 'click':
this.$emit('click');
this.$emit('tap');
}
},
// #endif
}
}
</script>
<style>
@keyframes _show {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
/* #ifdef MP-WEIXIN */
:host {
display: block;
overflow: auto;
-webkit-overflow-scrolling: touch;
}
/* #endif */
</style>

View File

@ -0,0 +1,676 @@
<template>
<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
<view class="u-datetime-picker">
<view class="u-picker-header" @touchmove.stop.prevent="">
<view class="u-btn-picker u-btn-picker--tips"
:style="{ color: cancelColor }"
hover-class="u-opacity"
:hover-stay-time="150"
@tap="getResult('cancel')"
>{{cancelText}}</view>
<view class="u-picker__title">{{ title }}</view>
<view
class="u-btn-picker u-btn-picker--primary"
:style="{ color: moving ? cancelColor : confirmColor }"
hover-class="u-opacity"
:hover-stay-time="150"
@touchmove.stop=""
@tap.stop="getResult('confirm')"
>
{{confirmText}}
</view>
</view>
<view class="u-picker-body">
<picker-view v-if="mode == 'region'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-if="!reset && params.province">
<view class="u-column-item" v-for="(item, index) in provinces" :key="index">
<view class="u-line-1">{{ item.label }}</view>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.city">
<view class="u-column-item" v-for="(item, index) in citys" :key="index">
<view class="u-line-1">{{ item.label }}</view>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.area">
<view class="u-column-item" v-for="(item, index) in areas" :key="index">
<view class="u-line-1">{{ item.label }}</view>
</view>
</picker-view-column>
</picker-view>
<picker-view v-else-if="mode == 'time'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-if="!reset && params.year">
<view class="u-column-item" v-for="(item, index) in years" :key="index">
{{ item }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.month">
<view class="u-column-item" v-for="(item, index) in months" :key="index">
{{ formatNumber(item) }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.day">
<view class="u-column-item" v-for="(item, index) in days" :key="index">
{{ formatNumber(item) }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.hour">
<view class="u-column-item" v-for="(item, index) in hours" :key="index">
{{ formatNumber(item) }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.minute">
<view class="u-column-item" v-for="(item, index) in minutes" :key="index">
{{ formatNumber(item) }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
<picker-view-column v-if="!reset && params.second">
<view class="u-column-item" v-for="(item, index) in seconds" :key="index">
{{ formatNumber(item) }}
<text class="u-text" v-if="showTimeTag"></text>
</view>
</picker-view-column>
</picker-view>
<picker-view v-else-if="mode == 'selector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-if="!reset">
<view class="u-column-item" v-for="(item, index) in range" :key="index">
<view class="u-line-1">{{ getItemValue(item, 'selector') }}</view>
</view>
</picker-view-column>
</picker-view>
<picker-view v-else-if="mode == 'multiSelector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
<picker-view-column v-if="!reset" v-for="(item, index) in range" :key="index">
<view class="u-column-item" v-for="(item1, index1) in item" :key="index1">
<view class="u-line-1">{{ getItemValue(item1, 'multiSelector') }}</view>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</u-popup>
</template>
<script>
import provinces from '../../libs/util/province.js';
import citys from '../../libs/util/city.js';
import areas from '../../libs/util/area.js';
/**
* picker picker弹出选择器
* @description 此选择器有两种弹出模式一是时间模式可以配置年秒参数 二是地区模式可以配置省区参数
* @tutorial https://www.uviewui.com/components/picker.html
* @property {Object} params 需要显示的参数见官网说明
* @property {String} mode 模式选择region-地区类型time-时间类型默认time
* @property {String Number} start-year 可选的开始年份mode=time时有效默认1950
* @property {String Number} end-year 可选的结束年份mode=time时有效默认2050
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配默认false
* @property {Boolean} show-time-tag 时间模式时是否显示后面的年月日中文提示
* @property {String} cancel-color 取消按钮的颜色默认#606266
* @property {String} confirm-color 确认按钮的颜色默认#2979ff
* @property {String} default-time 默认选中的时间mode=time时有效
* @property {String} confirm-text 确认按钮的文字
* @property {String} cancel-text 取消按钮的文字
* @property {String} default-region 默认选中的地区中文形式mode=region时有效
* @property {String} default-code 默认选中的地区编号形式mode=region时有效
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker默认true
* @property {String Number} z-index 弹出时的z-index值默认1075
* @property {Array} default-selector 数组形式其中每一项表示选择了range对应项中的第几个
* @property {Array} range 自定义选择的数据mode=selector或mode=multiSelector时有效
* @property {String} range-key 当range参数的元素为对象时指定Object中的哪个key的值作为选择器显示内容
* @event {Function} confirm 点击确定按钮返回当前选择的值
* @event {Function} cancel 点击取消按钮返回当前选择的值
* @example <u-picker v-model="show" mode="time"></u-picker>
*/
export default {
name: 'u-picker',
props: {
// picker
params: {
type: Object,
default() {
return {
year: true,
month: true,
day: true,
hour: false,
minute: false,
second: false,
province: true,
city: true,
area: true,
timestamp: true,
};
}
},
// mode=selectormode=multiSelector
range: {
type: Array,
default() {
return [];
}
},
// mode=selectormode=multiSelector
defaultSelector: {
type: Array,
default() {
return [0];
}
},
// range ArrayObject range-key Object key
rangeKey: {
type: String,
default: ''
},
// region-time-selector-multiSelector-
mode: {
type: String,
default: 'time'
},
//
startYear: {
type: [String, Number],
default: 1950
},
//
endYear: {
type: [String, Number],
default: 2050
},
// ""
cancelColor: {
type: String,
default: '#606266'
},
// ""
confirmColor: {
type: String,
default: '#2979ff'
},
// 2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02
defaultTime: {
type: String,
default: ''
},
// ["", "", ""]
defaultRegion: {
type: Array,
default() {
return [];
}
},
//
showTimeTag: {
type: Boolean,
default: true
},
// defaultRegionareaCodeareaCode["13", "1303", "130304"]
areaCode: {
type: Array,
default() {
return [];
}
},
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
//
value: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: [String, Number],
default: 0
},
//
title: {
type: String,
default: ''
},
//
cancelText: {
type: String,
default: '取消'
},
//
confirmText: {
type: String,
default: '确认'
}
},
data() {
return {
years: [],
months: [],
days: [],
hours: [],
minutes: [],
seconds: [],
year: 0,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
reset: false,
startDate: '',
endDate: '',
valueArr: [],
provinces: provinces,
citys: citys[0],
areas: areas[0][0],
province: 0,
city: 0,
area: 0,
moving: false //
};
},
mounted() {
this.init();
},
computed: {
propsChange() {
//
return `${this.mode}-${this.defaultTime}-${this.startYear}-${this.endYear}-${this.defaultRegion}-${this.areaCode}`;
},
regionChange() {
//
return `${this.province}-${this.city}`;
},
yearAndMonth() {
return `${this.year}-${this.month}`;
},
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
watch: {
propsChange() {
this.reset = true;
setTimeout(() => this.init(), 10);
},
// pickerthis.citysthis.areas
regionChange(val) {
this.citys = citys[this.province];
this.areas = areas[this.province][this.city];
},
// watch
// 3031229228
yearAndMonth(val) {
if (this.params.year) this.setDays();
},
// QQ()
value(n) {
if (n) {
this.reset = true;
setTimeout(() => this.init(), 10);
}
}
},
methods: {
//
pickstart() {
// #ifdef MP-WEIXIN
this.moving = true;
// #endif
},
//
pickend() {
// #ifdef MP-WEIXIN
this.moving = false;
// #endif
},
//
getItemValue(item, mode) {
// (2020-05-25)uni-appv-iffalse
// getItemValue
if (this.mode == mode) {
return typeof item == 'object' ? item[this.rangeKey] : item;
}
},
// 100
formatNumber(num) {
return +num < 10 ? '0' + num : String(num);
},
//
generateArray: function(start, end) {
// end-yearend+1
start = Number(start);
end = Number(end);
end = end > start ? end : start;
//
return [...Array(end + 1).keys()].slice(start);
},
getIndex: function(arr, val) {
let index = arr.indexOf(val);
// index-1(index)~(-1)=-(-1)-1=0
return ~index ? index : 0;
},
//
initTimeValue() {
// IE(uni)"-"
let fdate = this.defaultTime.replace(/\-/g, '/');
fdate = fdate && fdate.indexOf('/') == -1 ? `2020/01/01 ${fdate}` : fdate;
let time = null;
if (fdate) time = new Date(fdate);
else time = new Date();
//
this.year = time.getFullYear();
this.month = Number(time.getMonth()) + 1;
this.day = time.getDate();
this.hour = time.getHours();
this.minute = time.getMinutes();
this.second = time.getSeconds();
},
init() {
this.valueArr = [];
this.reset = false;
if (this.mode == 'time') {
this.initTimeValue();
if (this.params.year) {
this.valueArr.push(0);
this.setYears();
}
if (this.params.month) {
this.valueArr.push(0);
this.setMonths();
}
if (this.params.day) {
this.valueArr.push(0);
this.setDays();
}
if (this.params.hour) {
this.valueArr.push(0);
this.setHours();
}
if (this.params.minute) {
this.valueArr.push(0);
this.setMinutes();
}
if (this.params.second) {
this.valueArr.push(0);
this.setSeconds();
}
} else if (this.mode == 'region') {
if (this.params.province) {
this.valueArr.push(0);
this.setProvinces();
}
if (this.params.city) {
this.valueArr.push(0);
this.setCitys();
}
if (this.params.area) {
this.valueArr.push(0);
this.setAreas();
}
} else if (this.mode == 'selector') {
this.valueArr = this.defaultSelector;
} else if (this.mode == 'multiSelector') {
this.valueArr = this.defaultSelector;
this.multiSelectorValue = this.defaultSelector;
}
this.$forceUpdate();
},
// picker
setYears() {
//
this.years = this.generateArray(this.startYear, this.endYear);
// this.valueArrpicker
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.years, this.year));
},
setMonths() {
this.months = this.generateArray(1, 12);
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.months, this.month));
},
setDays() {
let totalDays = new Date(this.year, this.month, 0).getDate();
this.days = this.generateArray(1, totalDays);
let index = 0;
// 使setMonths()this.valueArr.splice(this.valueArr.length - 1, xxx)
// this.monththis.yearwatchthis.setDays()this.valueArr.length
if (this.params.year && this.params.month) index = 2;
else if (this.params.month) index = 1;
else if (this.params.year) index = 1;
else index = 0;
//
// 331229day3129(picker-column1)
if(this.day > this.days.length) this.day = this.days.length;
this.valueArr.splice(index, 1, this.getIndex(this.days, this.day));
},
setHours() {
this.hours = this.generateArray(0, 23);
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour));
},
setMinutes() {
this.minutes = this.generateArray(0, 59);
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute));
},
setSeconds() {
this.seconds = this.generateArray(0, 59);
this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second));
},
setProvinces() {
// province
if (!this.params.province) return;
let tmp = '';
let useCode = false;
// defaultRegionareaCode使areaCode
if (this.areaCode.length) {
tmp = this.areaCode[0];
useCode = true;
} else if (this.defaultRegion.length) tmp = this.defaultRegion[0];
else tmp = 0;
//
provinces.map((v, k) => {
if (useCode ? v.value == tmp : v.label == tmp) {
tmp = k;
}
});
this.province = tmp;
this.provinces = provinces;
//
this.valueArr.splice(0, 1, this.province);
},
setCitys() {
if (!this.params.city) return;
let tmp = '';
let useCode = false;
if (this.areaCode.length) {
tmp = this.areaCode[1];
useCode = true;
} else if (this.defaultRegion.length) tmp = this.defaultRegion[1];
else tmp = 0;
citys[this.province].map((v, k) => {
if (useCode ? v.value == tmp : v.label == tmp) {
tmp = k;
}
});
this.city = tmp;
this.citys = citys[this.province];
this.valueArr.splice(1, 1, this.city);
},
setAreas() {
if (!this.params.area) return;
let tmp = '';
let useCode = false;
if (this.areaCode.length) {
tmp = this.areaCode[2];
useCode = true;
} else if (this.defaultRegion.length) tmp = this.defaultRegion[2];
else tmp = 0;
areas[this.province][this.city].map((v, k) => {
if (useCode ? v.value == tmp : v.label == tmp) {
tmp = k;
}
});
this.area = tmp;
this.areas = areas[this.province][this.city];
this.valueArr.splice(2, 1, this.area);
},
close() {
this.$emit('input', false);
},
// picker
change(e) {
this.valueArr = e.detail.value;
let i = 0;
if (this.mode == 'time') {
// 使i++this.valueArrthis.params
// ifi1
if (this.params.year) this.year = this.years[this.valueArr[i++]];
if (this.params.month) this.month = this.months[this.valueArr[i++]];
if (this.params.day) this.day = this.days[this.valueArr[i++]];
if (this.params.hour) this.hour = this.hours[this.valueArr[i++]];
if (this.params.minute) this.minute = this.minutes[this.valueArr[i++]];
if (this.params.second) this.second = this.seconds[this.valueArr[i++]];
} else if (this.mode == 'region') {
if (this.params.province) this.province = this.valueArr[i++];
if (this.params.city) this.city = this.valueArr[i++];
if (this.params.area) this.area = this.valueArr[i++];
} else if (this.mode == 'multiSelector') {
let index = null;
//
this.defaultSelector.map((val, idx) => {
if (val != e.detail.value[idx]) index = idx;
});
//
if (index != null) {
this.$emit('columnchange', {
column: index,
index: e.detail.value[index]
});
}
}
},
//
getResult(event = null) {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
let result = {};
// this.paramstrue
if (this.mode == 'time') {
if (this.params.year) result.year = this.formatNumber(this.year || 0);
if (this.params.month) result.month = this.formatNumber(this.month || 0);
if (this.params.day) result.day = this.formatNumber(this.day || 0);
if (this.params.hour) result.hour = this.formatNumber(this.hour || 0);
if (this.params.minute) result.minute = this.formatNumber(this.minute || 0);
if (this.params.second) result.second = this.formatNumber(this.second || 0);
if (this.params.timestamp) result.timestamp = this.getTimestamp();
} else if (this.mode == 'region') {
if (this.params.province) result.province = provinces[this.province];
if (this.params.city) result.city = citys[this.province][this.city];
if (this.params.area) result.area = areas[this.province][this.city][this.area];
} else if (this.mode == 'selector') {
result = this.valueArr;
} else if (this.mode == 'multiSelector') {
result = this.valueArr;
}
if (event) this.$emit(event, result);
this.close();
},
//
getTimestamp() {
// yyyy-mm-ddiOS使"/"
let time = this.year + '/' + this.month + '/' + this.day + ' ' + this.hour + ':' + this.minute + ':' + this.second;
return new Date(time).getTime() / 1000;
}
}
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';
.u-datetime-picker {
position: relative;
z-index: 999;
}
.u-picker-view {
height: 100%;
box-sizing: border-box;
}
.u-picker-header {
width: 100%;
height: 90rpx;
padding: 0 40rpx;
@include vue-flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
font-size: 30rpx;
background: #fff;
position: relative;
}
.u-picker-header::after {
content: '';
position: absolute;
border-bottom: 1rpx solid #eaeef1;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
bottom: 0;
right: 0;
left: 0;
}
.u-picker__title {
color: $u-content-color;
}
.u-picker-body {
width: 100%;
height: 500rpx;
overflow: hidden;
background-color: #fff;
}
.u-column-item {
@include vue-flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: $u-main-color;
padding: 0 8rpx;
}
.u-text {
font-size: 24rpx;
padding-left: 8rpx;
}
.u-btn-picker {
padding: 16rpx;
box-sizing: border-box;
text-align: center;
text-decoration: none;
}
.u-opacity {
opacity: 0.5;
}
.u-btn-picker--primary {
color: $u-type-primary;
}
.u-btn-picker--tips {
color: $u-tips-color;
}
</style>

View File

@ -0,0 +1,456 @@
<template>
<view v-if="visibleSync" :style="[customStyle, {
zIndex: uZindex - 1
}]" class="u-drawer" hover-stop-propagation>
<u-mask :duration="duration" :custom-style="maskCustomStyle" :maskClickAble="maskCloseAble" :z-index="uZindex - 2" :show="showDrawer && mask" @click="maskClick"></u-mask>
<view
class="u-drawer-content"
@tap="modeCenterClose(mode)"
:class="[
safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
'u-drawer-' + mode,
showDrawer ? 'u-drawer-content-visible' : '',
zoom && mode == 'center' ? 'u-animation-zoom' : ''
]"
@touchmove.stop.prevent
@tap.stop.prevent
:style="[style]"
>
<view class="u-mode-center-box" @tap.stop.prevent @touchmove.stop.prevent v-if="mode == 'center'" :style="[centerStyle]">
<u-icon
@click="close"
v-if="closeable"
class="u-close"
:class="['u-close--' + closeIconPos]"
:name="closeIcon"
:color="closeIconColor"
:size="closeIconSize"
></u-icon>
<scroll-view class="u-drawer__scroll-view" scroll-y="true">
<slot />
</scroll-view>
</view>
<scroll-view class="u-drawer__scroll-view" scroll-y="true" v-else>
<slot />
</scroll-view>
<view @tap="close" class="u-close" :class="['u-close--' + closeIconPos]">
<u-icon
v-if="mode != 'center' && closeable"
:name="closeIcon"
:color="closeIconColor"
:size="closeIconSize"
></u-icon>
</view>
</view>
</view>
</template>
<script>
/**
* popup 弹窗
* @description 弹出层容器用于展示弹窗信息提示等内容支持上右和中部弹出组件只提供容器内部内容由用户自定义
* @tutorial https://www.uviewui.com/components/popup.html
* @property {String} mode 弹出方向默认left
* @property {Boolean} mask 是否显示遮罩默认true
* @property {Stringr | Number} length mode=left | 见官网说明默认auto
* @property {Boolean} zoom 是否开启缩放动画只在mode为center时有效默认true
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配默认false
* @property {Boolean} mask-close-able 点击遮罩是否可以关闭弹出层默认true
* @property {Object} custom-style 用户自定义样式
* @property {Stringr | Number} negative-top 中部弹出时往上偏移的值
* @property {Numberr | String} border-radius 弹窗圆角值默认0
* @property {Numberr | String} z-index 弹出内容的z-index值默认1075
* @property {Boolean} closeable 是否显示关闭图标默认false
* @property {String} close-icon 关闭图标的名称只能uView的内置图标
* @property {String} close-icon-pos 自定义关闭图标位置默认top-right
* @property {String} close-icon-color 关闭图标的颜色默认#909399
* @property {Number | String} close-icon-size 关闭图标的大小单位rpx默认30
* @event {Function} open 弹出层打开
* @event {Function} close 弹出层收起
* @example <u-popup v-model="show"><view>出淤泥而不染濯清涟而不妖</view></u-popup>
*/
export default {
name: 'u-popup',
props: {
/**
* 显示状态
*/
show: {
type: Boolean,
default: false
},
/**
* 弹出方向left|right|top|bottom|center
*/
mode: {
type: String,
default: 'left'
},
/**
* 是否显示遮罩
*/
mask: {
type: Boolean,
default: true
},
// (mode=left|right)(mode=top|bottom)rpx"auto"
// "50%"
length: {
type: [Number, String],
default: 'auto'
},
// mode=center
zoom: {
type: Boolean,
default: true
},
// iPhoneX
safeAreaInsetBottom: {
type: Boolean,
default: false
},
//
maskCloseAble: {
type: Boolean,
default: true
},
//
customStyle: {
type: Object,
default() {
return {};
}
},
value: {
type: Boolean,
default: false
},
// 使Pickerkeyboard
// v-modelprops
popup: {
type: Boolean,
default: true
},
// rpx
borderRadius: {
type: [Number, String],
default: 0
},
zIndex: {
type: [Number, String],
default: ''
},
//
closeable: {
type: Boolean,
default: false
},
// uView
closeIcon: {
type: String,
default: 'close'
},
// top-lefttop-rightbottom-leftbottom-right
closeIconPos: {
type: String,
default: 'top-right'
},
//
closeIconColor: {
type: String,
default: '#909399'
},
// rpx
closeIconSize: {
type: [String, Number],
default: '30'
},
// rpx"auto"
// "50%"length
width: {
type: String,
default: ''
},
// rpx"auto"
// "50%"length
height: {
type: String,
default: ''
},
// margin-topmode=center
negativeTop: {
type: [String, Number],
default: 0
},
//
maskCustomStyle: {
type: Object,
default() {
return {}
}
},
// ms
duration: {
type: [String, Number],
default: 250
}
},
data() {
return {
visibleSync: false,
showDrawer: false,
timer: null,
closeFromInner: false, // value
};
},
computed: {
// mode(mode = left|right)(mode = top|bottom)
style() {
let style = {};
// translate
if (this.mode == 'left' || this.mode == 'right') {
style = {
width: this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length),
height: '100%',
transform: `translate3D(${this.mode == 'left' ? '-100%' : '100%'},0px,0px)`
};
} else if (this.mode == 'top' || this.mode == 'bottom') {
style = {
width: '100%',
height: this.height ? this.getUnitValue(this.height) : this.getUnitValue(this.length),
transform: `translate3D(0px,${this.mode == 'top' ? '-100%' : '100%'},0px)`
};
}
style.zIndex = this.uZindex;
// borderRadius
if (this.borderRadius) {
switch (this.mode) {
case 'left':
style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
break;
case 'top':
style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
break;
case 'right':
style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
break;
case 'bottom':
style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
break;
default:
}
//
style.overflow = 'hidden';
}
if(this.duration) style.transition = `all ${this.duration / 1000}s linear`;
return style;
},
//
centerStyle() {
let style = {};
style.width = this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length);
// auto
style.height = this.height ? this.getUnitValue(this.height) : 'auto';
style.zIndex = this.uZindex;
style.marginTop = `-${this.$u.addUnit(this.negativeTop)}`;
if (this.borderRadius) {
style.borderRadius = `${this.borderRadius}rpx`;
//
style.overflow = 'hidden';
}
return style;
},
// z-index
uZindex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
},
watch: {
value(val) {
if (val) {
this.open();
} else if(!this.closeFromInner) {
this.close();
}
this.closeFromInner = false;
}
},
mounted() {
// valuetruepopup
this.value && this.open();
},
methods: {
// rpx
getUnitValue(val) {
if(/(%|px|rpx|auto)$/.test(val)) return val;
else return val + 'rpx'
},
//
maskClick() {
this.close();
},
close() {
// valuewatchvalueclose
// @close
this.closeFromInner = true;
this.change('showDrawer', 'visibleSync', false);
},
// .u-drawer-content
// mode=center
modeCenterClose(mode) {
if (mode != 'center' || !this.maskCloseAble) return;
this.close();
},
open() {
this.change('visibleSync', 'showDrawer', true);
},
//
//
change(param1, param2, status) {
// this.popupfalsepickeractionsheetpopup
if (this.popup == true) {
this.$emit('input', status);
}
this[param1] = status;
if(status) {
// #ifdef H5 || MP
this.timer = setTimeout(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
}, 50);
// #endif
// #ifndef H5 || MP
this.$nextTick(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
})
// #endif
} else {
this.timer = setTimeout(() => {
this[param2] = status;
this.$emit(status ? 'open' : 'close');
}, this.duration);
}
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-drawer {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.u-drawer-content {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: absolute;
z-index: 1003;
transition: all 0.25s linear;
}
.u-drawer__scroll-view {
width: 100%;
height: 100%;
}
.u-drawer-left {
top: 0;
bottom: 0;
left: 0;
background-color: #ffffff;
}
.u-drawer-right {
right: 0;
top: 0;
bottom: 0;
background-color: #ffffff;
}
.u-drawer-top {
top: 0;
left: 0;
right: 0;
background-color: #ffffff;
}
.u-drawer-bottom {
bottom: 0;
left: 0;
right: 0;
background-color: #ffffff;
}
.u-drawer-center {
@include vue-flex;
flex-direction: column;
bottom: 0;
left: 0;
right: 0;
top: 0;
justify-content: center;
align-items: center;
opacity: 0;
z-index: 99999;
}
.u-mode-center-box {
min-width: 100rpx;
min-height: 100rpx;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
background-color: #ffffff;
}
.u-drawer-content-visible.u-drawer-center {
transform: scale(1);
opacity: 1;
}
.u-animation-zoom {
transform: scale(1.15);
}
.u-drawer-content-visible {
transform: translate3D(0px, 0px, 0px) !important;
}
.u-close {
position: absolute;
z-index: 3;
}
.u-close--top-left {
top: 30rpx;
left: 30rpx;
}
.u-close--top-right {
top: 30rpx;
right: 30rpx;
}
.u-close--bottom-left {
bottom: 30rpx;
left: 30rpx;
}
.u-close--bottom-right {
right: 30rpx;
bottom: 30rpx;
}
</style>

View File

@ -0,0 +1,128 @@
<template>
<view class="u-radio-group u-clearfix">
<slot></slot>
</view>
</template>
<script>
import Emitter from '../../libs/util/emitter.js';
/**
* radioRroup 单选框父组件
* @description 单选框用于有一个选择用户只能选择其中一个的场景搭配u-radio使用
* @tutorial https://www.uviewui.com/components/radio.html
* @property {Boolean} disabled 是否禁用所有radio默认false
* @property {String Number} size 组件整体的大小单位rpx默认40
* @property {String} active-color 选中时的颜色应用到所有子Radio组件默认#2979ff
* @property {String Number} icon-size 图标大小单位rpx默认20
* @property {String} shape 外观形状shape-方形circle-圆形(默认circle)
* @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
* @property {String} width 宽度需带单位
* @property {Boolean} wrap 是否每个radio都换行默认false
* @event {Function} change 任一个radio状态发生变化时触发
* @example <u-radio-group v-model="value"></u-radio-group>
*/
export default {
name: "u-radio-group",
mixins: [Emitter],
props: {
//
disabled: {
type: Boolean,
default: false
},
// radioradionameradio
value: {
type: [String, Number],
default: ''
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
size: {
type: [String, Number],
default: 34
},
//
labelDisabled: {
type: Boolean,
default: false
},
// squarecircle
shape: {
type: String,
default: 'circle'
},
// rpx
iconSize: {
type: [String, Number],
default: 20
},
// checkboxu-checkbox-group
width: {
type: [String, Number],
default: 'auto'
},
// checkbox
wrap: {
type: Boolean,
default: false
}
},
created() {
// childrendata
this.children = [];
},
watch: {
//
parentData() {
if(this.children.length) {
this.children.map(child => {
// (u-radio)updateParentData()
typeof(child.updateParentData) == 'function' && child.updateParentData();
})
}
},
},
computed: {
// computedu-radio
// parentDatawatch(u-radio-group)
//
parentData() {
return [this.value, this.disabled, this.activeColor, this.size, this.labelDisabled, this.shape, this.iconSize, this.width, this.wrap];
}
},
methods: {
// radioradiovalue(propsvalue)
setValue(val) {
// val(parentValueval)
// u-radio
this.children.map(child => {
if(child.parentData.value != val) child.parentData.value = '';
})
// emitv-model
this.$emit('input', val);
this.$emit('change', val);
// this.$emit('input')
//
setTimeout(() => {
// u-form-item
this.dispatch('u-form-item', 'on-form-change', val);
}, 60)
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-radio-group {
/* #ifndef MP || APP-NVUE */
display: inline-flex;
flex-wrap: wrap;
/* #endif */
}
</style>

View File

@ -0,0 +1,271 @@
<template>
<view class="u-radio" :style="[radioStyle]">
<view class="u-radio__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
<u-icon
class="u-radio__icon-wrap__icon"
name="checkbox-mark"
:size="elIconSize"
:color="iconColor"/>
</view>
<view class="u-radio__label" @tap="onClickLabel" :style="{
fontSize: $u.addUnit(labelSize)
}">
<slot />
</view>
</view>
</template>
<script>
/**
* radio 单选框
* @description 单选框用于有一个选择用户只能选择其中一个的场景搭配u-radio-group使用
* @tutorial https://www.uviewui.com/components/radio.html
* @property {String Number} icon-size 图标大小单位rpx默认24
* @property {String Number} label-size label字体大小单位rpx默认28
* @property {String Number} name radio组件的标示符
* @property {String} shape 形状见上方说明默认circle
* @property {Boolean} disabled 是否禁用默认false
* @property {Boolean} label-disabled 点击文本是否可以操作radio默认true
* @property {String} active-color 选中时的颜色如设置parent的active-color将失效
* @event {Function} change 某个radio状态发生变化时触发(选中状态)
* @example <u-radio :label-disabled="false">门掩黄昏无计留春住</u-radio>
*/
export default {
name: "u-radio",
props: {
// radio
name: {
type: [String, Number],
default: ''
},
// squarecircle
shape: {
type: String,
default: ''
},
//
disabled: {
type: [String, Boolean],
default: ''
},
//
labelDisabled: {
type: [String, Boolean],
default: ''
},
// parentactiveColor
activeColor: {
type: String,
default: ''
},
// rpx
iconSize: {
type: [String, Number],
default: ''
},
// labelrpx
labelSize: {
type: [String, Number],
default: ''
},
},
data() {
return {
// computed使this.parent.shape
// 使
parentData: {
iconSize: null,
labelDisabled: null,
disabled: null,
shape: null,
activeColor: null,
size: null,
width: null,
height: null,
value: null,
wrap: null
}
};
},
created() {
this.parent = false;
// provide/inject使created
this.updateParentData();
this.parent.children.push(this);
},
computed: {
// u-raios-group
elDisabled() {
return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
},
// label
elLabelDisabled() {
return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled : false;
},
// size34rpx
elSize() {
return this.size ? this.size : (this.parentData.size ? this.parentData.size : 34);
},
// 20
elIconSize() {
return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 20);
},
//
elActiveColor() {
return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : 'primary');
},
//
elShape() {
return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
},
// radioradionameparentvalue
iconStyle() {
let style = {};
if (this.elActiveColor && this.parentData.value == this.name && !this.elDisabled) {
style.borderColor = this.elActiveColor;
style.backgroundColor = this.elActiveColor;
}
style.width = this.$u.addUnit(this.elSize);
style.height = this.$u.addUnit(this.elSize);
return style;
},
iconColor() {
return this.name == this.parentData.value ? '#ffffff' : 'transparent';
},
iconClass() {
let classes = [];
classes.push('u-radio__icon-wrap--' + this.elShape);
if (this.name == this.parentData.value) classes.push('u-radio__icon-wrap--checked');
if (this.elDisabled) classes.push('u-radio__icon-wrap--disabled');
if (this.name == this.parentData.value && this.elDisabled) classes.push(
'u-radio__icon-wrap--disabled--checked');
// ","
return classes.join(' ');
},
radioStyle() {
let style = {};
if (this.parentData.width) {
style.width = this.$u.addUnit(this.parentData.width);
// #ifdef MP
// 使float
style.float = 'left';
// #endif
// #ifndef MP
// H5APP使flex
style.flex = `0 0 ${this.$u.addUnit(this.parentData.width)}`;
// #endif
}
if (this.parentData.wrap) {
style.width = '100%';
// #ifndef MP
// H5APP使flex100%
style.flex = '0 0 100%';
// #endif
}
return style;
}
},
methods: {
updateParentData() {
this.getParentData('u-radio-group');
},
onClickLabel() {
if (!this.elLabelDisabled && !this.elDisabled) {
this.setRadioCheckedStatus();
}
},
toggle() {
if (!this.elDisabled) {
this.setRadioCheckedStatus();
}
},
emitEvent() {
// u-radionamev-model()
if(this.parentData.value != this.name) this.$emit('change', this.name);
},
//
// parentData.valuenameu-radio
// u-radioparentData.value(computed)
setRadioCheckedStatus() {
this.emitEvent();
if(this.parent) {
this.parent.setValue(this.name);
this.parentData.value = this.name;
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-radio {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
overflow: hidden;
user-select: none;
line-height: 1.8;
&__icon-wrap {
color: $u-content-color;
@include vue-flex;
flex: none;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: 42rpx;
height: 42rpx;
color: transparent;
text-align: center;
transition-property: color, border-color, background-color;
font-size: 20px;
border: 1px solid #c8c9cc;
transition-duration: 0.2s;
/* #ifdef MP-TOUTIAO */
// 0
&__icon {
line-height: 0;
}
/* #endif */
&--circle {
border-radius: 100%;
}
&--square {
border-radius: 3px;
}
&--checked {
color: #fff;
background-color: #2979ff;
border-color: #2979ff;
}
&--disabled {
background-color: #ebedf0;
border-color: #c8c9cc;
}
&--disabled--checked {
color: #c8c9cc !important;
}
}
&__label {
word-wrap: break-word;
margin-left: 10rpx;
margin-right: 24rpx;
color: $u-content-color;
font-size: 30rpx;
&--disabled {
color: #c8c9cc;
}
}
}
</style>

View File

@ -0,0 +1,275 @@
<template>
<view class="u-rate" :id="elId" @touchmove.stop.prevent="touchMove">
<view class="u-star-wrap" v-for="(item, index) in count" :key="index" :class="[elClass]">
<u-icon
:name="activeIndex > index ? elActiveIcon : inactiveIcon"
@click="click(index + 1, $event)"
:color="activeIndex > index ? elActiveColor : inactiveColor"
:custom-style="{
fontSize: size + 'rpx',
padding: `0 ${gutter / 2 + 'rpx'}`
}"
:custom-prefix="customPrefix"
:show-decimal-icon="showDecimalIcon(index)"
:percent="decimal"
:inactive-color="inactiveColor"
></u-icon>
</view>
</view>
</template>
<script>/**
* rate 评分
* @description 该组件一般用于满意度调查星型评分的场景
* @tutorial https://www.uviewui.com/components/rate.html
* @property {String Number} count 最多可选的星星数量默认5
* @property {String Number} current 默认选中的星星数量默认0
* @property {Boolean} disabled 是否禁止用户操作默认false
* @property {String Number} size 星星的大小单位rpx默认32
* @property {String} inactive-color 未选中星星的颜色默认#b2b2b2
* @property {String} active-color 选中的星星颜色默认#FA3534
* @property {String} active-icon 选中时的图标名只能为uView的内置图标默认star-fill
* @property {String} inactive-icon 未选中时的图标名只能为uView的内置图标默认star
* @property {String} gutter 星星之间的距离默认10
* @property {String Number} min-count 最少选中星星的个数默认0
* @property {Boolean} allow-half 是否允许半星选择默认false
* @event {Function} change 选中的星星发生变化时触发
* @example <u-rate :count="count" :current="2"></u-rate>
*/
export default {
name: 'u-rate',
props: {
// v-model
// 1.4.5
value: {
type: [Number, String],
default: -1
},
//
count: {
type: [Number, String],
default: 5
},
// ()
// 1.4.5value使
current: {
type: [Number, String],
default: 0
},
//
disabled: {
type: Boolean,
default: false
},
// rpx
size: {
type: [Number, String],
default: 32
},
//
inactiveColor: {
type: String,
default: '#b2b2b2'
},
//
activeColor: {
type: String,
default: '#FA3534'
},
// rpx
gutter: {
type: [Number, String],
default: 10
},
//
minCount: {
type: [Number, String],
default: 0
},
// ()
allowHalf: {
type: Boolean,
default: false
},
// ()
activeIcon: {
type: String,
default: 'star-fill'
},
// ()
inactiveIcon: {
type: String,
default: 'star'
},
// 便
customPrefix: {
type: String,
default: 'uicon'
},
colors: {
type: Array,
default() {
return []
}
},
icons: {
type: Array,
default() {
return []
}
}
},
data() {
return {
// id
elId: this.$u.guid(),
elClass: this.$u.guid(),
starBoxLeft: 0, //
// indexvalue使value(1.4.5)
activeIndex: this.value != -1 ? this.value : this.current,
starWidth: 0, //
starWidthArr: [] //
}
},
watch: {
current(val) {
this.activeIndex = val
},
value(val) {
this.activeIndex = val
}
},
computed: {
decimal() {
if (this.disabled) {
return this.activeIndex * 100 % 100
} else if (this.allowHalf) {
return 50
}
},
elActiveIcon() {
const len = this.icons.length
// elActiveColor
// icons34
//
if (len && len <= this.count) {
const step = Math.round(this.activeIndex / Math.round(this.count / len))
if (step < 1) return this.icons[0]
if (step > len) return this.icons[len - 1]
return this.icons[step - 1]
}
return this.activeIcon
},
elActiveColor() {
const len = this.colors.length
// colors(5colors32
// 45)
if (len && len <= this.count) {
const step = Math.round(this.activeIndex / Math.round(this.count / len))
if (step < 1) return this.colors[0]
if (step > len) return this.colors[len - 1]
return this.colors[step - 1]
}
return this.activeColor
}
},
methods: {
//
getElRectById() {
// uView
this.$uGetRect('#' + this.elId).then(res => {
this.starBoxLeft = res.left
})
},
//
getElRectByClass() {
// uView
this.$uGetRect('.' + this.elClass).then(res => {
this.starWidth = res.width
//
for (let i = 0; i < this.count; i++) {
this.starWidthArr[i] = (i + 1) * this.starWidth
}
})
},
//
touchMove(e) {
if (this.disabled) {
return
}
if (!e.changedTouches[0]) {
return
}
const movePageX = e.changedTouches[0].pageX
//
const distance = movePageX - this.starBoxLeft
// 0
if (distance <= 0) {
this.activeIndex = 0
}
//
let index = Math.ceil(distance / this.starWidth)
this.activeIndex = index > this.count ? this.count : index
//
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
click(index, e) {
if (this.disabled) {
return
}
//
if (this.allowHalf) {
}
// 0
if (index == 1) {
if (this.activeIndex == 1) {
this.activeIndex = 0
} else {
this.activeIndex = 1
}
} else {
this.activeIndex = index
}
//
if (this.activeIndex < this.minCount) this.activeIndex = this.minCount
this.emitEvent()
},
//
emitEvent() {
// change
this.$emit('change', this.activeIndex)
// value
if (this.value != -1) {
this.$emit('input', this.activeIndex)
}
},
showDecimalIcon(index) {
return this.disabled && parseInt(this.activeIndex) === index
}
},
mounted() {
this.getElRectById()
this.getElRectByClass()
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-rate {
display: -webkit-inline-flex;
display: inline-flex;
align-items: center;
margin: 0;
padding: 0;
}
.u-icon {
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,179 @@
<template>
<view class="">
<view class="u-content" :class="[elId]" :style="{
height: isLongContent && !showMore ? showHeight + 'rpx' : 'auto',
textIndent: textIndent
}">
<slot></slot>
</view>
<view @tap="toggleReadMore" v-if="isLongContent" class="u-content__showmore-wrap"
:class="{ 'u-content__show-more': showMore }"
:style="[innerShadowStyle]">
<text class="u-content__showmore-wrap__readmore-btn" :style="{
fontSize: fontSize + 'rpx',
color: color
}">
{{ showMore ? openText : closeText }}
</text>
<view class="u-content__showmore-wrap__readmore-btn__icon u-flex">
<u-icon :color="color" :size="fontSize" :name="showMore ? 'arrow-up' : 'arrow-down'"></u-icon>
</view>
</view>
</view>
</template>
<script>
/**
* readMore 阅读更多
* @description 该组件一般用于内容较长预先收起一部分点击展开全部内容的场景
* @tutorial https://www.uviewui.com/components/readMore.html
* @property {String Number} show-height 内容超出此高度才会显示展开全文按钮单位rpx默认400
* @property {Boolean} toggle 展开后是否显示收起按钮默认false
* @property {String} close-text 关闭时的提示文字默认展开阅读全文
* @property {String Number} font-size 提示文字的大小单位rpx默认28
* @property {String} text-indent 段落首行缩进的字符个数默认2em
* @property {String} open-text 展开时的提示文字默认收起
* @property {String} color 提示文字的颜色默认#2979ff
* @example <u-read-more><rich-text :nodes="content"></rich-text></u-read-more>
*/
export default {
name: "u-read-more",
props: {
// rpx
showHeight: {
type: [Number, String],
default: 400
},
// ""
toggle: {
type: Boolean,
default: false
},
//
closeText: {
type: String,
default: '展开阅读全文'
},
//
openText: {
type: String,
default: '收起'
},
//
color: {
type: String,
default: '#2979ff'
},
//
fontSize: {
type: [String, Number],
default: 28
},
//
shadowStyle: {
type: Object,
default () {
return {
backgroundImage: "linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)",
paddingTop: "300rpx",
marginTop: "-300rpx"
}
}
},
//
textIndent: {
type: String,
default: '2em'
},
// openclose
index: {
type: [Number, String],
default: ''
}
},
watch: {
paramsChange(val) {
this.init();
}
},
computed: {
paramsChange() {
return `${this.toggle}-${this.showHeight}`;
},
//
innerShadowStyle() {
if (this.showMore) return {};
else return this.shadowStyle
}
},
data() {
return {
isLongContent: false, //
showMore: false, // true-false-
elId: this.$u.guid(), // class
};
},
mounted() {
this.$nextTick(() => {
this.init();
})
},
methods: {
init() {
this.$uGetRect('.' + this.elId).then(res => {
//
if (res.height > uni.upx2px(this.showHeight)) {
this.isLongContent = true;
this.showMore = false;
}
})
},
//
toggleReadMore() {
this.showMore = !this.showMore;
// togglefalse""
if (this.toggle == false) this.isLongContent = false;
//
this.$emit(this.showMore ? 'open' : 'close', this.index);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-content {
font-size: 30rpx;
color: $u-content-color;
line-height: 1.8;
text-align: left;
overflow: hidden;
&__show-more {
padding-top: 0;
background: none;
margin-top: 20rpx;
}
&__showmore-wrap {
position: relative;
width: 100%;
padding-bottom: 26rpx;
@include vue-flex;
align-items: center;
justify-content: center;
&__readmore-btn {
@include vue-flex;
align-items: center;
justify-content: center;
line-height: 1;
&__icon {
margin-left: 14rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,269 @@
<template>
<view
v-if="show"
class="u-notice-bar"
:style="{
background: computeBgColor,
padding: padding
}"
:class="[
type ? `u-type-${type}-light-bg` : ''
]"
>
<view class="u-direction-row">
<view class="u-icon-wrap">
<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
</view>
<view class="u-notice-box" id="u-notice-box">
<view
class="u-notice-content"
id="u-notice-content"
:style="{
animationDuration: animationDuration,
animationPlayState: animationPlayState,
}"
>
<text class="u-notice-text" @tap="click" :style="[textStyle]"
:class="['u-type-' + type]">{{showText}}</text>
</view>
</view>
<view class="u-icon-wrap">
<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
//
list: {
type: Array,
default() {
return [];
}
},
// success|error|primary|info|warning|none
// none(contentColor)
type: {
type: String,
default: 'warning'
},
//
volumeIcon: {
type: Boolean,
default: true
},
//
moreIcon: {
type: Boolean,
default: false
},
//
closeIcon: {
type: Boolean,
default: false
},
//
autoplay: {
type: Boolean,
default: true
},
// 使
color: {
type: String,
default: ''
},
//
bgColor: {
type: String,
default: ''
},
//
show: {
type: Boolean,
default: true
},
// rpx
fontSize: {
type: [Number, String],
default: 26
},
//
volumeSize: {
type: [Number, String],
default: 34
},
// rpx
speed: {
type: [Number, String],
default: 160
},
// play-paused-
playState: {
type: String,
default: 'play'
},
//
padding: {
type: [Number, String],
default: '18rpx 24rpx'
}
},
data() {
return {
textWidth: 0, //
boxWidth: 0, //
animationDuration: '10s', //
animationPlayState: 'paused', //
showText: '' //
};
},
watch: {
list: {
immediate: true,
handler(val) {
this.showText = val.join('');
this.$nextTick(() => {
this.initSize();
});
}
},
playState(val) {
if(val == 'play') this.animationPlayState = 'running';
else this.animationPlayState = 'paused';
},
speed(val) {
this.initSize();
}
},
computed: {
// uview
computeColor() {
if (this.color) return this.color;
// 使content-color
else if(this.type == 'none') return '#606266';
else return this.type;
},
//
textStyle() {
let style = {};
if (this.color) style.color = this.color;
else if(this.type == 'none') style.color = '#606266';
style.fontSize = this.fontSize + 'rpx';
return style;
},
//
computeBgColor() {
if (this.bgColor) return this.bgColor;
else if(this.type == 'none') return 'transparent';
}
},
mounted() {
this.$nextTick(() => {
this.initSize();
});
},
methods: {
initSize() {
let query = [],
boxWidth = 0,
textWidth = 0;
let textQuery = new Promise((resolve, reject) => {
uni.createSelectorQuery()
.in(this)
.select(`#u-notice-content`)
.boundingClientRect()
.exec(ret => {
this.textWidth = ret[0].width;
resolve();
});
});
query.push(textQuery);
Promise.all(query).then(() => {
// t=s/v(=/)#u-notice-box.u-notice-contentpadding-left: 100%
// #u-notice-box
this.animationDuration = `${this.textWidth / uni.upx2px(this.speed)}s`;
// APP(HX2.4.6IOS13)
this.animationPlayState = 'paused';
setTimeout(() => {
if(this.playState == 'play' && this.autoplay) this.animationPlayState = 'running';
}, 10);
});
},
//
click(index) {
this.$emit('click');
},
//
close() {
this.$emit('close');
},
//
getMore() {
this.$emit('getMore');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-notice-bar {
padding: 18rpx 24rpx;
overflow: hidden;
}
.u-direction-row {
@include vue-flex;
align-items: center;
justify-content: space-between;
}
.u-left-icon {
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
align-items: center;
}
.u-notice-box {
flex: 1;
@include vue-flex;
overflow: hidden;
margin-left: 12rpx;
}
.u-right-icon {
margin-left: 12rpx;
display: inline-flex;
align-items: center;
}
.u-notice-content {
animation: u-loop-animation 10s linear infinite both;
text-align: right;
//
padding-left: 100%;
@include vue-flex;
flex-wrap: nowrap;
}
.u-notice-text {
font-size: 26rpx;
word-break: keep-all;
white-space: nowrap
}
@keyframes u-loop-animation {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-100%, 0, 0);
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<view class="u-row" :style="{
alignItems: uAlignItem,
justifyContent: uJustify
}"
@tap="click"
>
<slot />
</view>
</template>
<script>
/**
* row 行布局
* @description 通过基础的 12 分栏迅速简便地创建布局
* @tutorial https://www.uviewui.com/components/layout.html#row-props
* @property {String Number} gutter 栅格间隔左右各为此值的一半单位rpx默认0
* @property {String} justify 水平排列方式(微信小程序暂不支持)默认start(或flex-start)
* @property {String} align 垂直排列方式默认center
* @example <u-row gutter="16"></u-row>
*/
export default {
name: "u-row",
props: {
// col
gutter: {
type: [String, Number],
default: 20
},
// `start`(`flex-start`)`end`(`flex-end`)`center``around`(`space-around`)`between`(`space-between`)
justify: {
type: String,
default: 'start'
},
// topcenterbottom
align: {
type: String,
default: 'center'
},
//
stop: {
type: Boolean,
default: true
}
},
computed: {
uJustify() {
if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify;
else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify;
else return this.justify;
},
uAlignItem() {
if (this.align == 'top') return 'flex-start';
if (this.align == 'bottom') return 'flex-end';
else return this.align;
}
},
methods: {
click(e) {
this.$emit('click');
}
}
}
</script>
<style lang="scss">
@import "../../libs/css/style.components.scss";
.u-row {
// 使floatflex
/* #ifndef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
@include vue-flex;
/* #endif */
flex-wrap: wrap;
}
.u-row:after {
/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
display: table;
clear: both;
content: "";
/* #endif */
}
</style>

View File

@ -0,0 +1,342 @@
<template>
<view class="u-search" @tap="clickHandler" :style="{
margin: margin,
}">
<view
class="u-content"
:style="{
backgroundColor: bgColor,
borderRadius: shape == 'round' ? '100rpx' : '10rpx',
border: borderStyle,
height: height + 'rpx'
}"
>
<view class="u-icon-wrap">
<u-icon class="u-clear-icon" :size="30" :name="searchIcon" :color="searchIconColor ? searchIconColor : color"></u-icon>
</view>
<input
confirm-type="search"
@blur="blur"
:value="value"
@confirm="search"
@input="inputChange"
:disabled="disabled"
@focus="getFocus"
:focus="focus"
:maxlength="maxlength"
placeholder-class="u-placeholder-class"
:placeholder="placeholder"
:placeholder-style="`color: ${placeholderColor}`"
class="u-input"
type="text"
:style="[{
textAlign: inputAlign,
color: color,
backgroundColor: bgColor,
}, inputStyle]"
/>
<view class="u-close-wrap" v-if="keyword && clearabled && focused" @tap="clear">
<u-icon class="u-clear-icon" name="close-circle-fill" size="34" color="#c0c4cc"></u-icon>
</view>
</view>
<view :style="[actionStyle]" class="u-action"
:class="[showActionBtn || show ? 'u-action-active' : '']"
@tap.stop.prevent="custom"
>{{ actionText }}</view>
</view>
</template>
<script>
/**
* search 搜索框
* @description 搜索组件集成了常见搜索框所需功能用户可以一键引入开箱即用
* @tutorial https://www.uviewui.com/components/search.html
* @property {String} shape 搜索框形状round-圆形square-方形默认round
* @property {String} bg-color 搜索框背景颜色默认#f2f2f2
* @property {String} border-color 边框颜色配置了颜色才会有边框
* @property {String} placeholder 占位文字内容默认请输入关键字
* @property {Boolean} clearabled 是否启用清除控件默认true
* @property {Boolean} focus 是否自动获得焦点默认false
* @property {Boolean} show-action 是否显示右侧控件默认true
* @property {String} action-text 右侧控件文字默认搜索
* @property {Object} action-style 右侧控件的样式对象形式
* @property {String} input-align 输入框内容水平对齐方式默认left
* @property {Object} input-style 自定义输入框样式对象形式
* @property {Boolean} disabled 是否启用输入框默认false
* @property {String} search-icon-color 搜索图标的颜色默认同输入框字体颜色
* @property {String} color 输入框字体颜色默认#606266
* @property {String} placeholder-color placeholder的颜色默认#909399
* @property {String} search-icon 输入框左边的图标可以为uView图标名称或图片路径
* @property {String} margin 组件与其他上下左右元素之间的距离带单位的字符串形式"30rpx"
* @property {Boolean} animation 是否开启动画见上方说明默认false
* @property {String} value 输入框初始值
* @property {String | Number} maxlength 输入框最大能输入的长度-1为不限制长度
* @property {Boolean} input-style input输入框的样式可以定义文字颜色大小等对象形式
* @property {String | Number} height 输入框高度单位rpx默认64
* @event {Function} change 输入框内容发生变化时触发
* @event {Function} search 用户确定搜索时触发用户按回车键或者手机键盘右下角的"搜索"键时触发
* @event {Function} custom 用户点击右侧控件时触发
* @event {Function} clear 用户点击清除按钮时触发
* @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
*/
export default {
name: "u-search",
props: {
// round-square-
shape: {
type: String,
default: 'round'
},
// #f2f2f2
bgColor: {
type: String,
default: '#f2f2f2'
},
//
placeholder: {
type: String,
default: '请输入关键字'
},
//
clearabled: {
type: Boolean,
default: true
},
//
focus: {
type: Boolean,
default: false
},
//
showAction: {
type: Boolean,
default: true
},
//
actionStyle: {
type: Object,
default() {
return {};
}
},
//
actionText: {
type: String,
default: '搜索'
},
// left|center|right
inputAlign: {
type: String,
default: 'left'
},
//
disabled: {
type: Boolean,
default: false
},
// showActioninput
animation: {
type: Boolean,
default: false
},
//
borderColor: {
type: String,
default: 'none'
},
//
value: {
type: String,
default: ''
},
// rpx
height: {
type: [Number, String],
default: 64
},
// input
inputStyle: {
type: Object,
default() {
return {}
}
},
// -1(uniapp)
maxlength: {
type: [Number, String],
default: '-1'
},
//
searchIconColor: {
type: String,
default: ''
},
//
color: {
type: String,
default: '#606266'
},
// placeholder
placeholderColor: {
type: String,
default: '#909399'
},
// "30rpx""30rpx 20rpx"
margin: {
type: String,
default: '0'
},
// uView
searchIcon: {
type: String,
default: 'search'
}
},
data() {
return {
keyword: '',
showClear: false, //
show: false,
// input
focused: this.focus
//
// inputValue: this.value
};
},
watch: {
keyword(nVal) {
// v-model
this.$emit('input', nVal);
// changev-model
this.$emit('change', nVal);
},
value: {
immediate: true,
handler(nVal) {
this.keyword = nVal;
}
}
},
computed: {
showActionBtn() {
if (!this.animation && this.showAction) return true;
else return false;
},
// none
borderStyle() {
if (this.borderColor) return `1px solid ${this.borderColor}`;
else return 'none';
},
},
methods: {
// HX2.6.9 v-modelinput
inputChange(e) {
this.keyword = e.detail.value;
},
//
// this.$refs
clear() {
this.keyword = '';
// clearvalue()
this.$nextTick(() => {
this.$emit('clear');
})
},
//
search(e) {
this.$emit('search', e.detail.value);
try{
//
uni.hideKeyboard();
}catch(e){}
},
//
custom() {
this.$emit('custom', this.keyword);
try{
//
uni.hideKeyboard();
}catch(e){}
},
//
getFocus() {
this.focused = true;
//
if (this.animation && this.showAction) this.show = true;
this.$emit('focus', this.keyword);
},
//
blur() {
// 使@touchstarthx2.8.4
// @blur
setTimeout(() => {
this.focused = false;
}, 100)
this.show = false;
this.$emit('blur', this.keyword);
},
// disabled=true
clickHandler() {
if(this.disabled) this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-search {
@include vue-flex;
align-items: center;
flex: 1;
}
.u-content {
@include vue-flex;
align-items: center;
padding: 0 18rpx;
flex: 1;
}
.u-clear-icon {
@include vue-flex;
align-items: center;
}
.u-input {
flex: 1;
font-size: 28rpx;
line-height: 1;
margin: 0 10rpx;
color: $u-tips-color;
}
.u-close-wrap {
width: 40rpx;
height: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.u-placeholder-class {
color: $u-tips-color;
}
.u-action {
font-size: 28rpx;
color: $u-main-color;
width: 0;
overflow: hidden;
transition: all 0.3s;
white-space: nowrap;
text-align: center;
}
.u-action-active {
width: 80rpx;
margin-left: 10rpx;
}
</style>

View File

@ -0,0 +1,154 @@
<template>
<view class="u-section">
<view class="u-section__title" :style="{
fontWeight: bold ? 'bold' : 'normal',
color: color,
fontSize: fontSize + 'rpx',
paddingLeft: showLine ? (fontSize * 0.7) + 'rpx' : 0
}" :class="{
'u-section--line': showLine
}">
<view class="u-section__title__icon-wrap u-flex" :style="[lineStyle]" v-if="showLine">
<u-icon top="0" name="column-line" :size="fontSize * 1.25" bold :color="lineColor ? lineColor : color"></u-icon>
</view>
<text class="u-flex u-section__title__text">{{title}}</text>
</view>
<view class="u-section__right-info" v-if="right || $slots.right" :style="{
color: subColor
}" @tap="rightClick">
<slot name="right" v-if="$slots.right" />
<block v-else>
{{subTitle}}
<view class="u-section__right-info__icon-arrow u-flex" v-if="arrow">
<u-icon name="arrow-right" size="24" :color="subColor"></u-icon>
</view>
</block>
</view>
</view>
</template>
<script>
/**
* section 查看更多
* @description 该组件一般用于分类信息有很多但是限于篇幅只能列出一部分让用户通过"查看更多"获得更多信息的场景实际效果见演示
* @tutorial https://www.uviewui.com/components/section.html
* @property {String} title 左边主标题
* @property {String} sub-title 右边副标题默认更多
* @property {Boolean} right 是否显示右边的内容默认true
* @property {Boolean} showLine 是否显示左边的竖条默认true
* @property {Boolean} arrow 是否显示右边箭头默认true
* @property {String Number} font-size 主标题的字体大小默认28
* @property {Boolean} bold 主标题是否加粗默认true
* @property {String} color 主标题颜色默认#303133
* @event {Function} click 组件右侧的内容被点击时触发用于跳转"更多"
* @example <u-section title="今日热门" :right="false"></u-section>
*/
export default {
name: "u-section",
props: {
//
title: {
type: String,
default: ''
},
//
subTitle: {
type: String,
default: '更多'
},
//
right: {
type: Boolean,
default: true
},
fontSize: {
type: [Number, String],
default: 28
},
//
bold: {
type: Boolean,
default: true
},
//
color: {
type: String,
default: '#303133'
},
//
subColor: {
type: String,
default: '#909399'
},
//
showLine: {
type: Boolean,
default: true
},
// 线
lineColor: {
type: String,
default: ''
},
//
arrow: {
type: Boolean,
default: true
},
},
computed: {
//
lineStyle() {
// iOStop线
return {
// 线线
left: -(Number(this.fontSize) * 0.9) + 'rpx',
top: -(Number(this.fontSize) * (this.$u.os() == 'ios' ? 0.14 : 0.15)) + 'rpx',
}
}
},
methods: {
rightClick() {
this.$emit('click');
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-section {
@include vue-flex;
justify-content: space-between;
align-items: center;
width: 100%;
&__title {
position: relative;
font-size: 28rpx;
padding-left: 20rpx;
@include vue-flex;
align-items: center;
&__icon-wrap {
position: absolute;
}
&__text {
line-height: 1;
}
}
&__right-info {
color: $u-tips-color;
font-size: 26rpx;
@include vue-flex;
align-items: center;
&__icon-arrow {
margin-left: 6rpx;
}
}
}
</style>

View File

@ -0,0 +1,420 @@
<template>
<view class="u-select">
<!-- <view class="u-select__action" :class="{
'u-select--border': border
}" @tap.stop="selectHandler">
<view class="u-select__action__icon" :class="{
'u-select__action__icon--reverse': value == true
}">
<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
</view>
</view> -->
<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
<view class="u-select">
<view class="u-select__header" @touchmove.stop.prevent="">
<view
class="u-select__header__cancel u-select__header__btn"
:style="{ color: cancelColor }"
hover-class="u-hover-class"
:hover-stay-time="150"
@tap="getResult('cancel')"
>
{{cancelText}}
</view>
<view class="u-select__header__title">
{{title}}
</view>
<view
class="u-select__header__confirm u-select__header__btn"
:style="{ color: moving ? cancelColor : confirmColor }"
hover-class="u-hover-class"
:hover-stay-time="150"
@touchmove.stop=""
@tap.stop="getResult('confirm')"
>
{{confirmText}}
</view>
</view>
<view class="u-select__body">
<picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend" v-if="value">
<picker-view-column v-for="(item, index) in columnData" :key="index">
<view class="u-select__body__picker-view__item" v-for="(item1, index1) in item" :key="index1">
<view class="u-line-1">{{ item1[labelName] }}</view>
</view>
</picker-view-column>
</picker-view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
/**
* select 列选择器
* @description 此选择器用于单列多列多列联动的选择场景(从1.3.0版本起不建议使用Picker组件的单列和多列模式Select组件是专门为列选择而构造的组件更简单易用)
* @tutorial http://uviewui.com/components/select.html
* @property {String} mode 模式选择"single-column"-单列模式"mutil-column"-多列模式"mutil-column-auto"-多列联动模式
* @property {Array} list 列数据数组形式见官网说明
* @property {Boolean} v-model 布尔值变量用于控制选择器的弹出与收起
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {String} cancel-color 取消按钮的颜色默认#606266
* @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
* @property {String} confirm-text 确认按钮的文字
* @property {String} cancel-text 取消按钮的文字
* @property {String} default-value 提供的默认选中的下标见官网说明
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
* @property {String Number} z-index 弹出时的z-index值(默认10075)
* @property {String} value-name 自定义list数据的value属性名 1.3.6
* @property {String} label-name 自定义list数据的label属性名 1.3.6
* @property {String} child-name 自定义list数据的children属性名只对多列联动模式有效 1.3.7
* @event {Function} confirm 点击确定按钮返回当前选择的值
* @example <u-select v-model="show" :list="list"></u-select>
*/
export default {
props: {
//
list: {
type: Array,
default() {
return [];
}
},
//
border: {
type: Boolean,
default: true
},
//
value: {
type: Boolean,
default: false
},
// ""
cancelColor: {
type: String,
default: '#606266'
},
// ""
confirmColor: {
type: String,
default: '#2979ff'
},
// z-index
zIndex: {
type: [String, Number],
default: 0
},
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
//
defaultValue: {
type: Array,
default() {
return [0];
}
},
// single-column-mutil-column-mutil-column-auto-
mode: {
type: String,
default: 'single-column'
},
// value
valueName: {
type: String,
default: 'value'
},
// label
labelName: {
type: String,
default: 'label'
},
// children
childName: {
type: String,
default: 'children'
},
//
title: {
type: String,
default: ''
},
//
cancelText: {
type: String,
default: '取消'
},
//
confirmText: {
type: String,
default: '确认'
}
},
data() {
return {
//
defaultSelector: [0],
// picker-view
columnData: [],
//
selectValue: [],
// index
lastSelectIndex: [],
//
columnNum: 0,
//
moving: false
};
},
watch: {
// select
value: {
immediate: true,
handler(val) {
if(val) setTimeout(() => this.init(), 10);
}
},
},
computed: {
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
},
},
methods: {
//
pickstart() {
// #ifdef MP-WEIXIN
this.moving = true;
// #endif
},
//
pickend() {
// #ifdef MP-WEIXIN
this.moving = false;
// #endif
},
init() {
this.setColumnNum();
this.setDefaultSelector();
this.setColumnData();
this.setSelectValue();
},
//
setDefaultSelector() {
// columnNum0
this.defaultSelector = this.defaultValue.length == this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0);
this.lastSelectIndex = this.$u.deepClone(this.defaultSelector);
},
//
setColumnNum() {
// 1
if(this.mode == 'single-column') this.columnNum = 1;
// this.list
else if(this.mode == 'mutil-column') this.columnNum = this.list.length;
// this.list
else if(this.mode == 'mutil-column-auto') {
let num = 1;
let column = this.list;
// children
while(column[0][this.childName]) {
column = column[0] ? column[0][this.childName] : {};
num ++;
}
this.columnNum = num;
}
},
// picker
setColumnData() {
let data = [];
this.selectValue = [];
if(this.mode == 'mutil-column-auto') {
//
let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0];
//
for (let i = 0; i < this.columnNum; i++) {
// list
if (i == 0) {
data[i] = this.list;
column = column[this.childName];
} else {
//
data[i] = column;
column = column[this.defaultSelector[i]][this.childName];
}
}
} else if(this.mode == 'single-column') {
data[0] = this.list;
} else {
data = this.list;
}
this.columnData = data;
},
// defaultValue
setSelectValue() {
let tmp = null;
for(let i = 0; i < this.columnNum; i++) {
tmp = this.columnData[i][this.defaultSelector[i]];
let data = {
value: tmp ? tmp[this.valueName] : null,
label: tmp ? tmp[this.labelName] : null
};
//
if(tmp && tmp.extra) data.extra = tmp.extra;
this.selectValue.push(data)
}
},
//
columnChange(e) {
let index = null;
let columnIndex = e.detail.value;
// push
this.selectValue = [];
this.defaultSelector = columnIndex;
if(this.mode == 'mutil-column-auto') {
//
this.lastSelectIndex.map((val, idx) => {
if (val != columnIndex[idx]) index = idx;
});
for (let i = index + 1; i < this.columnNum; i++) {
// children
//
this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName];
//
this.defaultSelector[i] = 0;
}
// this.columnDatacolumnChange
// undefined
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]];
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null,
};
//
if(data && data.extra !== undefined) tmp.extra = data.extra;
this.selectValue.push(tmp);
})
//
this.lastSelectIndex = columnIndex;
} else if(this.mode == 'single-column') {
let data = this.columnData[0][columnIndex[0]];
//
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null,
};
//
if(data && data.extra !== undefined) tmp.extra = data.extra;
this.selectValue.push(tmp);
} else if(this.mode == 'mutil-column') {
//
columnIndex.map((item, index) => {
let data = this.columnData[index][columnIndex[index]];
//
let tmp = {
value: data ? data[this.valueName] : null,
label: data ? data[this.labelName] : null,
};
//
if(data && data.extra !== undefined) tmp.extra = data.extra;
this.selectValue.push(tmp);
})
}
},
close() {
this.$emit('input', false);
// default-value
this.$set(this, 'defaultSelector', [0]);
},
//
getResult(event = null) {
// #ifdef MP-WEIXIN
if (this.moving) return;
// #endif
if (event) this.$emit(event, this.selectValue);
this.close();
},
selectHandler() {
this.$emit('click');
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-select {
&__action {
position: relative;
line-height: $u-form-item-height;
height: $u-form-item-height;
&__icon {
position: absolute;
right: 20rpx;
top: 50%;
transition: transform .4s;
transform: translateY(-50%);
z-index: 1;
&--reverse {
transform: rotate(-180deg) translateY(50%);
}
}
}
&__hader {
&__title {
color: $u-content-color;
}
}
&--border {
border-radius: 6rpx;
border-radius: 4px;
border: 1px solid $u-form-item-border-color;
}
&__header {
@include vue-flex;
align-items: center;
justify-content: space-between;
height: 80rpx;
padding: 0 40rpx;
}
&__body {
width: 100%;
height: 500rpx;
overflow: hidden;
background-color: #fff;
&__picker-view {
height: 100%;
box-sizing: border-box;
&__item {
@include vue-flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: $u-main-color;
padding: 0 8rpx;
}
}
}
}
</style>

View File

@ -0,0 +1,199 @@
<template>
<view v-if="loading" :style="{
width: windowWinth + 'px',
height: windowHeight + 'px',
backgroundColor: bgColor,
position: 'absolute',
left: left + 'px',
top: top + 'px',
zIndex: 9998,
overflow: 'hidden'
}"
@touchmove.stop.prevent>
<view v-for="(item, index) in RectNodes" :key="$u.guid()" :class="[animation ? 'skeleton-fade' : '']" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
position: 'absolute',
left: (item.left - left) + 'px',
top: (item.top - top) + 'px'
}"></view>
<view v-for="(item, index) in circleNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
borderRadius: item.width/2 + 'px',
position: 'absolute',
left: (item.left - left) + 'px',
top: (item.top - top) + 'px'
}"></view>
<view v-for="(item, index) in filletNodes" :key="$u.guid()" :class="animation ? 'skeleton-fade' : ''" :style="{
width: item.width + 'px',
height: item.height + 'px',
backgroundColor: elColor,
borderRadius: borderRadius + 'rpx',
position: 'absolute',
left: (item.left - left) + 'px',
top: (item.top - top) + 'px'
}"></view>
</view>
</template>
<script>
/**
* skeleton 骨架屏
* @description 骨架屏一般用于页面在请求远程数据尚未完成时页面用灰色块预显示本来的页面结构给用户更好的体验
* @tutorial https://www.uviewui.com/components/skeleton.html
* @property {String} el-color 骨架块状元素的背景颜色默认#e5e5e5
* @property {String} bg-color 骨架组件背景颜色默认#ffffff
* @property {Boolean} animation 骨架块是否显示动画效果默认false
* @property {String Number} border-radius u-skeleton-fillet类名元素对应的骨架块的圆角大小单位rpx默认10
* @property {Boolean} loading 是否显示骨架组件请求完成后将此值设置为false默认true
* @example <u-skeleton :loading="true" :animation="true"></u-skeleton>
*/
export default {
name: "u-skeleton",
props: {
// rgb
elColor: {
type: String,
default: '#e5e5e5'
},
//
bgColor: {
type: String,
default: '#ffffff'
},
//
animation: {
type: Boolean,
default: false
},
// u-skeleton-fillet
borderRadius: {
type: [String, Number],
default: "10"
},
// true-false-
loading: {
type: Boolean,
default: true
}
},
data() {
return {
windowWinth: 750, //
windowHeight: 1500, //
filletNodes: [], //
circleNodes: [], //
RectNodes: [], //
top: 0,
left: 0,
}
},
methods: {
//
selecterQueryInfo() {
//
// 使in(this)
let query = '';
// #ifdef MP-WEIXIN
query = uni.createSelectorQuery().in(this.$parent);
// #endif
// #ifndef MP-WEIXIN
query = uni.createSelectorQuery()
// #endif
query.selectAll('.u-skeleton').boundingClientRect().exec((res) => {
this.windowHeight = res[0][0].height;
this.windowWinth = res[0][0].width;
this.top = res[0][0].bottom - res[0][0].height;
this.left = res[0][0].left;
});
//
this.getRectEls();
//
this.getCircleEls();
//
this.getFilletEls();
},
//
getRectEls() {
let query = '';
// 使in(this)
// #ifdef MP-WEIXIN
query = uni.createSelectorQuery().in(this.$parent);
// #endif
// #ifndef MP-WEIXIN
query = uni.createSelectorQuery()
// #endif
query.selectAll('.u-skeleton-rect').boundingClientRect().exec((res) => {
this.RectNodes = res[0];
});
},
//
getFilletEls() {
let query = '';
// 使in(this)
// #ifdef MP-WEIXIN
query = uni.createSelectorQuery().in(this.$parent);
// #endif
// #ifndef MP-WEIXIN
query = uni.createSelectorQuery()
// #endif
query.selectAll('.u-skeleton-fillet').boundingClientRect().exec((res) => {
this.filletNodes = res[0];
});
},
//
getCircleEls() {
let query = '';
// 使in(this)
// #ifdef MP-WEIXIN
query = uni.createSelectorQuery().in(this.$parent);
// #endif
// #ifndef MP-WEIXIN
query = uni.createSelectorQuery()
// #endif
query.selectAll('.u-skeleton-circle').boundingClientRect().exec((res) => {
this.circleNodes = res[0];
});
}
},
//
mounted() {
//
let systemInfo = uni.getSystemInfoSync();
this.windowHeight = systemInfo.windowHeight;
this.windowWinth = systemInfo.windowWidth;
this.selecterQueryInfo();
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.skeleton-fade {
width: 100%;
height: 100%;
background: rgb(194, 207, 214);
animation-duration: 1.5s;
animation-name: blink;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}
</style>

View File

@ -0,0 +1,257 @@
<template>
<view class="u-slider" @tap="onClick" :class="[disabled ? 'u-slider--disabled' : '']" :style="{
backgroundColor: inactiveColor
}">
<view
class="u-slider__gap"
:style="[
barStyle,
{
height: height + 'rpx',
backgroundColor: activeColor
}
]"
>
<view class="u-slider__button-wrap" @touchstart="onTouchStart"
@touchmove="onTouchMove" @touchend="onTouchEnd"
@touchcancel="onTouchEnd">
<slot v-if="$slots.default || $slots.$default"/>
<view v-else class="u-slider__button" :style="[blockStyle, {
height: blockWidth + 'rpx',
width: blockWidth + 'rpx',
backgroundColor: blockColor
}]"></view>
</view>
</view>
</view>
</template>
<script>
/**
* slider 滑块选择器
* @tutorial https://uviewui.com/components/slider.html
* @property {Number | String} value 滑块默认值默认0
* @property {Number | String} min 最小值默认0
* @property {Number | String} max 最大值默认100
* @property {Number | String} step 步长默认1
* @property {Number | String} blockWidth 滑块宽度高等于宽30
* @property {Number | String} height 滑块条高度单位rpx默认6
* @property {String} inactiveColor 底部条背景颜色默认#c0c4cc
* @property {String} activeColor 底部选择部分的背景颜色默认#2979ff
* @property {String} blockColor 滑块颜色默认#ffffff
* @property {Object} blockStyle 给滑块自定义样式对象形式
* @property {Boolean} disabled 是否禁用滑块(默认为false)
* @event {Function} start 滑动触发
* @event {Function} moving 正在滑动中
* @event {Function} end 滑动结束
* @example <u-slider v-model="value" />
*/
export default {
name: 'u-slider',
props: {
// 0-100
value: {
type: [Number, String],
default: 0
},
//
disabled: {
type: Boolean,
default: false
},
// rpx
blockWidth: {
type: [Number, String],
default: 30
},
//
min: {
type: [Number, String],
default: 0
},
//
max: {
type: [Number, String],
default: 100
},
//
step: {
type: [Number, String],
default: 1
},
// rpx
height: {
type: [Number, String],
default: 6
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#c0c4cc'
},
//
blockColor: {
type: String,
default: '#ffffff'
},
//
blockStyle: {
type: Object,
default() {
return {};
}
},
},
data() {
return {
startX: 0,
status: 'end',
newValue: 0,
distanceX: 0,
startValue: 0,
barStyle: {},
sliderRect: {
left: 0,
width: 0
}
};
},
watch: {
value(n) {
// value
if(this.status == 'end') this.updateValue(this.value, false);
}
},
created() {
this.updateValue(this.value, false);
},
mounted() {
//
this.$uGetRect('.u-slider').then(rect => {
this.sliderRect = rect;
});
},
methods: {
onTouchStart(event) {
if (this.disabled) return;
this.startX = 0;
//
let touches = event.touches[0];
//
this.startX = touches.clientX;
// this.valueprops$emit('input')
this.startValue = this.format(this.value);
//
this.status = 'start';
},
onTouchMove(event) {
if (this.disabled) return;
//
// statusmoving
if (this.status == 'start') this.$emit('start');
let touches = event.touches[0];
//
this.distanceX = touches.clientX - this.sliderRect.left;
//
// step
this.newValue = (this.distanceX / this.sliderRect.width) * 100;
this.status = 'moving';
// moving
this.$emit('moving');
this.updateValue(this.newValue, true);
},
onTouchEnd() {
if (this.disabled) return;
if (this.status === 'moving') {
this.updateValue(this.newValue, false);
this.$emit('end');
}
this.status = 'end';
},
updateValue(value, drag) {
// step
const width = this.format(value);
// max100
if(width > this.max || width > 100) return;
//
let barStyle = {
width: width + '%'
};
//
if (drag == true) {
barStyle.transition = 'none';
} else {
// css
delete barStyle.transition;
}
// value
this.$emit('input', width);
this.barStyle = barStyle;
},
format(value) {
//
return Math.round(Math.max(this.min, Math.min(value, this.max)) / this.step) * this.step;
},
onClick(event) {
if (this.disabled) return;
// onTouchMove
const value = ((event.detail.x - this.sliderRect.left) / this.sliderRect.width) * 100;
this.updateValue(value, false);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-slider {
position: relative;
border-radius: 999px;
border-radius: 999px;
background-color: #ebedf0;
}
.u-slider:before {
position: absolute;
right: 0;
left: 0;
content: '';
top: -8px;
bottom: -8px;
z-index: -1;
}
.u-slider__gap {
position: relative;
border-radius: inherit;
transition: width 0.2s;
transition: width 0.2s;
background-color: #1989fa;
}
.u-slider__button {
width: 24px;
height: 24px;
border-radius: 50%;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
background-color: #fff;
cursor: pointer;
}
.u-slider__button-wrap {
position: absolute;
top: 50%;
right: 0;
transform: translate3d(50%, -50%, 0);
}
.u-slider--disabled {
opacity: 0.5;
}
</style>

View File

@ -0,0 +1,200 @@
<template>
<view class="">
<view
class="u-steps"
:style="{
flexDirection: direction
}"
>
<view class="u-steps__item"
:class="['u-steps__item--' + direction]"
v-for="(item, index) in list" :key="index"
>
<view
class="u-steps__item__num"
v-if="mode == 'number'"
:style="{
backgroundColor: current < index ? 'transparent' : activeColor,
borderColor: current < index ? unActiveColor : activeColor
}"
>
<text v-if="current < index" :style="{
color: current < index ? unActiveColor : activeColor,
}">
{{ index + 1 }}
</text>
<u-icon v-else size="22" color="#ffffff" :name="icon"></u-icon>
</view>
<view class="u-steps__item__dot" v-if="mode == 'dot'" :style="{
backgroundColor: index <= current ? activeColor : unActiveColor
}"></view>
<text class="u-line-1" :style="{
color: index <= current ? activeColor : unActiveColor,
}" :class="['u-steps__item__text--' + direction]">
{{ item.name }}
</text>
<view class="u-steps__item__line" :class="['u-steps__item__line--' + mode]" v-if="index < list.length - 1">
<u-line :direction="direction" length="100%" :hair-line="false" :color="unActiveColor"></u-line>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* steps 步骤条
* @description 该组件一般用于完成一个任务要分几个步骤标识目前处于第几步的场景
* @tutorial https://www.uviewui.com/components/steps.html
* @property {String} mode 设置模式默认dot
* @property {Array} list 数轴条数据数组具体见上方示例
* @property {String} type type主题默认primary
* @property {String} direction row-横向column-竖向默认row
* @property {Number String} current 设置当前处于第几步
* @property {String} active-color 已完成步骤的激活颜色如设置type值会失效
* @property {String} un-active-color 未激活的颜色用于表示未完成步骤的颜色默认#606266
* @example <u-steps :list="numList" active-color="#fa3534"></u-steps>
*/
export default {
name: 'u-steps',
props: {
// dot|number
mode: {
type: String,
default: 'dot'
},
//
list: {
type: Array,
default() {
return [];
}
},
// , primary|success|info|warning|error
type: {
type: String,
default: 'primary'
},
//
current: {
type: [Number, String],
default: 0
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
unActiveColor: {
type: String,
default: '#909399'
},
//
icon: {
type: String,
default: 'checkmark'
},
// steprow-column-
direction: {
type: String,
default: 'row'
}
},
data() {
return {};
},
};
</script>
<style lang="scss" scoped>
@import '../../libs/css/style.components.scss';
$u-steps-item-number-width: 44rpx;
$u-steps-item-dot-width: 20rpx;
.u-steps {
@include vue-flex;
.u-steps__item {
flex: 1;
text-align: center;
position: relative;
min-width: 100rpx;
font-size: 26rpx;
color: #8799a3;
@include vue-flex;
justify-content: center;
flex-direction: column;
align-items: center;
&--row {
@include vue-flex;
flex-direction: column;
.u-steps__item__line {
position: absolute;
z-index: 0;
left: 75%;
width: 50%;
&--dot {
top: calc(#{$u-steps-item-dot-width} / 2);
}
&--number {
top: calc(#{$u-steps-item-number-width} / 2);
}
}
}
&--column {
@include vue-flex;
flex-direction: row;
justify-content: flex-start;
min-height: 120rpx;
.u-steps__item__line {
position: absolute;
z-index: 0;
height: 50%;
top: 75%;
&--dot {
left: calc(#{$u-steps-item-dot-width} / 2);
}
&--number {
left: calc(#{$u-steps-item-number-width} / 2);
}
}
}
&__num {
@include vue-flex;
align-items: center;
justify-content: center;
width: $u-steps-item-number-width;
height: $u-steps-item-number-width;
border: 1px solid #8799a3;
border-radius: 50%;
overflow: hidden;
}
&__dot {
width: $u-steps-item-dot-width;
height: $u-steps-item-dot-width;
@include vue-flex;
border-radius: 50%;
}
&__text--row {
margin-top: 14rpx;
}
&__text--column {
margin-left: 14rpx;
}
}
}
</style>

View File

@ -0,0 +1,157 @@
<template>
<view class="">
<view class="u-sticky-wrap" :class="[elClass]" :style="{
height: fixed ? height + 'px' : 'auto',
backgroundColor: bgColor
}">
<view class="u-sticky" :style="{
position: fixed ? 'fixed' : 'static',
top: stickyTop + 'px',
left: left + 'px',
width: width == 'auto' ? 'auto' : width + 'px',
zIndex: uZIndex
}">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* sticky 吸顶
* @description 该组件与CSS中position: sticky属性实现的效果一致当组件达到预设的到顶部距离时 就会固定在指定位置组件位置大于预设的顶部距离时会重新按照正常的布局排列
* @tutorial https://www.uviewui.com/components/sticky.html
* @property {String Number} offset-top 吸顶时与顶部的距离单位rpx默认0
* @property {String Number} index 自定义标识用于区分是哪一个组件
* @property {Boolean} enable 是否开启吸顶功能默认true
* @property {String} bg-color 组件背景颜色默认#ffffff
* @property {String Number} z-index 吸顶时的z-index值默认970
* @property {String Number} h5-nav-height 导航栏高度自定义导航栏时(无导航栏时需设置为0)需要传入此值单位px默认44
* @event {Function} fixed 组件吸顶时触发
* @event {Function} unfixed 组件取消吸顶时触发
* @example <u-sticky offset-top="200"><view>塞下秋来风景异衡阳雁去无留意</view></u-sticky>
*/
export default {
name: "u-sticky",
props: {
// H5NavigationBar44px
offsetTop: {
type: [Number, String],
default: 0
},
//
index: {
type: [Number, String],
default: ''
},
//
enable: {
type: Boolean,
default: true
},
// h5
h5NavHeight: {
type: [Number, String],
default: 44
},
//
bgColor: {
type: String,
default: '#ffffff'
},
// z-index
zIndex: {
type: [Number, String],
default: ''
}
},
data() {
return {
fixed: false,
height: 'auto',
stickyTop: 0,
elClass: this.$u.guid(),
left: 0,
width: 'auto',
};
},
watch: {
offsetTop(val) {
this.initObserver();
},
enable(val) {
if (val == false) {
this.fixed = false;
this.disconnectObserver('contentObserver');
} else {
this.initObserver();
}
}
},
computed: {
uZIndex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.sticky;
}
},
mounted() {
this.initObserver();
},
methods: {
initObserver() {
if (!this.enable) return;
// #ifdef H5
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) + this.h5NavHeight : this.h5NavHeight;
// #endif
// #ifndef H5
this.stickyTop = this.offsetTop != 0 ? uni.upx2px(this.offsetTop) : 0;
// #endif
this.disconnectObserver('contentObserver');
this.$uGetRect('.' + this.elClass).then((res) => {
this.height = res.height;
this.left = res.left;
this.width = res.width;
this.$nextTick(() => {
this.observeContent();
});
});
},
observeContent() {
this.disconnectObserver('contentObserver');
const contentObserver = this.createIntersectionObserver({
thresholds: [0.95, 0.98, 1]
});
contentObserver.relativeToViewport({
top: -this.stickyTop
});
contentObserver.observe('.' + this.elClass, res => {
if (!this.enable) return;
this.setFixed(res.boundingClientRect.top);
});
this.contentObserver = contentObserver;
},
setFixed(top) {
const fixed = top < this.stickyTop;
if (fixed) this.$emit('fixed', this.index);
else if(this.fixed) this.$emit('unfixed', this.index);
this.fixed = fixed;
},
disconnectObserver(observerName) {
const observer = this[observerName];
observer && observer.disconnect();
},
},
beforeDestroy() {
this.disconnectObserver('contentObserver');
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-sticky {
z-index: 9999999999;
}
</style>

View File

@ -0,0 +1,355 @@
<template>
<view class="u-subsection" :style="[subsectionStyle]">
<view class="u-item u-line-1" :style="[itemStyle(index)]" @tap="click(index)" :class="[noBorderRight(index), 'u-item-' + index]"
v-for="(item, index) in listInfo" :key="index">
<view :style="[textStyle(index)]" class="u-item-text u-line-1">{{ item.name }}</view>
</view>
<view class="u-item-bg" :style="[itemBarStyle]"></view>
</view>
</template>
<script>
/**
* subsection 分段器
* @description 该分段器一般用于用户从几个选项中选择某一个的场景
* @tutorial https://www.uviewui.com/components/subsection.html
* @property {Array} list 选项的数组形式见上方"基本使用"
* @property {String Number} current 初始化时默认选中的选项索引值默认0
* @property {String} active-color 激活时的颜色mode为subsection时固定为白色默认#303133
* @property {String} inactive-color 未激活时字体的颜色mode为subsection时无效默认#606266
* @property {String} mode 模式选择见官网"模式选择"说明默认button
* @property {String Number} font-size 字体大小单位rpx默认28
* @property {String Number} height 组件高度单位rpx默认70
* @property {Boolean} animation 是否开启动画效果见上方说明默认true
* @property {Boolean} bold 激活选项的字体是否加粗默认true
* @property {String} bg-color 组件背景颜色mode为button时有效默认#eeeeef
* @property {String} button-color 按钮背景颜色mode为button时有效默认#ffffff
* @event {Function} change 分段器选项发生改变时触发
* @example <u-subsection active-color="#ff9900"></u-subsection>
*/
export default {
name: "u-subsection",
props: {
// tab
list: {
type: Array,
default () {
return [];
}
},
// tabindex
current: {
type: [Number, String],
default: 0
},
//
activeColor: {
type: String,
default: '#303133'
},
//
inactiveColor: {
type: String,
default: '#606266'
},
// mode=buttonmode=subsection
mode: {
type: String,
default: 'button'
},
// rpx
fontSize: {
type: [Number, String],
default: 28
},
//
animation: {
type: Boolean,
default: true
},
// rpx
height: {
type: [Number, String],
default: 70
},
// tab
bold: {
type: Boolean,
default: true
},
// mode=button
bgColor: {
type: String,
default: '#eeeeef'
},
// mode = button
buttonColor: {
type: String,
default: '#ffffff'
},
//
vibrateShort: {
type: Boolean,
default: false
}
},
data() {
return {
listInfo: [],
itemBgStyle: {
width: 0,
left: 0,
backgroundColor: '#ffffff',
height: '100%',
transition: ''
},
currentIndex: this.current,
buttonPadding: 3, // mode = button
borderRadius: 5, //
firstTimeVibrateShort: true // current
};
},
watch: {
current: {
immediate: true,
handler(nVal) {
this.currentIndex = nVal;
this.changeSectionStatus(nVal);
}
}
},
created() {
// listlistInfopropslist
// ['', ''],[{name: ''}, {name: ''}]
this.listInfo = this.list.map((val, index) => {
if (typeof val != 'object') {
let obj = {
width: 0,
name: val
};
return obj;
} else {
val.width = 0;
return val;
}
});
},
computed: {
// mode=subsection
noBorderRight() {
return index => {
if (this.mode != 'subsection') return;
let classs = '';
//
if (index < this.list.length - 1) classs += ' u-none-border-right';
//
if (index == 0) classs += ' u-item-first';
if (index == this.list.length - 1) classs += ' u-item-last';
return classs;
};
},
//
textStyle() {
return index => {
let style = {};
//
if (this.mode == 'subsection') {
if (index == this.currentIndex) {
style.color = '#ffffff';
} else {
style.color = this.activeColor;
}
} else {
if (index == this.currentIndex) {
style.color = this.activeColor;
} else {
style.color = this.inactiveColor;
}
}
//
if (index == this.currentIndex && this.bold) style.fontWeight = 'bold';
//
style.fontSize = this.fontSize + 'rpx';
return style;
};
},
// item
itemStyle() {
return index => {
let style = {};
if (this.mode == 'subsection') {
// border
style.borderColor = this.activeColor;
style.borderWidth = '1px';
style.borderStyle = 'solid';
}
return style;
};
},
// mode=buttonview
subsectionStyle() {
let style = {};
style.height = uni.upx2px(this.height) + 'px';
if (this.mode == 'button') {
style.backgroundColor = this.bgColor;
style.padding = `${this.buttonPadding}px`;
style.borderRadius = `${this.borderRadius}px`;
}
return style;
},
//
itemBarStyle() {
let style = {};
style.backgroundColor = this.activeColor;
style.zIndex = 1;
if (this.mode == 'button') {
style.backgroundColor = this.buttonColor;
style.borderRadius = `${this.borderRadius}px`;
style.bottom = `${this.buttonPadding}px`;
style.height = uni.upx2px(this.height) - this.buttonPadding * 2 + 'px';
style.zIndex = 0;
}
return Object.assign(this.itemBgStyle, style);
}
},
mounted() {
setTimeout(() => {
this.getTabsInfo();
}, 10);
},
methods: {
//
changeSectionStatus(nVal) {
if (this.mode == 'subsection') {
//
if (nVal == this.list.length - 1) {
this.itemBgStyle.borderRadius = `0 ${this.buttonPadding}px ${this.buttonPadding}px 0`;
}
if (nVal == 0) {
this.itemBgStyle.borderRadius = `${this.buttonPadding}px 0 0 ${this.buttonPadding}px`;
}
if (nVal > 0 && nVal < this.list.length - 1) {
this.itemBgStyle.borderRadius = '0';
}
}
//
setTimeout(() => {
this.itemBgLeft();
}, 10);
if (this.vibrateShort && !this.firstTimeVibrateShort) {
// 使APP(HX 2.6.8)H5
// #ifndef H5
uni.vibrateShort();
// #endif
}
// firstTimeVibrateShortfalse()
this.firstTimeVibrateShort = false;
},
click(index) {
//
if (index == this.currentIndex) return;
this.currentIndex = index;
this.changeSectionStatus(index);
this.$emit('change', Number(index));
},
// tab
getTabsInfo() {
let view = uni.createSelectorQuery().in(this);
for (let i = 0; i < this.list.length; i++) {
view.select('.u-item-' + i).boundingClientRect();
}
view.exec(res => {
if (!res.length) {
setTimeout(() => {
this.getTabsInfo();
return;
}, 10);
}
// itemlistInfo
res.map((val, index) => {
this.listInfo[index].width = val.width;
});
//
if (this.mode == 'subsection') {
this.itemBgStyle.width = this.listInfo[0].width + 'px';
} else if (this.mode == 'button') {
this.itemBgStyle.width = this.listInfo[0].width + 'px';
}
//
this.itemBgLeft();
});
},
itemBgLeft() {
//
if (this.animation) {
this.itemBgStyle.transition = 'all 0.35s';
} else {
this.itemBgStyle.transition = 'all 0s';
}
let left = 0;
// item
this.listInfo.map((val, index) => {
if (index < this.currentIndex) left += val.width;
});
// mode
if (this.mode == 'subsection') {
this.itemBgStyle.left = left + 'px';
} else if (this.mode == 'button') {
this.itemBgStyle.left = left + this.buttonPadding + 'px';
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-subsection {
@include vue-flex;
align-items: center;
overflow: hidden;
position: relative;
}
.u-item {
flex: 1;
text-align: center;
font-size: 26rpx;
height: 100%;
@include vue-flex;
align-items: center;
justify-content: center;
color: $u-main-color;
padding: 0 6rpx;
}
.u-item-bg {
background-color: $u-type-primary;
position: absolute;
z-index: -1;
}
.u-none-border-right {
border-right: none !important;
}
.u-item-first {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
.u-item-last {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.u-item-text {
transition: all 0.35s;
color: $u-main-color;
@include vue-flex;
align-items: center;
position: relative;
z-index: 3;
}
</style>

View File

@ -0,0 +1,255 @@
<template>
<view class="">
<movable-area class="u-swipe-action" :style="{ backgroundColor: bgColor }">
<movable-view
class="u-swipe-view"
@change="change"
@touchend="touchend"
@touchstart="touchstart"
direction="horizontal"
:disabled="disabled"
:x="moveX"
:style="{
width: movableViewWidth ? movableViewWidth : '100%'
}"
>
<view
class="u-swipe-content"
@tap.stop="contentClick"
>
<slot></slot>
</view>
<view class="u-swipe-del" v-if="showBtn" @tap.stop="btnClick(index)" :style="[btnStyle(item.style)]" v-for="(item, index) in options" :key="index">
<view class="u-btn-text">{{ item.text }}</view>
</view>
</movable-view>
</movable-area>
</view>
</template>
<script>
/**
* swipeAction 左滑单元格
* @description 该组件一般用于左滑唤出操作菜单的场景用的最多的是左滑删除操作
* @tutorial https://www.uviewui.com/components/swipeAction.html
* @property {String} bg-color 整个组件背景颜色默认#ffffff
* @property {Array} options 数组形式可以配置背景颜色和文字
* @property {String Number} index 标识符点击时候用于区分点击了哪一个用v-for循环时的index即可
* @property {String Number} btn-width 按钮宽度单位rpx默认180
* @property {Boolean} disabled 是否禁止某个swipeAction滑动默认false
* @property {Boolean} show 打开或者关闭某个组件默认false
* @event {Function} click 点击组件时触发
* @event {Function} close 组件触发关闭状态时
* @event {Function} content-click 点击内容时触发
* @event {Function} open 组件触发打开状态时
* @example <u-swipe-action btn-text="">...</u-swipe-action>
*/
export default {
name: 'u-swipe-action',
props: {
// index
index: {
type: [Number, String],
default: ''
},
// rpx
btnWidth: {
type: [String, Number],
default: 180
},
// action
disabled: {
type: Boolean,
default: false
},
//
show: {
type: Boolean,
default: false
},
//
bgColor: {
type: String,
default: '#ffffff'
},
// 使iOS(2020-05-06)
vibrateShort: {
type: Boolean,
default: false
},
//
options: {
type: Array,
default() {
return [];
}
}
},
watch: {
show: {
immediate: true,
handler(nVal, oVal) {
if (nVal) {
this.open();
} else {
this.close();
}
}
}
},
data() {
return {
moveX: 0, // movable-viewx
scrollX: 0, // movable-viewchangex
status: false, //
movableAreaWidth: 0, //
elId: this.$u.guid(), // id
showBtn: false, //
};
},
computed: {
movableViewWidth() {
return this.movableAreaWidth + this.allBtnWidth + 'px';
},
innerBtnWidth() {
return uni.upx2px(this.btnWidth);
},
allBtnWidth() {
return uni.upx2px(this.btnWidth) * this.options.length;
},
btnStyle() {
return style => {
let css = {};
style.width = this.btnWidth + 'rpx';
return style;
};
}
},
mounted() {
this.getActionRect();
},
methods: {
//
btnClick(index) {
this.status = false;
// this.indexindex(options)
this.$emit('click', this.index, index);
},
// movable-view
change(e) {
this.scrollX = e.detail.x;
},
//
close() {
this.moveX = 0;
this.status = false;
},
//
open() {
if (this.disabled) return;
this.moveX = -this.allBtnWidth;
this.status = true;
},
// movable-view
touchend() {
this.moveX = this.scrollX;
//
//
// this.moveX
// propsmovable-view
// https://uniapp.dcloud.io/use?id=%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98
this.$nextTick(function() {
if (this.status == false) {
// x
if (this.scrollX <= -this.allBtnWidth / 4) {
this.moveX = -this.allBtnWidth; // movable-view
this.status = true; //
this.emitOpenEvent();
//
if (this.vibrateShort) uni.vibrateShort();
} else {
this.moveX = 0; //
this.status = false;
this.emitCloseEvent();
}
} else {
// X()
if (this.scrollX > (-this.allBtnWidth * 3) / 4) {
this.moveX = 0;
this.$nextTick(() => {
this.moveX = 101;
});
this.status = false;
this.emitCloseEvent();
} else {
this.moveX = -this.allBtnWidth;
this.status = true;
this.emitOpenEvent();
}
}
});
},
emitOpenEvent() {
this.$emit('open', this.index);
},
emitCloseEvent() {
this.$emit('close', this.index);
},
//
touchstart() {},
getActionRect() {
this.$uGetRect('.u-swipe-action').then(res => {
this.movableAreaWidth = res.width;
// ""
this.$nextTick(() => {
this.showBtn = true;
})
});
},
//
contentClick() {
//
if (this.status == true) {
this.status = 'close';
this.moveX = 0;
}
this.$emit('content-click', this.index);
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-swipe-action {
width: auto;
height: initial;
position: relative;
overflow: hidden;
}
.u-swipe-view {
@include vue-flex;
height: initial;
position: relative;
/* 这一句很关键,覆盖默认的绝对定位 */
}
.u-swipe-content {
flex: 1;
}
.u-swipe-del {
position: relative;
font-size: 30rpx;
color: #ffffff;
}
.u-btn-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

View File

@ -0,0 +1,340 @@
<template>
<view class="u-swiper-wrap" :style="{
borderRadius: `${borderRadius}rpx`
}">
<swiper :current="elCurrent" @change="change" @animationfinish="animationfinish" :interval="interval" :circular="circular" :duration="duration" :autoplay="autoplay"
:previous-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'" :next-margin="effect3d ? effect3dPreviousMargin + 'rpx' : '0'"
:style="{
height: height + 'rpx',
backgroundColor: bgColor
}">
<swiper-item class="u-swiper-item" v-for="(item, index) in list" :key="index">
<view class="u-list-image-wrap" @tap.stop.prevent="listClick(index)" :class="[uCurrent != index ? 'u-list-scale' : '']" :style="{
borderRadius: `${borderRadius}rpx`,
transform: effect3d && uCurrent != index ? 'scaleY(0.9)' : 'scaleY(1)',
margin: effect3d && uCurrent != index ? '0 20rpx' : 0,
}">
<image class="u-swiper-image" :src="item[name] || item" :mode="imgMode"></image>
<view v-if="title && item.title" class="u-swiper-title u-line-1" :style="[{
'padding-bottom': titlePaddingBottom
}, titleStyle]">
{{ item.title }}
</view>
</view>
</swiper-item>
</swiper>
<view class="u-swiper-indicator" :style="{
top: indicatorPos == 'topLeft' || indicatorPos == 'topCenter' || indicatorPos == 'topRight' ? '12rpx' : 'auto',
bottom: indicatorPos == 'bottomLeft' || indicatorPos == 'bottomCenter' || indicatorPos == 'bottomRight' ? '12rpx' : 'auto',
justifyContent: justifyContent,
padding: `0 ${effect3d ? '74rpx' : '24rpx'}`
}">
<block v-if="mode == 'rect'">
<view class="u-indicator-item-rect" :class="{ 'u-indicator-item-rect-active': index == uCurrent }" v-for="(item, index) in list"
:key="index"></view>
</block>
<block v-if="mode == 'dot'">
<view class="u-indicator-item-dot" :class="{ 'u-indicator-item-dot-active': index == uCurrent }" v-for="(item, index) in list"
:key="index"></view>
</block>
<block v-if="mode == 'round'">
<view class="u-indicator-item-round" :class="{ 'u-indicator-item-round-active': index == uCurrent }" v-for="(item, index) in list"
:key="index"></view>
</block>
<block v-if="mode == 'number'">
<view class="u-indicator-item-number">{{ uCurrent + 1 }}/{{ list.length }}</view>
</block>
</view>
</view>
</template>
<script>
/**
* swiper 轮播图
* @description 该组件一般用于导航轮播广告展示等场景,可开箱即用
* @tutorial https://www.uviewui.com/components/swiper.html
* @property {Array} list 轮播图数据见官网"基本使用"说明
* @property {Boolean} title 是否显示标题文字需要配合list参数见官网说明默认false
* @property {String} mode 指示器模式见官网说明默认round
* @property {String Number} height 轮播图组件高度单位rpx默认250
* @property {String} indicator-pos 指示器的位置默认bottomCenter
* @property {Boolean} effect3d 是否开启3D效果默认false
* @property {Boolean} autoplay 是否自动播放默认true
* @property {String Number} interval 自动轮播时间间隔单位ms默认2500
* @property {Boolean} circular 是否衔接播放见官网说明默认true
* @property {String} bg-color 背景颜色默认#f3f4f6
* @property {String Number} border-radius 轮播图圆角值单位rpx默认8
* @property {Object} title-style 自定义标题样式
* @property {String Number} effect3d-previous-margin mode = true模式的情况下激活项与前后项之间的距离单位rpx默认50
* @property {String} img-mode 图片的裁剪模式详见image组件裁剪模式默认aspectFill
* @event {Function} click 点击轮播图时触发
* @example <u-swiper :list="list" mode="dot" indicator-pos="bottomRight"></u-swiper>
*/
export default {
name: "u-swiper",
props: {
// ,[{image: 'xxxx', title: 'xxxx'}{image: 'yyyy', title: 'yyyy'}]title
list: {
type: Array,
default () {
return [];
}
},
// title
title: {
type: Boolean,
default: false
},
//
indicator: {
type: Object,
default () {
return {};
}
},
//
borderRadius: {
type: [Number, String],
default: 8
},
//
interval: {
type: [String, Number],
default: 3000
},
// rect|dot|number|round
mode: {
type: String,
default: 'round'
},
// listrpx
height: {
type: [Number, String],
default: 250
},
// topLeft|topCenter|topRight|bottomLeft|bottomCenter|bottomRight
indicatorPos: {
type: String,
default: 'bottomCenter'
},
//
effect3d: {
type: Boolean,
default: false
},
// 3Ditemitemrpx
effect3dPreviousMargin: {
type: [Number, String],
default: 50
},
//
autoplay: {
type: Boolean,
default: true
},
// ms
duration: {
type: [Number, String],
default: 500
},
//
circular: {
type: Boolean,
default: true
},
//
imgMode: {
type: String,
default: 'aspectFill'
},
// list
name: {
type: String,
default: 'image'
},
//
bgColor: {
type: String,
default: '#f3f4f6'
},
//
current: {
type: [Number, String],
default: 0
},
//
titleStyle: {
type: Object,
default() {
return {}
}
}
},
watch: {
// listuCurrent
list(nVal, oVal) {
if(nVal.length !== oVal.length) this.uCurrent = 0;
},
// currentuCurrentcurrentuCurrent
// uCurrent
current(n) {
this.uCurrent = n;
}
},
data() {
return {
uCurrent: this.current // swiper-itemindex
};
},
computed: {
justifyContent() {
if (this.indicatorPos == 'topLeft' || this.indicatorPos == 'bottomLeft') return 'flex-start';
if (this.indicatorPos == 'topCenter' || this.indicatorPos == 'bottomCenter') return 'center';
if (this.indicatorPos == 'topRight' || this.indicatorPos == 'bottomRight') return 'flex-end';
},
titlePaddingBottom() {
let tmp = 0;
if (this.mode == 'none') return '12rpx';
if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode == 'number') {
tmp = '60rpx';
} else if (['bottomLeft', 'bottomCenter', 'bottomRight'].indexOf(this.indicatorPos) >= 0 && this.mode != 'number') {
tmp = '40rpx';
} else {
tmp = '12rpx';
}
return tmp;
},
// uniswipercurrentNumber
elCurrent() {
return Number(this.current);
}
},
methods: {
listClick(index) {
this.$emit('click', index);
},
change(e) {
let current = e.detail.current;
this.uCurrent = current;
// changeindex0
this.$emit('change', current);
},
// animationfinishchange
// swiperuCurrent
animationfinish(e) {
// #ifndef MP-TOUTIAO
// this.uCurrent = e.detail.current;
// #endif
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-swiper-wrap {
position: relative;
overflow: hidden;
transform: translateY(0);
}
.u-swiper-image {
width: 100%;
will-change: transform;
height: 100%;
/* #ifndef APP-NVUE */
display: block;
/* #endif */
/* #ifdef H5 */
pointer-events: none;
/* #endif */
}
.u-swiper-indicator {
padding: 0 24rpx;
position: absolute;
@include vue-flex;
width: 100%;
z-index: 1;
}
.u-indicator-item-rect {
width: 26rpx;
height: 8rpx;
margin: 0 6rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-rect-active {
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-dot {
width: 14rpx;
height: 14rpx;
margin: 0 6rpx;
border-radius: 20rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-dot-active {
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-round {
width: 14rpx;
height: 14rpx;
margin: 0 6rpx;
border-radius: 20rpx;
transition: all 0.5s;
background-color: rgba(0, 0, 0, 0.3);
}
.u-indicator-item-round-active {
width: 34rpx;
background-color: rgba(255, 255, 255, 0.8);
}
.u-indicator-item-number {
padding: 6rpx 16rpx;
line-height: 1;
background-color: rgba(0, 0, 0, 0.3);
border-radius: 100rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
.u-list-scale {
transform-origin: center center;
}
.u-list-image-wrap {
width: 100%;
height: 100%;
flex: 1;
transition: all 0.5s;
overflow: hidden;
box-sizing: content-box;
position: relative;
}
.u-swiper-title {
position: absolute;
background-color: rgba(0, 0, 0, 0.3);
bottom: 0;
left: 0;
width: 100%;
font-size: 28rpx;
padding: 12rpx 24rpx;
color: rgba(255, 255, 255, 0.9);
}
.u-swiper-item {
@include vue-flex;
overflow: hidden;
align-items: center;
}
</style>

View File

@ -0,0 +1,163 @@
<template>
<view class="u-switch" :class="[value == true ? 'u-switch--on' : '', disabled ? 'u-switch--disabled' : '']" @tap="onClick"
:style="[switchStyle]">
<view class="u-switch__node node-class" :style="{
width: $u.addUnit(this.size),
height: $u.addUnit(this.size)
}">
<u-loading :show="loading" class="u-switch__loading" :size="size * 0.6" :color="loadingColor" />
</view>
</view>
</template>
<script>
/**
* switch 开关选择器
* @description 选择开关一般用于只有两个选择且只能选其一的场景
* @tutorial https://www.uviewui.com/components/switch.html
* @property {Boolean} loading 是否处于加载中默认false
* @property {Boolean} disabled 是否禁用默认false
* @property {String Number} size 开关尺寸单位rpx默认50
* @property {String} active-color 打开时的背景色默认#2979ff
* @property {Boolean} inactive-color 关闭时的背景色默认#ffffff
* @property {Boolean | Number | String} active-value 打开选择器时通过change事件发出的值默认true
* @property {Boolean | Number | String} inactive-value 关闭选择器时通过change事件发出的值默认false
* @event {Function} change 在switch打开或关闭时触发
* @example <u-switch v-model="checked" active-color="red" inactive-color="#eee"></u-switch>
*/
export default {
name: "u-switch",
props: {
//
loading: {
type: Boolean,
default: false
},
//
disabled: {
type: Boolean,
default: false
},
// rpx
size: {
type: [Number, String],
default: 50
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#ffffff'
},
// v-model
value: {
type: Boolean,
default: false
},
// 使iOS(2020-05-06)
vibrateShort: {
type: Boolean,
default: false
},
//
activeValue: {
type: [Number, String, Boolean],
default: true
},
//
inactiveValue: {
type: [Number, String, Boolean],
default: false
},
},
data() {
return {
}
},
computed: {
switchStyle() {
let style = {};
style.fontSize = this.size + 'rpx';
style.backgroundColor = this.value ? this.activeColor : this.inactiveColor;
return style;
},
loadingColor() {
return this.value ? this.activeColor : null;
}
},
methods: {
onClick() {
if (!this.disabled && !this.loading) {
// 使APP(HX 2.6.8)H5
if(this.vibrateShort) uni.vibrateShort();
this.$emit('input', !this.value);
// value
this.$nextTick(() => {
this.$emit('change', this.value ? this.activeValue : this.inactiveValue);
})
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-switch {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
box-sizing: initial;
width: 2em;
height: 1em;
background-color: #fff;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 1em;
transition: background-color 0.3s;
font-size: 50rpx;
}
.u-switch__node {
@include vue-flex;
align-items: center;
justify-content: center;
position: absolute;
top: 0;
left: 0;
border-radius: 100%;
z-index: 1;
background-color: #fff;
background-color: #fff;
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, 0.05), 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05);
box-shadow: 0 3px 1px 0 rgba(0, 0, 0, 0.05), 0 2px 2px 0 rgba(0, 0, 0, 0.1), 0 3px 3px 0 rgba(0, 0, 0, 0.05);
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05), -webkit-transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05);
transition: transform cubic-bezier(0.3, 1.05, 0.4, 1.05);
transition: transform 0.3s cubic-bezier(0.3, 1.05, 0.4, 1.05)
}
.u-switch__loading {
@include vue-flex;
align-items: center;
justify-content: center;
}
.u-switch--on {
background-color: #1989fa;
}
.u-switch--on .u-switch__node {
transform: translateX(100%);
}
.u-switch--disabled {
opacity: 0.4;
}
</style>

View File

@ -0,0 +1,330 @@
<template>
<view v-if="show" class="u-tabbar" @touchmove.stop.prevent="() => {}">
<view class="u-tabbar__content safe-area-inset-bottom" :style="{
height: $u.addUnit(height),
backgroundColor: bgColor,
}" :class="{
'u-border-top': borderTop
}">
<view class="u-tabbar__content__item" v-for="(item, index) in list" :key="index" :class="{
'u-tabbar__content__circle': midButton &&item.midButton
}" @tap.stop="clickHandler(index)" :style="{
backgroundColor: bgColor
}">
<view :class="[
midButton && item.midButton ? 'u-tabbar__content__circle__button' : 'u-tabbar__content__item__button'
]">
<u-icon
:size="midButton && item.midButton ? midButtonSize : iconSize"
:name="elIconPath(index)"
img-mode="scaleToFill"
:color="elColor(index)"
:custom-prefix="item.customIcon ? 'custom-icon' : 'uicon'"
></u-icon>
<u-badge :count="item.count" :is-dot="item.isDot"
v-if="item.count || item.isDot"
:offset="[-2, getOffsetRight(item.count, item.isDot)]"
></u-badge>
</view>
<view class="u-tabbar__content__item__text" :style="{
color: elColor(index)
}">
<text class="u-line-1">{{item.text}}</text>
</view>
</view>
<view v-if="midButton" class="u-tabbar__content__circle__border" :class="{
'u-border': borderTop,
}" :style="{
backgroundColor: bgColor,
left: midButtonLeft
}">
</view>
</view>
<!-- 这里加上一个48rpx的高度,是为了增高有凸起按钮时的防塌陷高度(也即按钮凸出来部分的高度) -->
<view class="u-fixed-placeholder safe-area-inset-bottom" :style="{
height: `calc(${$u.addUnit(height)} + ${midButton ? 48 : 0}rpx)`,
}"></view>
</view>
</template>
<script>
export default {
props: {
//
show: {
type: Boolean,
default: true
},
// v-modelcurrent
value: {
type: [String, Number],
default: 0
},
// tabbar
bgColor: {
type: String,
default: '#ffffff'
},
// tabbar50pxrpx
height: {
type: [String, Number],
default: '50px'
},
// rpx
iconSize: {
type: [String, Number],
default: 40
},
// rpx
midButtonSize: {
type: [String, Number],
default: 90
},
//
activeColor: {
type: String,
default: '#303133'
},
//
inactiveColor: {
type: String,
default: '#606266'
},
//
midButton: {
type: Boolean,
default: false
},
//
list: {
type: Array,
default () {
return []
}
},
//
beforeSwitch: {
type: Function,
default: null
},
// 线
borderTop: {
type: Boolean,
default: true
},
// tabbar
hideTabBar: {
type: Boolean,
default: true
},
},
data() {
return {
// cssjs
midButtonLeft: '50%',
pageUrl: '', // URL
}
},
created() {
// tabbar
if(this.hideTabBar) uni.hideTabBar();
// u-tabbar"/"
let pages = getCurrentPages();
// route
this.pageUrl = pages[pages.length - 1].route;
},
computed: {
elIconPath() {
return (index) => {
// u-tabbaritempagePath
// datapageUrlitemtabbar
// 使v-modelvalue
let pagePath = this.list[index].pagePath;
// pagePath使tabbar使tabbar
// tabbar item
if(pagePath) {
if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) {
return this.list[index].selectedIconPath;
} else {
return this.list[index].iconPath;
}
} else {
// v-model
return index == this.value ? this.list[index].selectedIconPath : this.list[index].iconPath
}
}
},
elColor() {
return (index) => {
// elIconPath
let pagePath = this.list[index].pagePath;
if(pagePath) {
if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) return this.activeColor;
else return this.inactiveColor;
} else {
return index == this.value ? this.activeColor : this.inactiveColor;
}
}
}
},
mounted() {
this.midButton && this.getMidButtonLeft();
},
methods: {
async clickHandler(index) {
if(this.beforeSwitch && typeof(this.beforeSwitch) === 'function') {
//
// (H5)customBack()thisthis
// bind()thisthis.customBack()this
let beforeSwitch = this.beforeSwitch.bind(this.$u.$parent.call(this))(index);
// promise
if (!!beforeSwitch && typeof beforeSwitch.then === 'function') {
await beforeSwitch.then(res => {
// promise
this.switchTab(index);
}).catch(err => {
})
} else if(beforeSwitch === true) {
// true
this.switchTab(index);
}
} else {
this.switchTab(index);
}
},
// tab
switchTab(index) {
// v-model
this.$emit('change', index);
// pagePath使uni.switchTab
if(this.list[index].pagePath) {
uni.switchTab({
url: this.list[index].pagePath
})
} else {
// papgePathv-modelvalue
// v-modelvaluegetCurrentPages()
this.$emit('input', index);
}
},
// right
getOffsetRight(count, isDot) {
// count9()right
if(isDot) {
return -20;
} else if(count > 9) {
return -40;
} else {
return -30;
}
},
// left
getMidButtonLeft() {
let windowWidth = this.$u.sys().windowWidth;
// cssleft: 50%js
this.midButtonLeft = (windowWidth / 2) + 'px';
}
}
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
.u-fixed-placeholder {
/* #ifndef APP-NVUE */
box-sizing: content-box;
/* #endif */
}
.u-tabbar {
&__content {
@include vue-flex;
align-items: center;
position: relative;
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: 998;
/* #ifndef APP-NVUE */
box-sizing: content-box;
/* #endif */
&__circle__border {
border-radius: 100%;
width: 110rpx;
height: 110rpx;
top: -48rpx;
position: absolute;
z-index: 4;
background-color: #ffffff;
// 3tabbar itemcss
// 使jsjs
left: 50%;
transform: translateX(-50%);
&:after {
border-radius: 100px;
}
}
&__item {
flex: 1;
justify-content: center;
height: 100%;
padding: 12rpx 0;
@include vue-flex;
flex-direction: column;
align-items: center;
position: relative;
&__button {
position: absolute;
top: 14rpx;
left: 50%;
transform: translateX(-50%);
}
&__text {
color: $u-content-color;
font-size: 26rpx;
line-height: 28rpx;
position: absolute;
bottom: 14rpx;
left: 50%;
transform: translateX(-50%);
width: 100%;
text-align: center;
}
}
&__circle {
position: relative;
@include vue-flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
/* #ifndef APP-NVUE */
height: calc(100% - 1px);
/* #endif */
&__button {
width: 90rpx;
height: 90rpx;
border-radius: 100%;
@include vue-flex;
justify-content: center;
align-items: center;
position: absolute;
background-color: #ffffff;
top: -40rpx;
left: 50%;
z-index: 6;
transform: translateX(-50%);
}
}
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<view class="u-table" :style="[tableStyle]">
<slot />
</view>
</template>
<script>
/**
* table 表格
* @description 表格组件一般用于展示大量结构化数据的场景
* @tutorial https://www.uviewui.com/components/table.html
* @property {String} border-color 表格边框的颜色默认#e4e7ed
* @property {String} bg-color 表格的背景颜色默认#ffffff
* @property {String} align 单元格的内容对齐方式作用类似css的text-align默认center
* @property {String} padding 单元格的内边距同css的padding写法默认10rpx 0
* @property {String Number} font-size 单元格字体大小单位rpx默认28
* @property {String} color 单元格字体颜色默认#606266
* @property {Object} th-style th单元格的样式对象形式(将th所需参数放在table组件是为了避免每一个th组件要写一遍
* @event {Function} click 点击组件时触发
* @event {Function} close 点击关闭按钮时触发
* @example <u-table></u-table>
*/
export default {
name: "u-table",
props: {
borderColor: {
type: String,
default: '#e4e7ed'
},
align: {
type: String,
default: 'center'
},
// td
padding: {
type: String,
default: '10rpx 6rpx'
},
//
fontSize: {
type: [String, Number],
default: 28
},
//
color: {
type: String,
default: '#606266'
},
// th
thStyle: {
type: Object,
default () {
return {}
}
},
// table
bgColor: {
type: String,
default: '#ffffff'
}
},
data() {
return {}
},
computed: {
tableStyle() {
let style = {};
style.borderLeft = `solid 1px ${this.borderColor}`;
style.borderTop = `solid 1px ${this.borderColor}`;
style.backgroundColor = this.bgColor;;
return style;
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-table {
width: 100%;
box-sizing: border-box;
}
</style>

View File

@ -0,0 +1,488 @@
<template>
<view class="u-tabs" :style="{
zIndex: zIndex,
background: bgColor
}">
<scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{ zIndex: zIndex + 1 }">
<view class="u-tabs-scroll-box" :class="{'u-tabs-scorll-flex': !isScroll}">
<view class="u-tabs-item" :style="[tabItemStyle(index)]"
v-for="(item, index) in getTabs" :key="index" :class="[preId + index]" @tap="emit(index)">
<u-badge :count="item[count] || item['count'] || 0" :offset="offset" size="mini"></u-badge>
{{ item[name] || item['name']}}
</view>
<view v-if="showBar" class="u-scroll-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</template>
<script>
import colorGradient from '../../libs/function/colorGradient';
let color = colorGradient;
const { windowWidth } = uni.getSystemInfoSync();
const preId = 'UEl_';
/**
* tabsSwiper 全屏选项卡
* @description 该组件内部实现主要依托于uniapp的scroll-view和swiper组件主要特色是切换过程中tabsSwiper文字的颜色可以渐变底部滑块可以 跟随式滑动活动tab滚动居中等应用场景可以用于需要左右切换页面比如商城的订单中心(待收货-待付款-待评价-已退货)等应用场景
* @tutorial https://www.uviewui.com/components/tabsSwiper.html
* @property {Boolean} is-scroll tabs是否可以左右拖动默认true
* @property {Array} list 标签数组元素为对象[{name: '推荐'}]
* @property {String Number} current 指定哪个tab为激活状态默认0
* @property {String Number} height 导航栏的高度单位rpx默认80
* @property {String Number} font-size tab文字大小单位rpx默认30
* @property {String Number} swiper-width tabs组件外部swiper的宽度默认为屏幕宽度单位rpx默认750
* @property {String} active-color 滑块和激活tab文字的颜色默认#2979ff
* @property {String} inactive-color tabs文字颜色默认#303133
* @property {String Number} bar-width 滑块宽度单位rpx默认40
* @property {String Number} bar-height 滑块高度单位rpx默认6
* @property {Object} bar-style 底部滑块的样式对象形式
* @property {Object} active-item-style 活动tabs item的样式对象形式
* @property {Boolean} show-bar 是否显示底部的滑块默认true
* @property {String Number} gutter 单个tab标签的左右内边距之和单位rpx默认40
* @property {String} bg-color tabs导航栏的背景颜色默认#ffffff
* @property {String} name 组件内部读取的list参数中的属性名见官网说明默认name
* @property {String} count 组件内部读取的list参数中的属性名badge徽标数同name属性的使用见官网说明默认count
* @property {Array} offset 设置badge徽标数的位置偏移格式为 [x, y]也即设置的为top和right的值单位rpx默认[5, 20]
* @property {Boolean} bold 激活选项的字体是否加粗默认true
* @event {Function} change 点击标签时触发
* @example <u-tabs-swiper ref="tabs" :list="list" :is-scroll="false"></u-tabs-swiper>
*/
export default {
name: "u-tabs-swiper",
props: {
// 23使flextab
isScroll: {
type: Boolean,
default: true
},
//
list: {
type: Array,
default () {
return [];
}
},
// tab
current: {
type: [Number, String],
default: 0
},
// rpx
height: {
type: [Number, String],
default: 80
},
// rpx
fontSize: {
type: [Number, String],
default: 30
},
// , s
// duration: {
// type: [Number, String],
// default: 0.5
// },
swiperWidth: {
//line3, swiper, rpx
type: [String, Number],
default: 750
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#303133'
},
// barrpx
barWidth: {
type: [Number, String],
default: 40
},
// bar
barHeight: {
type: [Number, String],
default: 6
},
// tabrpx
gutter: {
type: [Number, String],
default: 40
},
// z-index
zIndex: {
type: [Number, String],
default: 1
},
//
bgColor: {
type: String,
default: '#ffffff'
},
//
autoCenterMode: {
type: String,
default: 'window'
},
// (tab)
name: {
type: String,
default: 'name'
},
// ()
count: {
type: String,
default: 'count'
},
//
offset: {
type: Array,
default: () => {
return [5, 20]
}
},
// tab
bold: {
type: Boolean,
default: true
},
// tab item
activeItemStyle: {
type: Object,
default() {
return {}
}
},
//
showBar: {
type: Boolean,
default: true
},
//
barStyle: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
scrollLeft: 0, // scroll-view
tabQueryInfo: [], // tab
windowWidth: 0, // px
//scrollBarLeft: 0, // bartranslateX()
animationFinishCurrent: this.current,
componentsWidth: 0,
line3AddDx: 0,
line3Dx: 0,
preId,
sW: 0,
tabsInfo: [],
colorGradientArr: [],
colorStep: 100 //
};
},
computed: {
// current
getCurrent() {
const current = Number(this.current);
//
if (current > this.getTabs.length - 1) {
return this.getTabs.length - 1;
}
if (current < 0) return 0;
return current;
},
getTabs() {
return this.list;
},
//
scrollBarLeft() {
return Number(this.line3Dx) + Number(this.line3AddDx);
},
// px
barWidthPx() {
return uni.upx2px(this.barWidth);
},
// tab
tabItemStyle() {
return (index) => {
let style = {
height: this.height + 'rpx',
lineHeight: this.height + 'rpx',
padding: `0 ${this.gutter / 2}rpx`,
color: this.tabsInfo.length > 0 ? (this.tabsInfo[index] ? this.tabsInfo[index].color : this.activeColor) : this.inactiveColor,
fontSize: this.fontSize + 'rpx',
zIndex: this.zIndex + 2,
fontWeight: (index == this.getCurrent && this.bold) ? 'bold' : 'normal'
};
if(index == this.getCurrent) {
// tab item
style = Object.assign(style, this.activeItemStyle);
}
return style;
}
},
//
tabBarStyle() {
let style = {
width: this.barWidthPx + 'px',
height: this.barHeight + 'rpx',
borderRadius: '100px',
backgroundColor: this.activeColor,
left: this.scrollBarLeft + 'px'
};
return Object.assign(style, this.barStyle);
}
},
watch: {
current(n, o) {
this.change(n);
this.setFinishCurrent(n);
},
list() {
this.$nextTick(() => {
this.init();
})
}
},
mounted() {
this.init();
},
methods: {
async init() {
this.countPx();
await this.getTabsInfo();
this.countLine3Dx();
this.getQuery(() => {
this.setScrollViewToCenter();
});
//
this.colorGradientArr = color.colorGradient(this.inactiveColor, this.activeColor, this.colorStep);
},
// tab
getTabsInfo() {
return new Promise((resolve, reject) => {
let view = uni.createSelectorQuery().in(this);
for (let i = 0; i < this.list.length; i++) {
view.select('.' + preId + i).boundingClientRect();
}
view.exec(res => {
const arr = [];
for (let i = 0; i < res.length; i++) {
// tab
res[i].color = this.inactiveColor;
// tabactiveColor
if (i == this.getCurrent) res[i].color = this.activeColor;
arr.push(res[i]);
}
this.tabsInfo = arr;
resolve();
});
})
},
// swiper
countLine3Dx() {
const tab = this.tabsInfo[this.animationFinishCurrent];
// tab
if (tab) this.line3Dx = tab.left + tab.width / 2 - this.barWidthPx / 2 - this.tabsInfo[0].left;
},
countPx() {
// swiperrpxpxdxpx
this.sW = uni.upx2px(Number(this.swiperWidth));
},
emit(index) {
this.$emit('change', index);
},
change() {
this.setScrollViewToCenter();
},
getQuery(cb) {
try {
let view = uni.createSelectorQuery().in(this).select('.u-tabs');
view.fields({
size: true
},
data => {
if (data) {
this.componentsWidth = data.width;
if (cb && typeof cb === 'function') cb(data);
} else {
this.getQuery(cb);
}
}
).exec();
} catch (e) {
this.componentsWidth = windowWidth;
}
},
// tab
setScrollViewToCenter() {
let tab;
tab = this.tabsInfo[this.animationFinishCurrent];
if (tab) {
let tabCenter = tab.left + tab.width / 2;
let fatherWidth;
// tabtab
if (this.autoCenterMode === 'window') {
fatherWidth = windowWidth;
} else {
fatherWidth = this.componentsWidth;
}
this.scrollLeft = tabCenter - fatherWidth / 2;
}
},
setDx(dx) {
let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1;
//
nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex;
nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex;
const tab = this.tabsInfo[nextTabIndex];
// tabx
let nowTab = this.tabsInfo[this.animationFinishCurrent];
let nowTabX = nowTab.left + nowTab.width / 2;
// tab
let nextTab = this.tabsInfo[nextTabIndex];
let nextTabX = nextTab.left + nextTab.width / 2;
// tabtabtab
let distanceX = Math.abs(nextTabX - nowTabX);
this.line3AddDx = (dx / this.sW) * distanceX;
this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx);
},
// tab
setTabColor(nowTabIndex, nextTabIndex, dx) {
let colorIndex = Math.abs(Math.ceil((dx / this.sW) * 100));
let colorLength = this.colorGradientArr.length;
//
colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex;
// tab
this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex];
// tab
this.tabsInfo[nowTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex];
},
// swiper
setFinishCurrent(current) {
// tabtab
this.tabsInfo.map((val, index) => {
if (current == index) val.color = this.activeColor;
else val.color = this.inactiveColor;
return val;
});
this.line3AddDx = 0;
this.animationFinishCurrent = current;
this.countLine3Dx();
}
}
};
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
view,
scroll-view {
box-sizing: border-box;
}
.u-tabs {
width: 100%;
transition-property: background-color, color;
}
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
/* #ifdef H5 */
// 穿H5scroll-view
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.u-scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.u-tabs-scroll-box {
position: relative;
}
.u-tabs-scorll-flex {
@include vue-flex;
justify-content: space-between;
}
.u-tabs-scorll-flex .u-tabs-item {
flex: 1;
}
.u-tabs-item {
position: relative;
display: inline-block;
text-align: center;
transition-property: background-color, color, font-weight;
}
.content {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.boxStyle {
pointer-events: none;
position: absolute;
transition-property: all;
}
.boxStyle2 {
pointer-events: none;
position: absolute;
bottom: 0;
transition-property: all;
transform: translateY(-100%);
}
.itemBackgroundBox {
pointer-events: none;
position: absolute;
top: 0;
transition-property: left, background-color;
@include vue-flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.itemBackground {
height: 100%;
width: 100%;
transition-property: all;
}
.u-scroll-bar {
position: absolute;
bottom: 4rpx;
}
</style>

View File

@ -0,0 +1,369 @@
<template>
<view class="u-tabs" :style="{
background: bgColor
}">
<!-- $u.getRect()对组件根节点无效因为写了.in(this)故这里获取内层接点尺寸 -->
<view>
<scroll-view scroll-x class="u-scroll-view" :scroll-left="scrollLeft" scroll-with-animation>
<view class="u-scroll-box" :id="id" :class="{'u-tabs-scorll-flex': !isScroll}">
<view class="u-tab-item u-line-1" :id="'u-tab-item-' + index" v-for="(item, index) in list" :key="index" @tap="clickTab(index)"
:style="[tabItemStyle(index)]">
<u-badge :count="item[count] || item['count'] || 0" :offset="offset" size="mini"></u-badge>
{{ item[name] || item['name']}}
</view>
<view v-if="showBar" class="u-tab-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
/**
* tabs 标签
* @description 该组件是一个tabs标签组件在标签多的时候可以配置为左右滑动标签少的时候可以禁止滑动 该组件的一个特点是配置为滚动模式时激活的tab会自动移动到组件的中间位置
* @tutorial https://www.uviewui.com/components/tabs.html
* @property {Boolean} is-scroll tabs是否可以左右拖动默认true
* @property {Array} list 标签数组元素为对象[{name: '推荐'}]
* @property {String Number} current 指定哪个tab为激活状态默认0
* @property {String Number} height 导航栏的高度单位rpx默认80
* @property {String Number} font-size tab文字大小单位rpx默认30
* @property {String Number} duration 滑块移动一次所需的时间单位秒默认0.5
* @property {String} active-color 滑块和激活tab文字的颜色默认#2979ff
* @property {String} inactive-color tabs文字颜色默认#303133
* @property {String Number} bar-width 滑块宽度单位rpx默认40
* @property {Object} active-item-style 活动tabs item的样式对象形式
* @property {Object} bar-style 底部滑块的样式对象形式
* @property {Boolean} show-bar 是否显示底部的滑块默认true
* @property {String Number} bar-height 滑块高度单位rpx默认6
* @property {String Number} item-width 标签的宽度默认auto
* @property {String Number} gutter 单个tab标签的左右内边距之和单位rpx默认40
* @property {String} bg-color tabs导航栏的背景颜色默认#ffffff
* @property {String} name 组件内部读取的list参数中的属性名tab名称见官网说明默认name
* @property {String} count 组件内部读取的list参数中的属性名badge徽标数同name属性的使用见官网说明默认count
* @property {Array} offset 设置badge徽标数的位置偏移格式为 [x, y]也即设置的为top和right的值单位rpx默认[5, 20]
* @property {Boolean} bold 激活选项的字体是否加粗默认true
* @event {Function} change 点击标签时触发
* @example <u-tabs ref="tabs" :list="list" :is-scroll="false"></u-tabs>
*/
export default {
name: "u-tabs",
props: {
// 23使flextab
isScroll: {
type: Boolean,
default: true
},
//
list: {
type: Array,
default () {
return [];
}
},
// tab
current: {
type: [Number, String],
default: 0
},
//
height: {
type: [String, Number],
default: 80
},
//
fontSize: {
type: [String, Number],
default: 30
},
// , ms
duration: {
type: [String, Number],
default: 0.5
},
//
activeColor: {
type: String,
default: '#2979ff'
},
//
inactiveColor: {
type: String,
default: '#303133'
},
// barrpx
barWidth: {
type: [String, Number],
default: 40
},
// bar
barHeight: {
type: [String, Number],
default: 6
},
// tab
gutter: {
type: [String, Number],
default: 30
},
//
bgColor: {
type: String,
default: '#ffffff'
},
// (tab)
name: {
type: String,
default: 'name'
},
// ()
count: {
type: String,
default: 'count'
},
//
offset: {
type: Array,
default: () => {
return [5, 20]
}
},
// tab
bold: {
type: Boolean,
default: true
},
// tab item
activeItemStyle: {
type: Object,
default() {
return {}
}
},
//
showBar: {
type: Boolean,
default: true
},
//
barStyle: {
type: Object,
default() {
return {}
}
},
//
itemWidth: {
type: [Number, String],
default: 'auto'
}
},
data() {
return {
scrollLeft: 0, // scroll-view
tabQueryInfo: [], // tab
componentWidth: 0, // px
scrollBarLeft: 0, // bartranslateX()
parentLeft: 0, // (tabs)
id: this.$u.guid(), // id
currentIndex: this.current,
barFirstTimeMove: true, // ()
};
},
watch: {
// tabtab使
// applist
list(n, o) {
// list
if(n.length !== o.length) this.currentIndex = 0;
// $nextTicktabtab
this.$nextTick(() => {
this.init();
});
},
current: {
immediate: true,
handler(nVal, oVal) {
//
this.$nextTick(() => {
this.currentIndex = nVal;
this.scrollByIndex();
});
}
},
},
computed: {
// bar
tabBarStyle() {
let style = {
width: this.barWidth + 'rpx',
transform: `translate(${this.scrollBarLeft}px, -100%)`,
//
'transition-duration': `${this.barFirstTimeMove ? 0 : this.duration }s`,
'background-color': this.activeColor,
height: this.barHeight + 'rpx',
opacity: this.barFirstTimeMove ? 0 : 1,
//
'border-radius': `${this.barHeight / 2}px`
};
Object.assign(style, this.barStyle);
return style;
},
// tab
tabItemStyle() {
return (index) => {
let style = {
height: this.height + 'rpx',
'line-height': this.height + 'rpx',
'font-size': this.fontSize + 'rpx',
'transition-duration': `${this.duration}s`,
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
flex: this.isScroll ? 'auto' : '1',
width: this.$u.addUnit(this.itemWidth)
};
//
if (index == this.currentIndex && this.bold) style.fontWeight = 'bold';
if (index == this.currentIndex) {
style.color = this.activeColor;
// tab item
style = Object.assign(style, this.activeItemStyle);
} else {
style.color = this.inactiveColor;
}
return style;
}
}
},
methods: {
// init便
async init() {
// tabs
let tabRect = await this.$uGetRect('#' + this.id);
// tabs
this.parentLeft = tabRect.left;
// tabs
this.componentWidth = tabRect.width;
this.getTabRect();
},
// tab
clickTab(index) {
// tab
if(index == this.currentIndex) return ;
//
this.$emit('change', index);
},
// tab
getTabRect() {
//
let query = uni.createSelectorQuery().in(this);
// tab使exec()
for (let i = 0; i < this.list.length; i++) {
// sizerect
query.select(`#u-tab-item-${i}`).fields({
size: true,
rect: true
});
}
//
query.exec(
function(res) {
this.tabQueryInfo = res;
// bar
this.scrollByIndex();
}.bind(this)
);
},
// scroll-viewtab
scrollByIndex() {
// tabtabwidthleft()
let tabInfo = this.tabQueryInfo[this.currentIndex];
if (!tabInfo) return;
// tab
let tabWidth = tabInfo.width;
// itemtabsitemlefttabsleft
let offsetLeft = tabInfo.left - this.parentLeft;
// tabs-itemscroll-view
let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2;
this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft;
// item
let left = tabInfo.left + tabInfo.width / 2 - this.parentLeft;
// item
this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2;
// barFirstTimeMovetruefalse
// scrollBarLeftcomputed
if(this.barFirstTimeMove == true) {
setTimeout(() => {
this.barFirstTimeMove = false;
}, 100)
}
}
},
mounted() {
this.init();
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
view,
scroll-view {
box-sizing: border-box;
}
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.u-scroll-box {
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
/* #ifdef H5 */
// 穿H5scroll-view
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.u-scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.u-tab-item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
.u-tab-bar {
position: absolute;
bottom: 0;
}
.u-tabs-scorll-flex {
@include vue-flex;
justify-content: space-between;
}
</style>

View File

@ -0,0 +1,294 @@
<template>
<view v-if="show" :class="[
disabled ? 'u-disabled' : '',
'u-size-' + size,
'u-shape-' + shape,
'u-mode-' + mode + '-' + type
]"
class="u-tag" :style="[customStyle]" @tap="clickTag">
{{text}}
<view class="u-icon-wrap" @tap.stop>
<u-icon @click="close" size="22" v-if="closeable" :color="closeIconColor"
name="close" class="u-close-icon" :style="[iconStyle]"></u-icon>
</view>
</view>
</template>
<script>
/**
* tag 提示
* @description 该组件一般用于标记和选择
* @tutorial https://www.uviewui.com/components/tag.html
* @property {String} type 主题类型默认primary
* @property {String} size 标签大小默认default
* @property {String} shape 标签形状默认square
* @property {String} text 标签的文字内容
* @property {String} bg-color 自定义标签的背景颜色
* @property {String} border-color 标签的边框颜色
* @property {String} close-color 关闭按钮的颜色
* @property {String Number} index 点击标签时会通过click事件返回该值
* @property {String} mode 模式选择见官网说明默认light
* @property {Boolean} closeable 是否可关闭设置为true文字右边会出现一个关闭图标默认false
* @property {Boolean} show 标签显示与否默认true
* @event {Function} click 点击标签触发
* @event {Function} close closeable为true时点击标签关闭按钮触发
* @example <u-tag text="雪月夜" type="success" />
*/
export default {
name: 'u-tag',
//
props: {
// infoprimarysuccesswarningerror
type: {
type: String,
default: 'primary'
},
disabled: {
type: [Boolean, String],
default: false
},
// defaultmini
size: {
type: String,
default: 'default'
},
// tagcircle, squarecircleLeftcircleRight
shape: {
type: String,
default: 'square'
},
//
text: {
type: [String, Number],
default: ''
},
//
bgColor: {
type: String,
default: ''
},
//
color: {
type: String,
default: ''
},
//
borderColor: {
type: String,
default: ''
},
//
closeColor: {
type: String,
default: ''
},
//
index: {
type: [Number, String],
default: ''
},
// dark|light|plain
mode: {
type: String,
default: 'light'
},
//
closeable: {
type: Boolean,
default: false
},
//
show: {
type: Boolean,
default: true
}
},
data() {
return {
}
},
computed: {
customStyle() {
let style = {};
// type
if(this.color) style.color = this.color;
// tagtype
if(this.bgColor) style.backgroundColor = this.bgColor;
// tagborderColor使color
if(this.mode == 'plain' && this.color && !this.borderColor) style.borderColor = this.color;
else style.borderColor = this.borderColor;
return style;
},
iconStyle() {
if(!this.closeable) return ;
let style = {};
if(this.size == 'mini') style.fontSize = '20rpx';
else style.fontSize = '22rpx';
if(this.mode == 'plain' || this.mode == 'light') style.color = this.type;
else if(this.mode == 'dark') style.color = "#ffffff";
if(this.closeColor) style.color = this.closeColor;
return style;
},
//
closeIconColor() {
//
// dark
// type
let color = '';
if(this.closeColor) return this.closeColor;
else if(this.color) return this.color;
else if(this.mode == 'dark') return '#ffffff';
else return this.type;
}
},
methods: {
//
clickTag() {
// disabled
if(this.disabled) return ;
this.$emit('click', this.index);
},
//
close() {
this.$emit('close', this.index);
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-tag {
box-sizing: border-box;
align-items: center;
border-radius: 6rpx;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
line-height: 1;
}
.u-size-default {
font-size: 22rpx;
padding: 12rpx 22rpx;
}
.u-size-mini {
font-size: 20rpx;
padding: 6rpx 12rpx;
}
.u-mode-light-primary {
background-color: $u-type-primary-light;
color: $u-type-primary;
border: 1px solid $u-type-primary-disabled;
}
.u-mode-light-success {
background-color: $u-type-success-light;
color: $u-type-success;
border: 1px solid $u-type-success-disabled;
}
.u-mode-light-error {
background-color: $u-type-error-light;
color: $u-type-error;
border: 1px solid $u-type-error-disabled;
}
.u-mode-light-warning {
background-color: $u-type-warning-light;
color: $u-type-warning;
border: 1px solid $u-type-warning-disabled;
}
.u-mode-light-info {
background-color: $u-type-info-light;
color: $u-type-info;
border: 1px solid $u-type-info-disabled;
}
.u-mode-dark-primary {
background-color: $u-type-primary;
color: #FFFFFF;
}
.u-mode-dark-success {
background-color: $u-type-success;
color: #FFFFFF;
}
.u-mode-dark-error {
background-color: $u-type-error;
color: #FFFFFF;
}
.u-mode-dark-warning {
background-color: $u-type-warning;
color: #FFFFFF;
}
.u-mode-dark-info {
background-color: $u-type-info;
color: #FFFFFF;
}
.u-mode-plain-primary {
background-color: #FFFFFF;
color: $u-type-primary;
border: 1px solid $u-type-primary;
}
.u-mode-plain-success {
background-color: #FFFFFF;
color: $u-type-success;
border: 1px solid $u-type-success;
}
.u-mode-plain-error {
background-color: #FFFFFF;
color: $u-type-error;
border: 1px solid $u-type-error;
}
.u-mode-plain-warning {
background-color: #FFFFFF;
color: $u-type-warning;
border: 1px solid $u-type-warning;
}
.u-mode-plain-info {
background-color: #FFFFFF;
color: $u-type-info;
border: 1px solid $u-type-info;
}
.u-disabled {
opacity: 0.55;
}
.u-shape-circle {
border-radius: 100rpx;
}
.u-shape-circleRight {
border-radius: 0 100rpx 100rpx 0;
}
.u-shape-circleLeft {
border-radius: 100rpx 0 0 100rpx;
}
.u-close-icon {
margin-left: 14rpx;
font-size: 22rpx;
color: $u-type-success;
}
.u-icon-wrap {
display: inline-flex;
transform: scale(0.86);
}
</style>

View File

@ -0,0 +1,66 @@
<template>
<view class="u-td" :style="[tdStyle]">
<slot></slot>
</view>
</template>
<script>
/**
* td td单元格
* @description 表格组件一般用于展示大量结构化数据的场景搭配u-table使用
* @tutorial https://www.uviewui.com/components/table.html#td-props
* @property {String Number} width 单元格宽度百分比或者具体带单位的值如30% 200rpx等一般使用百分比单元格宽度默认为均分tr的长度默认auto
* @example <u-td>二年级</u-td>
*/
export default {
name: "u-td",
props: {
// 30% 200rpx使
width: {
type: [Number, String],
default: 'auto'
}
},
data() {
return {
tdStyle: {
}
}
},
created() {
this.parent = false;
},
mounted() {
this.parent = this.$u.$parent.call(this, 'u-table');
if (this.parent) {
//
let style = {};
if (this.width != "auto") style.flex = `0 0 ${this.width}`;
style.textAlign = this.parent.align;
style.fontSize = this.parent.fontSize + 'rpx';
style.padding = this.parent.padding;
style.borderBottom = `solid 1px ${this.parent.borderColor}`;
style.borderRight = `solid 1px ${this.parent.borderColor}`;
style.color = this.parent.color;
this.tdStyle = style;
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-td {
@include vue-flex;
flex-direction: column;
flex: 1;
justify-content: center;
font-size: 28rpx;
color: $u-content-color;
align-self: stretch;
box-sizing: border-box;
height: 100%;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<view class="u-th" :style="[thStyle]">
<slot></slot>
</view>
</template>
<script>
/**
* th th单元格
* @description 表格组件一般用于展示大量结构化数据的场景搭配u-table使用
* @tutorial https://www.uviewui.com/components/table.html#td-props
* @property {String Number} width 标题单元格宽度百分比或者具体带单位的值如30%200rpx等一般使用百分比单元格宽度默认为均分tr的长度
* @example 暂无示例
*/
export default {
name: "u-th",
props: {
// 30% 200rpx使
width: {
type: [Number, String],
default: ''
}
},
data() {
return {
thStyle: {}
}
},
created() {
this.parent = false;
},
mounted() {
this.parent = this.$u.$parent.call(this, 'u-table');
if (this.parent) {
//
let style = {};
if (this.width) style.flex = `0 0 ${this.width}`;
style.textAlign = this.parent.align;
style.padding = this.parent.padding;
style.borderBottom = `solid 1px ${this.parent.borderColor}`;
style.borderRight = `solid 1px ${this.parent.borderColor}`;
Object.assign(style, this.parent.thStyle);
this.thStyle = style;
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-th {
@include vue-flex;
flex-direction: column;
flex: 1;
justify-content: center;
font-size: 28rpx;
color: $u-main-color;
font-weight: bold;
background-color: rgb(245, 246, 248);
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<view class="u-time-axis-item">
<slot name="content" />
<view class="u-time-axis-node" :style="[nodeStyle]">
<slot name="node">
<view class="u-dot">
</view>
</slot>
</view>
</view>
</template>
<script>
/**
* timeLineItem 时间轴Item
* @description 时间轴组件一般用于物流信息展示各种跟时间相关的记录等场景(搭配u-time-line使用)
* @tutorial https://www.uviewui.com/components/timeLine.html
* @property {String} bg-color 左边节点的背景颜色一般通过slot内容自定义背景颜色即可默认#ffffff
* @property {String Number} node-top 节点左边图标绝对定位的top值单位rpx
* @example <u-time-line-item node-top="2">...</u-time-line-item>
*/
export default {
name: "u-time-line-item",
props: {
//
bgColor: {
type: String,
default: "#ffffff"
},
// top
nodeTop: {
type: [String, Number],
default: ""
}
},
data() {
return {
}
},
computed: {
nodeStyle() {
let style = {
backgroundColor: this.bgColor,
};
if (this.nodeTop != "") style.top = this.nodeTop + 'rpx';
return style;
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-time-axis-item {
@include vue-flex;
flex-direction: column;
width: 100%;
position: relative;
margin-bottom: 32rpx;
}
.u-time-axis-node {
position: absolute;
top: 12rpx;
left: -40rpx;
transform-origin: 0;
transform: translateX(-50%);
@include vue-flex;
align-items: center;
justify-content: center;
z-index: 1;
font-size: 24rpx;
}
.u-dot {
height: 16rpx;
width: 16rpx;
border-radius: 100rpx;
background: #ddd;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<view class="u-time-axis">
<slot />
</view>
</template>
<script>
/**
* timeLine 时间轴
* @description 时间轴组件一般用于物流信息展示各种跟时间相关的记录等场景
* @tutorial https://www.uviewui.com/components/timeLine.html
* @example <u-time-line></u-time-line>
*/
export default {
name: "u-time-line",
data() {
return {
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-time-axis {
padding-left: 40rpx;
position: relative;
}
.u-time-axis::before {
content: " ";
position: absolute;
left: 0;
top: 12rpx;
width: 1px;
bottom: 0;
border-left: 1px solid #ddd;
transform-origin: 0 0;
transform: scaleX(0.5);
}
</style>

View File

@ -0,0 +1,220 @@
<template>
<view class="u-toast" :class="[isShow ? 'u-show' : '', 'u-type-' + tmpConfig.type, 'u-position-' + tmpConfig.position]" :style="{
zIndex: uZIndex
}">
<view class="u-icon-wrap">
<u-icon v-if="tmpConfig.icon" class="u-icon" :name="iconName" :size="30" :color="tmpConfig.type"></u-icon>
</view>
<text class="u-text">{{tmpConfig.title}}</text>
</view>
</template>
<script>
/**
* toast 消息提示
* @description 此组件表现形式类似uni的uni.showToastAPI但也有不同的地方
* @tutorial https://www.uviewui.com/components/toast.html
* @property {String} z-index toast展示时的z-index值
* @event {Function} show 显示toast如需一进入页面就显示toast请在onReady生命周期调用
* @example <u-toast ref="uToast" />
*/
export default {
name: "u-toast",
props: {
// z-index
zIndex: {
type: [Number, String],
default: ''
},
},
data() {
return {
isShow: false,
timer: null, //
config: {
params: {}, // URL
title: '', //
type: '', // primarysuccesserrorwarningblack
duration: 2000, //
isTab: false, // tab
url: '', // toastback
icon: true, //
position: 'center', // toast
callback: null, //
back: false, // toast
},
tmpConfig: {}, //
};
},
computed: {
iconName() {
// nonetypeerror|warning|succes|info
if (['error', 'warning', 'success', 'info'].indexOf(this.tmpConfig.type) >= 0 && this.tmpConfig.icon) {
let icon = this.$u.type2icon(this.tmpConfig.type);
return icon;
}
},
uZIndex() {
// toastz-index使
return this.isShow ? (this.zIndex ? this.zIndex : this.$u.zIndex.toast) : '999999';
}
},
methods: {
// toastthis.$refs.xxx.show(options)
show(options) {
// this.configu-toast
this.tmpConfig = this.$u.deepMerge(this.config, options);
if (this.timer) {
//
clearTimeout(this.timer);
this.timer = null;
}
this.isShow = true;
this.timer = setTimeout(() => {
// toast
this.isShow = false;
clearTimeout(this.timer);
this.timer = null;
// callback
typeof(this.tmpConfig.callback) === 'function' && this.tmpConfig.callback();
this.timeEnd();
}, this.tmpConfig.duration);
},
// toastthis.$refs.xxx.hide()
hide() {
this.isShow = false;
if (this.timer) {
//
clearTimeout(this.timer);
this.timer = null;
}
},
//
timeEnd() {
// urlisTabtruefalse
if (this.tmpConfig.url) {
// url"/"uni"/"
if (this.tmpConfig.url[0] != '/') this.tmpConfig.url = '/' + this.tmpConfig.url;
//
if (Object.keys(this.tmpConfig.params).length) {
// url
// 使"/","?","="/page/index/index?name=mary"
// params"?"
let query = '';
if (/.*\/.*\?.*=.*/.test(this.tmpConfig.url)) {
// objectget
query = this.$u.queryParams(this.tmpConfig.params, false);
this.tmpConfig.url = this.tmpConfig.url + "&" + query;
} else {
query = this.$u.queryParams(this.tmpConfig.params);
this.tmpConfig.url += query;
}
}
// tab使uni.switchTab
if (this.tmpConfig.isTab) {
uni.switchTab({
url: this.tmpConfig.url
});
} else {
uni.navigateTo({
url: this.tmpConfig.url
});
}
} else if(this.tmpConfig.back) {
// 退
this.$u.route({
type: 'back'
})
}
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-toast {
position: fixed;
z-index: -1;
transition: opacity 0.3s;
text-align: center;
color: #fff;
border-radius: 8rpx;
background: #585858;
@include vue-flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
opacity: 0;
pointer-events: none;
padding: 18rpx 40rpx;
}
.u-toast.u-show {
opacity: 1;
}
.u-icon {
margin-right: 10rpx;
@include vue-flex;
align-items: center;
line-height: normal;
}
.u-position-center {
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
/* #ifndef APP-NVUE */
max-width: 70%;
/* #endif */
}
.u-position-top {
left: 50%;
top: 20%;
transform: translate(-50%,-50%);
}
.u-position-bottom {
left: 50%;
bottom: 20%;
transform: translate(-50%,-50%);
}
.u-type-primary {
color: $u-type-primary;
background-color: $u-type-primary-light;
border: 1px solid rgb(215, 234, 254);
}
.u-type-success {
color: $u-type-success;
background-color: $u-type-success-light;
border: 1px solid #BEF5C8;
}
.u-type-error {
color: $u-type-error;
background-color: $u-type-error-light;
border: 1px solid #fde2e2;
}
.u-type-warning {
color: $u-type-warning;
background-color: $u-type-warning-light;
border: 1px solid #faecd8;
}
.u-type-info {
color: $u-type-info;
background-color: $u-type-info-light;
border: 1px solid #ebeef5;
}
.u-type-default {
color: #fff;
background-color: #585858;
}
</style>

View File

@ -0,0 +1,121 @@
<template>
<view class="u-tips" :class="['u-' + type, isShow ? 'u-tip-show' : '']" :style="{
top: navbarHeight + 'px',
zIndex: uZIndex
}">{{ title }}</view>
</template>
<script>
/**
* topTips 顶部提示
* @description 该组件一般用于页面顶部向下滑出一个提示尔后自动收起的场景
* @tutorial https://www.uviewui.com/components/topTips.html
* @property {String Number} navbar-height 导航栏高度(包含状态栏高度在内)单位PX
* @property {String Number} z-index z-index值默认975
* @example <u-top-tips ref="uTips"></u-top-tips>
*/
export default {
name: "u-top-tips",
props: {
//
navbarHeight: {
type: [Number, String],
// #ifndef H5
default: 0,
// #endif
// #ifdef H5
default: 44,
// #endif
},
// z-index
zIndex: {
type: [Number, String],
default: ''
}
},
data() {
return {
timer: null, //
isShow: false, //
title: '', //
type: 'primary', // primarysuccesserrorwarninginfo
duration: 2000, //
};
},
computed: {
uZIndex() {
return this.zIndex ? this.zIndex : this.$u.zIndex.topTips;
}
},
methods: {
show(config = {}) {
//
clearTimeout(this.timer);
// (type)
if (config.duration) this.duration = config.duration;
if (config.type) this.type = config.type;
this.title = config.title;
this.isShow = true;
//
this.timer = setTimeout(() => {
this.isShow = false;
clearTimeout(this.timer);
this.timer = null;
}, this.duration);
}
}
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
view {
box-sizing: border-box;
}
//
.u-tips {
width: 100%;
position: fixed;
z-index: 1;
padding: 20rpx 30rpx;
color: #FFFFFF;
font-size: 28rpx;
left: 0;
right: 0;
@include vue-flex;
align-items: center;
justify-content: center;
opacity: 0;
// translateY(-100%)Y(h5)(app)
transform: translateY(-100%);
transition: all 0.35s linear;
}
.u-tip-show {
transform: translateY(0);
opacity: 1;
z-index: 99;
}
.u-primary {
background: $u-type-primary;
}
.u-success {
background: $u-type-success;
}
.u-warning {
background: $u-type-warning;
}
.u-error {
background: $u-type-error;
}
.u-info {
background: $u-type-info;
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<view class="u-tr">
<slot></slot>
</view>
</template>
<script>
/**
* tr 表格行标签
* @description 表格组件一般用于展示大量结构化数据的场景搭配<u-table>使用
* @tutorial https://www.uviewui.com/components/table.html
* @example <u-tr></u-tr>
*/
export default {
name: "u-tr",
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/style.components.scss";
.u-tr {
@include vue-flex;
}
</style>

Some files were not shown because too many files have changed in this diff Show More