Browse Source

first

master
j1ack 2 years ago
commit
00b864b5ef
  1. BIN
      .DS_Store
  2. 5
      .gitignore
  3. 123
      App.vue
  4. BIN
      __MACOSX/components/._.DS_Store
  5. BIN
      __MACOSX/components/jm-search/._.DS_Store
  6. BIN
      __MACOSX/components/jm-search/._jm-search.vue
  7. 14
      common/SystemConfiguration.js
  8. 6
      common/errorCode.js
  9. 57
      common/request.js
  10. 27
      common/storage.js
  11. 29
      common/uniapp-axios-adapter.js
  12. 82
      common/with-authentication.js
  13. 40
      common/with-repeat-guard.js
  14. 66
      component/GeneralButton/index.vue
  15. 141
      component/header.vue
  16. 127
      global.scss
  17. 168
      index.html
  18. 51
      main.js
  19. 96
      manifest.json
  20. 7851
      package-lock.json
  21. 12
      package.json
  22. 50
      pages.json
  23. 217
      pages/index/index.vue
  24. BIN
      static/images/Union.png
  25. BIN
      static/images/com_icon_srank.452821d11.png
  26. BIN
      static/images/com_icon_yaoqing.c8e1575f1.png
  27. BIN
      static/images/community_bg_h51.png
  28. BIN
      static/images/invite_table_title.e3146df81.png
  29. BIN
      static/images/logo.png
  30. 26
      store/index.js
  31. 78
      uni.scss
  32. 150
      utils/atom.js
  33. 24
      utils/constant.js
  34. 204
      utils/index.js
  35. 10
      utils/language/config.js
  36. 18
      utils/language/en_US.js
  37. 19
      utils/language/zh_TW.js
  38. 131
      utils/mixins/list-page.mixin.js
  39. 36
      utils/noScale.js
  40. 19
      utils/web3x/ippt-abi.json
  41. 439
      utils/web3x/usdt-abi.json
  42. 418
      utils/web3x/web3x.js
  43. 21
      uview-ui/LICENSE
  44. 106
      uview-ui/README.md
  45. 190
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  46. 256
      uview-ui/components/u-alert-tips/u-alert-tips.vue
  47. 290
      uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  48. 1265
      uview-ui/components/u-avatar-cropper/weCropper.js
  49. 244
      uview-ui/components/u-avatar/u-avatar.vue
  50. 153
      uview-ui/components/u-back-top/u-back-top.vue
  51. 216
      uview-ui/components/u-badge/u-badge.vue
  52. 596
      uview-ui/components/u-button/u-button.vue
  53. 640
      uview-ui/components/u-calendar/u-calendar.vue
  54. 257
      uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  55. 299
      uview-ui/components/u-card/u-card.vue
  56. 70
      uview-ui/components/u-cell-group/u-cell-group.vue
  57. 316
      uview-ui/components/u-cell-item/u-cell-item.vue
  58. 123
      uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  59. 284
      uview-ui/components/u-checkbox/u-checkbox.vue
  60. 220
      uview-ui/components/u-circle-progress/u-circle-progress.vue
  61. 156
      uview-ui/components/u-col/u-col.vue
  62. 204
      uview-ui/components/u-collapse-item/u-collapse-item.vue
  63. 99
      uview-ui/components/u-collapse/u-collapse.vue
  64. 237
      uview-ui/components/u-column-notice/u-column-notice.vue
  65. 318
      uview-ui/components/u-count-down/u-count-down.vue
  66. 241
      uview-ui/components/u-count-to/u-count-to.vue
  67. 153
      uview-ui/components/u-divider/u-divider.vue
  68. 132
      uview-ui/components/u-dropdown-item/u-dropdown-item.vue
  69. 298
      uview-ui/components/u-dropdown/u-dropdown.vue
  70. 193
      uview-ui/components/u-empty/u-empty.vue
  71. 384
      uview-ui/components/u-field/u-field.vue
  72. 431
      uview-ui/components/u-form-item/u-form-item.vue
  73. 134
      uview-ui/components/u-form/u-form.vue
  74. 74
      uview-ui/components/u-full-screen/u-full-screen.vue
  75. 54
      uview-ui/components/u-gap/u-gap.vue
  76. 126
      uview-ui/components/u-grid-item/u-grid-item.vue
  77. 108
      uview-ui/components/u-grid/u-grid.vue
  78. 336
      uview-ui/components/u-icon/u-icon.vue
  79. 266
      uview-ui/components/u-image/u-image.vue
  80. 89
      uview-ui/components/u-index-anchor/u-index-anchor.vue
  81. 315
      uview-ui/components/u-index-list/u-index-list.vue
  82. 387
      uview-ui/components/u-input/u-input.vue
  83. 217
      uview-ui/components/u-keyboard/u-keyboard.vue
  84. 244
      uview-ui/components/u-lazy-load/u-lazy-load.vue
  85. 147
      uview-ui/components/u-line-progress/u-line-progress.vue
  86. 84
      uview-ui/components/u-line/u-line.vue
  87. 89
      uview-ui/components/u-link/u-link.vue
  88. 25
      uview-ui/components/u-loading-page/u-loading-page.vue
  89. 103
      uview-ui/components/u-loading/u-loading.vue
  90. 203
      uview-ui/components/u-loadmore/u-loadmore.vue
  91. 123
      uview-ui/components/u-mask/u-mask.vue
  92. 311
      uview-ui/components/u-message-input/u-message-input.vue
  93. 283
      uview-ui/components/u-modal/u-modal.vue
  94. 315
      uview-ui/components/u-navbar/u-navbar.vue
  95. 233
      uview-ui/components/u-no-network/u-no-network.vue
  96. 272
      uview-ui/components/u-notice-bar/u-notice-bar.vue
  97. 371
      uview-ui/components/u-number-box/u-number-box.vue
  98. 158
      uview-ui/components/u-number-keyboard/u-number-keyboard.vue
  99. 100
      uview-ui/components/u-parse/libs/CssHandler.js
  100. 580
      uview-ui/components/u-parse/libs/MpHtmlParser.js

BIN
.DS_Store

Binary file not shown.

5
.gitignore

@ -0,0 +1,5 @@
/node_modules/
/.hbuilderx/
/unpackage/
.idea

123
App.vue

@ -0,0 +1,123 @@
<script>
export default {
onLaunch: function(options) {
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
methods: {
},
}
</script>
<style>
/*每个页面公共css */
</style>
<style lang="scss">
@import "./global";
@import "uview-ui/index.scss";
.timeReduce{
position: absolute;
right: 30rpx;
top: 20rpx;
}
/deep/ .u-empty {
margin-top: 50% !important;
}
.tCenter {
text-align: center
}
html {
// margin-top: 80rpx;
}
/*每个页面公共css */
/* 注意要写在第一行,同时给style标签加入lang="scss"属性 */
::v-deep .u-input__input {
color: #9299AD !important;
}
.f13{
font-size: 26rpx;
}
.f15{
font-size: 30rpx;
}
//
.m20 {
margin-top: 20rpx;
}
.relative {
position: relative;
}
.borBottom {
border-bottom: 2rpx solid #484848;
;
}
.content {
// width: 750rpx;
// background: #fff;
// min-height: 100vh;
// position: relative;
// z-index: 10;
// padding: 0 32rpx;
// padding-bottom: 200rpx;
}
.container {
padding: 0 32rpx
}
.flex {
display: flex;
align-items: center;
justify-content: space-between;
}
.flex2 {
display: flex;
align-items: center;
}
.btnColor {
background: linear-gradient(270.87deg, #FFD464 0.82%, #FF7575 102.54%) !important;
border-radius: 8px;
}
.plain {
border: 1px solid #FFFFFF;
border-radius: 8px;
}
.home_item_top {
margin-top: 40rpx;
}
.title_con image {
width: 36rpx;
height: 36rpx;
margin-left: 16rpx;
}
.disabled {
background: #464648 !important;
color: #999999 !important;
}
.FF9F32 {
color: #FF9F32;
}
</style>

BIN
__MACOSX/components/._.DS_Store

Binary file not shown.

BIN
__MACOSX/components/jm-search/._.DS_Store

Binary file not shown.

BIN
__MACOSX/components/jm-search/._jm-search.vue

Binary file not shown.

14
common/SystemConfiguration.js

@ -0,0 +1,14 @@
let isTest = false;
import constant from "../utils/constant";
// const constant = isTest ? {
// baseUrl: `http://kaka-carddealer-admin.weirui0755.com/prod-api`,
// }:{
// baseUrl: '/prod-api',
// }
// console.log(constant.address)
export default {
constant: {
baseUrl: constant.baseUrl,
},
}

6
common/errorCode.js

@ -0,0 +1,6 @@
export default {
'401': '认证失败,无法访问系统资源',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'default': '系统未知错误,请反馈给管理员'
}

57
common/request.js

@ -0,0 +1,57 @@
import Vue from 'vue'
import axios from 'axios'
import "./uniapp-axios-adapter"
import {withRepeatGuard} from "./with-repeat-guard";
import {tansParams} from "../utils/atom";
import {withAuthentication} from "./with-authentication";
import Constant from "../utils/constant";
const service = axios.create({
withCredentials: false, //表示跨域请求时是否需要使用凭证
crossDomain: true,
baseURL: Constant.baseUrl,
timeout: 160000
})
// withAuthentication(service)
withRepeatGuard(service)
/**
* 请求拦截
*
* Axios 默认配置, 还接受以下配置
* $withCredentials: 是否携带 token, 默认为 true
* $withLoading: 是否显示 loading, 默认 true
* $withRepeatGuard: 是否开启重复请求拦截, 默认 true
*/
service.interceptors.request.use(
config => {
uni.showLoading({
title: 'loading',
mask: true
})
// get请求映射params参数
if (config.method === 'get' && config.params) {
let url = config.url + '?' + tansParams(config.params);
url = url.slice(0, -1);
config.params = {};
config.url = url;
}
return config;
},
);
service.interceptors.response.use(
response => {
uni.hideLoading();
return response;
},
error => {
uni.hideLoading();
return Promise.reject(error);
},
)
export default service

27
common/storage.js

@ -0,0 +1,27 @@
export default {
set (key, value) {
if (key != null && value != null) {
uni.setStorageSync(key, value)
}
},
get (key) {
if (key == null) {
return null
}
return uni.getStorageSync(key)
},
setJSON (key, jsonValue) {
if (jsonValue != null) {
this.set(key, JSON.stringify(jsonValue))
}
},
getJSON (key) {
const value = this.get(key)
if (value != null && value !== '') {
return JSON.parse(value)
}
},
remove (key) {
return uni.removeStorageSync(key);
}
}

29
common/uniapp-axios-adapter.js

@ -0,0 +1,29 @@
import axios from "axios";
import settle from "axios/lib/core/settle";
import buildURL from "axios/lib/helpers/buildURL";
axios.defaults.adapter = function(config) {
return new Promise((resolve, reject) => {
const url = config.baseURL + buildURL(config.url, config.params, config.paramsSerializer)
uni.request({
method: config.method.toUpperCase(),
url: url,
header: config.headers,
data: config.data,
dataType: config.dataType,
responseType: config.responseType,
sslVerify: config.sslVerify,
complete: function complete(response) {
response = {
data: response.data,
status: response.statusCode,
errMsg: response.errMsg,
header: response.header,
config: config
};
settle(resolve, reject, response);
},
})
})
}

82
common/with-authentication.js

@ -0,0 +1,82 @@
import storage from "./storage";
import errorCode from "./errorCode";
import store from "../store";
/**
* 处理凭证信息
* @param axiosInstance {AxiosInstance}
*/
export const withAuthentication = (axiosInstance) => {
axiosInstance.interceptors.request.use((config) => {
const $withCredentials = config.$withCredentials !== false;
if ($withCredentials) {
const token = storage.get('credential')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
})
axiosInstance.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// console.log(res)
if (code === 401) {
// if (!isRelogin.show) {
// isRelogin.show = true;
// MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
// isRelogin.show = false;
// }).catch(() => {
// isRelogin.show = false;
// });
// }
store.dispatch('LogOut')
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
uni.showToast({
title: msg,
icon: 'none'
})
return Promise.reject(new Error(msg))
} else if (code === 601) {
uni.showToast({
title: msg,
icon: 'none'
})
return Promise.reject('error')
} else if (code !== 200) {
uni.showToast({
title: msg,
icon: 'none'
})
return Promise.reject('error')
} else {
return res.data
}
},
error => {
console.log('err' + error)
let {
message
} = error;
if (message == "Network Error") {
message = "后端接口连接异常";
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
uni.showToast({
title: message,
icon: 'none',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
}

40
common/with-repeat-guard.js

@ -0,0 +1,40 @@
import storage from "./storage";
/**
* 避免重复处理
* @param axiosInstance {AxiosInstance}
*/
export const withRepeatGuard = (axiosInstance) => {
axiosInstance.interceptors.request.use((config) => {
// 如果该请求允许重复
if (config.$repeatable) {
return config
}
// 如果该请求不允许重复, 并且是 post 或者 put 请求, 则判断是否已经有相同的请求在处理
if (config.method === 'post' || config.method === 'put') {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
time: new Date().getTime()
}
const sessionObj = storage.getJSON('sessionObj')
if (sessionObj === undefined || sessionObj === null || sessionObj === '') {
storage.setJSON('sessionObj', requestObj)
} else {
const s_url = sessionObj.url; // 请求地址
const s_data = sessionObj.data; // 请求数据
const s_time = sessionObj.time; // 请求时间
const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交
if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {
const message = '数据正在处理,请勿重复提交';
console.warn(`[${s_url}]: ` + message)
return Promise.reject(new Error(message))
} else {
storage.setJSON('sessionObj', requestObj)
}
}
}
return config;
})
}

66
component/GeneralButton/index.vue

@ -0,0 +1,66 @@
<template>
<view>
<view class="btn" :style="{'--bgColor':bgColor,'width':btnWidth}" @click="$u.throttle(emitClick, 900)">
<text>{{ btnTitle }}</text>
</view>
</view>
</template>
<script>
export default {
name:"Btn",
props: {
btnTitle: {
type: String,
default () {
return '确认'
}
},
btnWidth: {
type: String,
default () {
return ''
}
},
bgColor: {
type: String,
default () {
return '#006BFF'
}
}
},
data() {
return {
};
},
methods: {
emitClick() {
this.$emit('emitClick')
}
}
}
</script>
<style lang="scss" scoped>
.btn {
margin: 0 auto;
height: 92rpx;
line-height: 92rpx;
border-radius: 200rpx;
text-align: center;
letter-spacing: 3px;
color: #FFFFFF;
background: #006BFF;
// position:relative;
// left: 50%;
// transform: translateX(-50%);
// bottom:-150rpx;
// bottom:-200rpx;
margin-top: 80rpx;
.btnTttle {
font-size: 36rpx;
line-height: 32rpx;
}
}
</style>

141
component/header.vue

@ -0,0 +1,141 @@
<template>
<view class="cardHeader">
<view class="flex">
<image src="../static/images/logo.png" mode="" class="logo"></image>
<view class="title">IPPSWAP</view>
</view>
<view class="lang" @click="showLang">
<text class="lang-text">
{{langTrue=='English'?'EN':'CN'}}
</text>
</view>
<view class="select-con" v-show="isShowLang">
<view class="item" v-for="(item, index) in languageData" :key="index" @click="changeLanguage(item,index)"
:style="{color:index==current?'#fff':''}">
{{ item.title }}
</view>
</view>
</view>
</template>
<script>
import languageData from '@/utils/language/config'
export default {
name: 'Header',
props: {
title: {
type: String | Number,
default () {
return ''
}
},
},
data() {
return {
current: uni.getStorageSync('current') || 0,
isShowLang: false,
i: 1,
languageData,
language: languageData[0].language || 'zh_TW',
langTrue: languageData[0].title || '繁体中文',
}
},
mounted() {
if (uni.getStorageSync('langTrue')) {
this.langTrue = uni.getStorageSync('langTrue')
this.language = this._i18n.locale
}
},
methods: {
changeLanguage(e, i) {
const index = i || 0
this.current = i
const current = this.languageData[index]
this._i18n.locale = current.language
this.$store.commit('setLanguage', current.language)
this.langTrue = current.title
uni.setStorageSync('langTrue', current.title)
uni.setStorageSync('current', this.current)
this.isShowLang = false
this.i++
},
showLang() {
this.i++
if (this.i % 2 == 0) {
this.isShowLang = true
} else {
this.isShowLang = false
}
},
}
}
</script>
<style lang="scss" scoped>
.select-con {
position: absolute;
right: 32rpx;
top: 105rpx;
z-index: 10;
background: url('../static/images/Union.png') no-repeat;
background-size: cover;
color: rgba(167, 181, 229, 1);
width: 176rpx;
height: 178rpx;
padding: 40rpx 30rpx;
.item:last-child {
margin-top: 30rpx;
}
}
.cardHeader {
position: relative;
box-sizing: border-box;
height: 106rpx;
padding: 20rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
background: radial-gradient(104.53% 5436.39% at -4.53% 16.35%, #7F97EC 3.77%, #8D6CEA 11.22%, #5944D7 24.63%, #2D2EA8 36.47%, #182390 63.92%, #16228E 100%)
/* warning: gradient uses a rotation that is not supported by CSS and may not behave as expected */
;
.logo {
width: 62rpx;
height: 58rpx;
}
.title {
height: 100%;
font-size: 40rpx;
display: table;
color: #fff;
margin-left: 24rpx;
}
.lang {
text-align: center;
font-size: 28rpx;
font-weight: 700;
width: 66rpx;
height: 66rpx;
line-height: 60rpx;
border: 2rpx solid transparent;
border-radius: 12rpx;
background-clip: padding-box, border-box;
background-origin: padding-box, border-box;
background-image: linear-gradient(180deg, #222794 25%, #592A86 77.78%), linear-gradient(to right, rgba(230, 232, 156, 1), rgba(247, 135, 171, 1));
.lang-text {
background: linear-gradient(to bottom, rgba(230, 232, 156, 1), rgba(247, 135, 171, 1));
-webkit-background-clip: text;
color: transparent;
}
}
}
</style>

127
global.scss

@ -0,0 +1,127 @@
$deep-color: #333333;
$gray-color: #9299AD;
$primary-color: #5C5BEE;
$light-primary-color: #E8E8FF;
$text-red:#D91C1C;
$text-org:#EE991A;
$bg-light-org:#FFF1DB;
$text-green:#39B872;
$bg-white:#fff;
$text-white:#fff;
:export {
deepColor: $deep-color;
grayColor: $gray-color;
primaryColor: $primary-color;
lightPrimaryColor: $light-primary-color;
}
uni-app {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
.text-blue{
color: #34ACF0;
}
.text-red{
color: #D91C1C;
}
.text-org{
color: #EE991A;
}
.bg-light-org{
background: #FFF1DB;
}
.text-green{
color: #39B872;
}
.bg-white{
background-color: #fff;
}
.text-white{
color: #fff;
}
.text-deep {
color: $deep-color;
}
.bg-deep {
background-color: $deep-color;
}
// 灰色
.text-gray {
color: $gray-color;
}
.bg-gray {
background-color: $gray-color;
}
// 主题深色
.text-deep-primary {
// color: #5C5BEE !important;
color: $primary-color;
}
.bg-deep-primary {
background-color: $primary-color;
}
// 主题浅色
.text-light-primary {
color: $light-primary-color;
}
.bg-light-primary {
background-color: $light-primary-color;
}
// 浅主题色按钮
.btn-light-primary {
background-color: $light-primary-color;
color: $primary-color;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease-in-out;
padding-left: 0;
padding-right: 0;
&:after {
border: none;
}
}
// 深主题色按钮
.btn-deep-primary {
background-color: $primary-color;
color: #fff;
border: none;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease-in-out;
padding-left: 0;
padding-right: 0;
&:after {
border: none;
}
}
.a-line-overflow {
white-space: nowrap;/*强制在一行显示*/
overflow: hidden;
text-overflow: ellipsis;/*溢出显示省略号*/
}
.page-content {
width: 750rpx;
position: relative;
padding: 0 32rpx;
}
.uni-input-input {
// 修改输入框文字的颜色
color: #333333;
}

168
index.html

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
<script>
window.onload = function() {
document.addEventListener('touchstart', function(event) {
if (event.touches.length > 1) {
event.preventDefault();
}
});
document.addEventListener('gesturestart', function(event) {
event.preventDefault();
});
};
</script>
<title></title>
<style>
body {
background: #090E11;
height: 100%;
}
#app {
height: 100%;
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
left: 50%;
top: 50%;
box-sizing: border-box;
transform: translate(-50%, -50%);
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: #fff;
margin: -4px 0 0 -4px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
<!--preload-links-->
<!--app-context-->
<!-- <link rel="icon" href="./static/images/logo2.png" /> -->
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<script>
// VConsole will be exported to `window.VConsole` by default.
var vConsole = new window.VConsole();
</script> -->
</head>
<body>
<div id="app">
<div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<!--app-html-->
</div>
<!-- <script type="module" src="/main.js"></script> -->
</body>
</html>

51
main.js

@ -0,0 +1,51 @@
import App from './App'
import uView from "uview-ui";
import utils from './utils/index.js'
import store from './store'
import './utils/noScale.js'
import VueI18n from 'vue-i18n'
// 多国语言
import EN from './utils/language/en_US.js'
import ZH from './utils/language/zh_TW.js'
// #ifndef VUE3
import Vue from 'vue'
Vue.use(uView);
// 自定义顶部部导航栏
import tabBar from 'component/header.vue'
Vue.component('tab-bar', tabBar)
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)
// 全局边框颜色变量
Vue.prototype.borderColor = '#E3E2EC'
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: store.state.language, // 默认选择的语言
messages: {
'en_US': EN,
'zh_TW': ZH,
}
})
Vue.prototype.$utils = utils
Vue.prototype.$store = store
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
i18n,
store,
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import {
createSSRApp
} from 'vue'
export function createApp() {
const app = createSSRApp(App)
return {
app
}
}
// #endif

96
manifest.json

@ -0,0 +1,96 @@
{
"name" : "kakapay",
"appid" : "__UNI__0088A51",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2",
"h5" : {
"publicPath" : "/",
"devServer" : {
"disableHostCheck" : true,
"proxy" : {
"/htmlApi" : {
"ws" : false,
"target" : "http://kaka-carddealer-admin.weirui0755.com/prod-api",
"changeOrigin" : true,
"secure" : false,
"pathRewrite" : {
"^/htmlApi" : ""
}
}
}
},
"title" : "kakapay",
"optimization" : {
"treeShaking" : {
"enable" : true
}
},
"template" : "index.html"
}
}

7851
package-lock.json

File diff suppressed because it is too large

12
package.json

@ -0,0 +1,12 @@
{
"dependencies": {
"ant-design-vue": "^3.2.13",
"axios": "^1.1.3",
"crypto-js": "^4.1.1",
"decimal.js": "^10.4.3",
"swiper": "^8.4.5",
"vue-clipboard2": "^0.3.3",
"vue-i18n": "^9.2.2",
"web3": "^1.8.0"
}
}

50
pages.json

@ -0,0 +1,50 @@
{
"easycom": {
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
},
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": ""
// "enablePullDownRefresh": true
}
}
],
"globalStyle": {
// "mp-alipay": {
// /* */
// "transparentTitle": "always",
// "allowsBounceVertical": "NO"
// },
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "码商",
"navigationStyle": "custom",
"navigationBarTextStyle": "black",
"rpxCalcMaxDeviceWidth": 960, // rpx px 960
"rpxCalcBaseDeviceWidth": 375, // rpx rpx px 375
"app-plus": {
"background": "#293154",
"titleNView": false //app+h5
}
},
"tabBar": {
// "color": "#646D7E",
// "selectedColor": "#4A8CF5",
// "list": [
// {
// "pagePath": "pages/home/home",
// "text": "首页",
// "iconPath": "/static/tongyonh/icon-home-28Selected.png",
// "selectedIconPath": "/static/tongyonh/icon-home-28Selected2.png"
// },
// {
// "pagePath": "pages/mine/mine",
// "text": "我的",
// "iconPath": "/static/tongyonh/icon-home-28Selected.png",
// "selectedIconPath": "/static/tongyonh/icon-home-28Selected2.png"
// }
// ]
}
}

217
pages/index/index.vue

@ -0,0 +1,217 @@
<template>
<view class="content">
<tab-bar />
<view class="container">
<view class="top">
<view class="title f44 text-primary">
{{ i18n.Home }}<br /><br />
</view>
<view class="text-deep-primary">
{{ i18n.cx }}<br /><br /><br />
{{ i18n.zw }}<br /><br /><br />
{{ i18n.yuan }}<br /><br /><br />
{{ i18n.we }}<br /><br /><br />
{{ i18n.ji }}
</view>
</view>
<view class="bottom">
<view class="item">
<view class="flex2 label-title">
<image src="@/static/images/com_icon_srank.452821d11.png" mode=""></image>
<text class="text-primary f36">{{ i18n.contract }}</text>
</view>
<view class="m48">
<u-input v-model="form.contract" placeholder="" :clearable="false" :disabled="true" />
</view>
</view>
<view class="item m48">
<view class="flex2 label-title">
<image src="@/static/images/invite_table_title.e3146df81.png" mode=""></image>
<text class="text-primary f36">{{ i18n.address }}</text>
</view>
<view class="m48">
<u-input v-model="form.supAddress" placeholder="" :clearable="false" :disabled="true" />
</view>
</view>
<general-button @emitClick="submit" :btnTitle="i18n.confirm"></general-button>
<view class="item m48">
<view class="flex2 label-title">
<image src="@/static/images/com_icon_yaoqing.c8e1575f1.png" mode=""></image>
<text class="text-primary f36">{{ i18n.link }}</text>
</view>
<view class="m48 text-primary" style="word-break: break-all;">
https://ippswap.com/community?inviteCode={{form.address}}
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import GeneralButton from '@/component/GeneralButton'
import {
web3x
} from "@/utils/web3x/web3x.js"
export default {
components: {
GeneralButton,
},
data() {
return {
form: {
address: '',
contract: '0x6b175474e89094c44da98b954eedeac495271d0f',
supAddress: '',
},
}
},
computed: {
i18n() {
return this.$t("tabBar");
},
},
onShow() {
},
onPullDownRefresh() {
},
onLoad(val) {
if (val) {
this.form.supAddress = val.inviteCode
}
this.init()
},
onReachBottom() {
},
// onReadyonLoad
onReady() {},
methods: {
init() {
web3x.connectViaInPage()
.then(res => {
this.form.address = web3x.selectedAddress
//
console.log("当前钱包地址", web3x.selectedAddress);
})
},
submit() {
if (!this.form.supAddress) {
uni.$u.toast(this.$t("tabBar").empty)
return
}
web3x.connectViaInPage()
.then(async res => {
uni.showLoading({
title: '',
mask: true
})
//
console.log("当前钱包地址", web3x.selectedAddress);
// USDT IPPT ( IPPT )
await web3x.usdt.approveMAX("0x622d7b79a904e00e5fcab06396ff009e441f0186")
uni.hideLoading()
// IPPT
// 0xFb4FC7Ddb8c4aa6b944703CE1e89D2B9Aa67a400:
// web3x.selectedAddress:
await web3x.ippt.$creatCode(this.form.supAddress, web3x.selectedAddress)
})
},
go(val) {
uni.navigateTo({
url: val,
})
},
},
}
</script>
<style lang="scss" scoped>
.m48 {
margin-top: 48rpx;
}
::v-deep .uni-input-input {
color: #fff;
min-height: 88rpx !important;
}
::v-deep .u-input__input {
color: #fff;
min-height: 88rpx !important;
}
::v-deep .u-input {
border: 2rpx solid transparent;
border-radius: 100rpx;
background-clip: padding-box, border-box;
border-color: transparent !important;
background-origin: padding-box, border-box;
background-image: linear-gradient(180deg, #141644 25%, #141644 77.78%), linear-gradient(to right, rgba(126, 223, 135, 1), rgba(255, 158, 137, 1));
height: 88rpx;
padding: 0 40rpx !important;
}
.text-primary {
color: #fff;
}
.text-deep-primary {
color: rgba(167, 181, 229, 1);
}
.f44 {
font-size: 44rpx;
}
.f28 {
font-size: 28rpx;
}
.f36 {
font-size: 36rpx;
}
.content {
width: 750rpx;
background: #0B1016;
min-height: 100vh;
position: relative;
z-index: 10;
padding-bottom: 100rpx;
background-image: url('../../static/images/community_bg_h51.png');
background-repeat: no-repeat;
background-size: contain;
background-position: 0px 130rpx;
.container {
padding: 0rpx 32rpx;
.top {
padding-top: 64rpx;
}
.bottom {
border: 1px solid rgba(119, 120, 177, 1);
background: #212A51;
padding: 32rpx 32rpx 40rpx 32rpx;
border-radius: 24rpx;
margin-top: 36rpx;
.label-title {
image {
width: 52rpx;
height: 52rpx;
margin-right: 24rpx;
}
}
}
}
}
</style>

BIN
static/images/Union.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
static/images/com_icon_srank.452821d11.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
static/images/com_icon_yaoqing.c8e1575f1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
static/images/community_bg_h51.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

BIN
static/images/invite_table_title.e3146df81.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
static/images/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

26
store/index.js

@ -0,0 +1,26 @@
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
// 中英化
language: uni.getStorageSync("language") || 'zh_TW',
},
mutations: {
// 设置中英文
setLanguage: (state, language) => {
const obj = state
obj.language = language
uni.setStorageSync("language", language)
},
},
actions: {
}
})
export default store

78
uni.scss

@ -0,0 +1,78 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
@import "uview-ui/theme.scss";
@import "uview-ui/index.scss";
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
/* 文字基本颜色 */
$uni-text-color:#333;//基本色
$uni-text-color-inverse:#fff;//反色
$uni-text-color-grey:#999;//辅助灰色如加载更多的提示信息
$uni-text-color-placeholder: #808080;
$uni-text-color-disable:#c0c0c0;
/* 背景颜色 */
$uni-bg-color:#ffffff;
$uni-bg-color-grey:#f8f8f8;
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
/* 边框颜色 */
$uni-border-color:#c8c7cc;
/* 尺寸变量 */
/* 文字尺寸 */
$uni-font-size-sm:12px;
$uni-font-size-base:14px;
$uni-font-size-lg:16;
/* 图片尺寸 */
$uni-img-size-sm:20px;
$uni-img-size-base:26px;
$uni-img-size-lg:40px;
/* Border Radius */
$uni-border-radius-sm: 2px;
$uni-border-radius-base: 3px;
$uni-border-radius-lg: 6px;
$uni-border-radius-circle: 50%;
/* 水平间距 */
$uni-spacing-row-sm: 5px;
$uni-spacing-row-base: 10px;
$uni-spacing-row-lg: 15px;
/* 垂直间距 */
$uni-spacing-col-sm: 4px;
$uni-spacing-col-base: 8px;
$uni-spacing-col-lg: 12px;
/* 透明度 */
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
/* 文章场景相关 */
$uni-color-title: #2C405A; // 文章标题颜色
$uni-font-size-title:20px;
$uni-color-subtitle: #555555; // 二级标题颜色
$uni-font-size-subtitle:26px;
$uni-color-paragraph: #3F536E; // 文章段落颜色
$uni-font-size-paragraph:15px;

150
utils/atom.js

@ -0,0 +1,150 @@
/**
* 格式化金额, 保留两位小数
* @param o
* @param o.amount {number | string} 金额
* @param o.precision {number} 精度, 默认 100 ()
* @param o.thousand {boolean} 是否需要千分位, 默认 true
* @param o.decimal {boolean} 是否需要小数点, 默认 true
* @param o.decimalLength {number} 小数点位数, 默认 2
* @param o.decimalSeparator {string} 小数点分隔符, 默认 '.'
* @param o.thousandSeparator {string} 千分位分隔符, 默认 ','
* @return {string | number}
*/
export const amountFormat = (o) => {
if (o.amount === undefined || o.amount === null) {
return o.amount;
}
let amount = o.amount;
// 如果 amount 是字符串, 则转换为数字
if (typeof amount === 'string') {
amount = parseFloat(amount);
}
let precision = o.precision || 100;
let thousand = o.thousand === undefined ? true : o.thousand;
let decimal = o.decimal === undefined ? true : o.decimal;
let decimalLength = o.decimalLength || 2;
let decimalSeparator = o.decimalSeparator || '.';
let thousandSeparator = o.thousandSeparator || ',';
amount = amount / precision;
amount = amount.toFixed(decimalLength);
if (thousand) {
amount = amount.replace(/(\d)(?=(\d{3})+\.)/g, '$1' + thousandSeparator);
}
if (decimal) {
amount = amount.replace('.', decimalSeparator);
}
return amount;
}
/**
* 参数处理
* @param {*} params 参数
*/
export function tansParams(params) {
let result = ''
for (const propName of Object.keys(params)) {
const value = params[propName];
var part = encodeURIComponent(propName) + "=";
if (value !== null && value !== "" && typeof (value) !== "undefined") {
if (typeof value === 'object') {
for (const key of Object.keys(value)) {
if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {
let params = propName + '[' + key + ']';
var subPart = encodeURIComponent(params) + "=";
result += subPart + encodeURIComponent(value[key]) + "&";
}
}
} else {
result += part + encodeURIComponent(value) + "&";
}
}
}
return result
}
// 精度计算乘法
export function NumberMul(arg1, arg2) {
let m = 0
const s1 = arg1.toString()
const s2 = arg2.toString()
try {
m += s1.split('.')[1].length
} catch (e) {}
try {
m += s2.split('.')[1].length
} catch (e) {}
return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}
// 精度计算除法
// 除数,被除数, 保留的小数点后的位数
export function NumberDiv(arg1, arg2) {
arg1 = parseFloat(arg1)
arg2 = parseFloat(arg2)
let t1 = 0
let t2 = 0
let r1; let r2
try {
t1 = arg1.toString().split('.')[1].length
} catch (e) {}
try {
t2 = arg2.toString().split('.')[1].length
} catch (e) {}
r1 = Number(arg1.toString().replace('.', ''))
r2 = Number(arg2.toString().replace('.', ''))
return Mul(r1 / r2, Math.pow(10, t2 - t1))
}
export function Mul(arg1, arg2) {
arg1 = parseFloat(arg1)
arg2 = parseFloat(arg2)
let m = 0
const s1 = arg1.toString()
const s2 = arg2.toString()
try {
m += s1.split('.')[1].length
} catch (e) {}
try {
m += s2.split('.')[1].length
} catch (e) {}
return Number(s1.replace('.', '')) * Number(s2.replace('.', '')) / Math.pow(10, m)
}
export const GetADateTime = {
// 获取今天起始时间日期
getTodayBegin() {
const date = new Date();
const year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
// 年月日时分秒
// 月和日如果是一位数的话,前面补0
if (month < 10) {
month = '0' + month;
}
if (day < 10) {
day = '0' + day;
}
return year + '-' + month + '-' + day + ' 00:00:00';
},
// 获取今天结束时间日期
getTodayEnd() {
const date = new Date();
const year = date.getFullYear();
let month = date.getMonth() + 1;
let day = date.getDate();
// 年月日时分秒
// 月和日如果是一位数的话,前面补0
if (month < 10) {
month = '0' + month;
}
if (day < 10) {
day = '0' + day;
}
return year + '-' + month + '-' + day + ' 23:59:59';
}
}

24
utils/constant.js

@ -0,0 +1,24 @@
import Theme from '../global.scss';
const SHOW_DIALOG=true
const env = "dev";
// const env = "stage";
// const env = "prod";
const IS_TEST = env === "dev" || env === "stage";
const baseUrl = IS_TEST ? 'http://kaka-carddealer-admin.weirui0755.com/stage-api'
: '/prod-api'
export default {
baseUrl,
IS_TEST,
SHOW_DIALOG,
/**
* @type {{deepColor: string; grayColor: string; primaryColor: string; lightPrimaryColor: string;}}
*/
Theme,
appVariant: {
title: "Yuntong Supply Chain"
}
}

204
utils/index.js

@ -0,0 +1,204 @@
var utils = {
// 获取今天+1
addDate() {
let nowDate = new Date();
nowDate.setDate(nowDate.getDate() + 1);
var day = nowDate.getDate();
let date = {
year: nowDate.getFullYear(),
month: nowDate.getMonth() + 1,
date: day,
}
// console.log(date);
if (date.date <= 9) {
return date.year + '-' + 0 + date.month + '-' + 0 + date.date;
} else {
return date.year + '-' + 0 + date.month + '-' + date.date;
}
// console.log(this.systemDate);
},
// 获取今天
addDate2() {
let nowDate = new Date();
let date = {
year: nowDate.getFullYear(),
month: nowDate.getMonth() + 1,
date: nowDate.getDate(),
}
// console.log(date);
if (date.date <= 9) {
return date.year + '-' + 0 + date.month + '-' + 0 + date.date;
} else {
return date.year + '-' + 0 + date.month + '-' + date.date;
}
// console.log(this.systemDate);
},
getSubStr: function(str) {
var subStr1 = str.substr(0, 5);
var subStr2 = str.substr(str.length - 5, 20);
var subStr = subStr1 + "..." + subStr2;
return subStr;
},
checkEmail: function(email) {
return RegExp(
/^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/
)
.test(email);
},
checkMobile: function(mobile) {
return RegExp(/^1[34578]\d{9}$/).test(mobile);
},
caculateTime: function(timeZome, time) {
return time + (timeZome * 1000 * 60 * 60);
},
formatyymmdd: function(time) {
var date = new Date(time)
console.log(date, 5555655)
var localTime = date.getTime();
var localOffset = date.getTimezoneOffset() * 60000 //获得当地时间偏移的毫秒数
var utc = localTime + localOffset //utc即GMT时间
var offset = 8; //东8区
var beijing = utc + (3600000 * offset);
date = new Date(beijing);
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
console.log(M + '/' + D + '/' + Y, 5454545454);
return M + '/' + D + '/' + Y;
},
formatyymmdd2: function(time) {
var date = new Date(time)
var localTime = date.getTime();
var localOffset = date.getTimezoneOffset() * 60000; //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = 8; //东8区
var beijing = utc + (3600000 * offset);
date = new Date(beijing);
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return Y + M + D;
},
formatyymmddhhmmss: function(time) {
var date = new Date(time)
var localTime = date.getTime()
var localOffset = date.getTimezoneOffset() * 60000 //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = uni.getStorageSync('coinTypeInfo').system_timezone //时区拿接口的
var beijing = utc + (3600000 * offset);
date = new Date(beijing)
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
// return Y + '-' + M + '-' + D + ' ' + h + ':' + m + ':' + s;
return D + '/' + M + '/' + Y + ' ' + h + ':' + m + ':' + s;
},
// 日月时分秒
formatmmddhhmmss: function(time) {
var date = new Date(time)
var localTime = date.getTime()
var localOffset = date.getTimezoneOffset() * 60000 //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = uni.getStorageSync('coinTypeInfo').system_timezone //时区拿接口的
var beijing = utc + (3600000 * offset);
date = new Date(beijing)
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
// return Y + '-' + M + '-' + D + ' ' + h + ':' + m + ':' + s;
return D + '/' + M + '/' + ' ' + h + ':' + m + ':' + s;
},
formatyymmddhhmm: function(time) {
var date = new Date(time)
var localTime = date.getTime()
var localOffset = date.getTimezoneOffset() * 60000 //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = uni.getStorageSync('coinTypeInfo').system_timezone //时区拿接口的
var beijing = utc + (3600000 * offset);
date = new Date(beijing)
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
// return Y + '-' + M + '-' + D + ' ' + h + ':' + m + ':' + s;
return D + '/' + M + '/' + Y + ' ' + h + ':' + m;
},
getformatyymmddhhmmss: function(time) {
var date = new Date(time)
var localTime = date.getTime()
var localOffset = date.getTimezoneOffset() * 60000 //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = uni.getStorageSync('coinTypeInfo').system_timezone //时区拿接口的
var beijing = utc + (3600000 * offset);
date = new Date(beijing)
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
// return Y + '-' + M + '-' + D + ' ' + h + ':' + m + ':' + s;
return D + '/' + M + '/' + Y + ' ' + h + ':' + m + ':' + s;
},
formathhmm: function(time) {
var date = new Date(time)
var localTime = date.getTime();
var localOffset = date.getTimezoneOffset() * 60000; //获得当地时间偏移的毫秒数
var utc = localTime + localOffset; //utc即GMT时间
var offset = 8; //东8区
var beijing = utc + (3600000 * offset);
date = new Date(beijing);
var Y = date.getFullYear()
var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1)
var D = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
var h = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
var m = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
return h + ':' + m;
},
//客户端时间转换为北京时间
getBeijingtime: function() {
//获得当前运行环境时间
let d = new Date();
let currentDate = new Date();
let tmpHours = currentDate.getHours();
//算得时区
let time_zone = -d.getTimezoneOffset() / 60;
if (time_zone < 0) {
time_zone = Math.abs(time_zone) + 8;
currentDate.setHours(tmpHours + time_zone);
} else {
time_zone -= 8;
currentDate.setHours(tmpHours - time_zone);
}
return currentDate;
},
}
export default utils

10
utils/language/config.js

@ -0,0 +1,10 @@
export default [
{
title: '繁体中文',
language: 'zh_TW'
},
{
title: 'English',
language: 'en_US',
},
]

18
utils/language/en_US.js

@ -0,0 +1,18 @@
// zh_TW.js
export default {
tabBar: {
Home: 'Dear Ipswap Foundation Member:',
contract: 'Mapping smart contracts',
address: 'My inviter address',
confirm: 'confirm',
link: 'My invitation link',
cx: 'Due to its unique innovation, IPPSWAP has become a popular choice for partners in the industry, developing rapidly and attracting attention from various fields. The fund is investigating the reasons for the transfer of LP assets. We will be committed to creating a safe, transparent, and fair investment environment, enhancing risk control capabilities and information transparency, and protecting the rights and interests of all investors.',
zw: 'As the most innovative and profitable foundation, we always uphold a responsible attitude towards member investment. We are now launching a new ippswap mining plan to allow everyone to participate more flexibly and efficiently in mining, and gain more profits.',
yuan: 'Original ippswap participants are required to participate in airdrop mapping and receive 50% of the original ipp token chips for free. The duration of receiving airdrop and mapping is 25 days, and the token mining plan will be launched after 25 days. Members who fail to participate in air drop collection and mapping within the specified time limit will be deemed to have waived their permission.',
we: 'We believe that through a new mining plan, the ippswap fund will become a more prosperous and active community, bringing more opportunities and benefits to all participants. Welcome to join us! Thank you for your support and trust.',
ji: 'ippswap Foundation',
empty:'Inviter address is empty',
},
}

19
utils/language/zh_TW.js

@ -0,0 +1,19 @@
// zh_TW.js
export default {
tabBar: {
Home: '尊敬的ippswap基金會會員:',
contract: '映射智慧合約',
address: '我的邀請人地址',
confirm: '確認',
link: '我的邀請連結',
cx: '因其獨特的創新,ippswap已成為圈內廣大夥伴的熱門選擇,發展迅猛,引起各方領域的關注。基金正在調查LP資產轉出原因,我們將致力於創建安全、透明、公正的投資環境,並提升風控能力和資訊公開透明度,保障所有投資者權益。',
zw: '作為最有創新意識和最有盈利能力的基金會,我們始終秉持對會員投資負責的態度,現開啟新的ippswap挖礦計畫,讓大家更靈活、高效地參與挖礦,獲得更多收益。',
yuan: '原ippswap參與人員需參與空投映射,免費獲得原ipp代幣50%籌碼。領取空投及映射時長為25天,25天後將代幣挖礦計畫開啟。未在規定時限參與空投領取及映射的會員將視作放棄許可權。',
we: '我們相信,通過全新的挖礦計畫,ippswap基金將成為更繁榮、活躍的社區,為所有參與者帶來更多機會和福利。歡迎加入我們!謝謝大家的支持和信任。',
ji: 'ippswap基金會',
empty:'邀請人地址為空',
},
}

131
utils/mixins/list-page.mixin.js

@ -0,0 +1,131 @@
const initListPageContext = () => ({
// 每页
pageSize: 10,
// 当前页
page: 1,
// 总数量
total: 0,
})
export default {
onPullDownRefresh () {
// 重置页数
this.listPageContext.page = 1;
this.getData(this.listPageContext)
.finally(() => {
uni.stopPullDownRefresh();
});
},
/**
* 尝试给页面打补丁
* 获取当前以加载的数据
* 仅在没有数据的时候加载并且数据已经存在的情况下调用
* @return {*}
*/
onShow () {
if (this.status === "loading" || this.originData.length === 0) {
return;
}
return this._getData({page: 1, pageSize: this.originData.length || this.listPageContext.pageSize})
.then(res => {
this.originData = res.data;
this.listPageContext.total = res.total;
})
},
onReachBottom () {
this._onReachBottom();
},
data () {
return {
/**
* 用于 loadmore 组件
* @type {'loadmore' | 'loading' | 'nomore'}
*/
status: 'loadmore',
loadText: {
loadmore: '轻轻上拉',
loading: '努力加载中',
nomore: '没有更多了'
},
// 原始数据
originData: [],
listPageContext: initListPageContext(),
}
},
computed: {
// 是否还有更多数据
hasMore () {
return this.originData.length < this.listPageContext.total
},
// 视图使用的列表数据
dataForUI () {
return this.originData.map((item, index) => this._mapFrom(item, index))
},
},
methods: {
_getData (paging) {
throw new Error('请在组件中实现 _getData 方法')
},
_mapFrom (remoteItem, index) {
throw new Error('请在组件中实现 _mapFrom 方法')
},
_uniqueId (remoteItem) {
throw new Error('请在组件中实现 _uniqueId 方法')
},
getData (paging) {
paging = paging || this.listPageContext
this.status = 'loading'
return this._getData(paging)
.then(res => {
if (this.listPageContext.page === 1) {
this.originData = res.data
} else {
this.originData = this.originData.concat(res.data)
}
this.listPageContext.total = res.total
// 判断是否还有更多
if (this.originData.length >= this.listPageContext.total) {
this.status = 'nomore'
} else {
this.status = 'loadmore'
}
})
.catch(() => {
this.status = 'loadmore'
})
},
patchOriginData (index, newData) {
const oldData = this.originData[index]
this.originData = [
...this.originData.slice(0, index),
{...oldData, ...newData},
...this.originData.slice(index + 1),
]
},
// 删除某条数据
deleteOriginData (index) {
this.originData = [
...this.originData.slice(0, index),
...this.originData.slice(index + 1),
]
},
resetListPage () {
this.originData = []
this.listPageContext = initListPageContext()
},
_onReachBottom() {
// 如果没有更多数据了,就不再加载
// 如果数据为 0, 则属于误触发,不再加载
// 如果正在加载中,也不再加载
if (!this.hasMore || this.originData.length === 0 || this.status === "loading") {
return
}
// 页数 +1
this.listPageContext.page += 1;
this.getData(this.listPageContext);
}
},
}

36
utils/noScale.js

@ -0,0 +1,36 @@
window.onload = function() {
document.addEventListener('touchstart', function(event) {
if (event.touches.length > 1) {
event.preventDefault();
}
});
document.addEventListener('gesturestart', function(event) {
event.preventDefault();
});
};
// 禁用双指放大
document.documentElement.addEventListener('touchstart', function(event) {
if (event.touches.length > 1) {
event.preventDefault();
}
}, {
passive: false
});
// 禁用双击放大
var lastTouchEnd = 0;
document.documentElement.addEventListener('touchend', function(event) {
var now = Date.now();
if (now - lastTouchEnd <= 300) {
event.preventDefault();
}
lastTouchEnd = now;
}, {
passive: false
});

19
utils/web3x/ippt-abi.json

@ -0,0 +1,19 @@
[
{
"inputs": [],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{"internalType": "address", "name": "superior", "type": "address"},
{"internalType": "address", "name": "plivate", "type": "address"}
],
"name": "creatCode",
"outputs": [
{"internalType": "bool", "name": "", "type": "bool"}
],
"stateMutability": "nonpayable",
"type": "function"
}
]

439
utils/web3x/usdt-abi.json

@ -0,0 +1,439 @@
[
{
"inputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"constant": true,
"inputs": [],
"name": "_decimals",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "_name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "_symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "subtractedValue",
"type": "uint256"
}
],
"name": "decreaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "getOwner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "addedValue",
"type": "uint256"
}
],
"name": "increaseAllowance",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "mint",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}
]

418
utils/web3x/web3x.js

@ -0,0 +1,418 @@
import Web3 from "web3";
import IPPTAbi from './ippt-abi.json'
import USDTAbi from './usdt-abi.json'
// 将科学计数法形式的数字或字符串转换为可读的字符串
function scientificNotationToString(param) {
let strParam = String(param);
let flag = /e/.test(strParam);
if (!flag) return param.toString();
// 指数符号 true: 正,false: 负
let sysbol = true;
if (/e-/.test(strParam)) {
sysbol = false;
}
// 指数
let index = Number(strParam.match(/\d+$/)[0]);
// 基数
let basis = strParam.match(/^[\d\.]+/)[0].replace(/\./, "");
if (sysbol) {
return basis.padEnd(index + 1, "0");
} else {
return basis.padStart(index + basis.length, "0").replace(/^0/, "0.");
}
}
// 将数字转换到指定精度
function toDecimals(value, decimals) {
return Number(value) * 10 ** Number(decimals);
}
// 从指定精度转换数字
function fromDecimals(value, decimals) {
return value / Math.pow(10, decimals);
}
class ERC20 {
decimals = 0;
/**
* 合约实例
* @type {Contract}
*/
contract;
/**
* 合约地址
* @type {string}
*/
contractAddress;
/**
* 合约 ABI
*/
abi;
// 当前的钱包地址, 当 connected 为 true 时可用
get selectedAddress () {
return window.ethereum.selectedAddress;
}
/**
*
* @return {*}
*/
get ethereum() {
return window.ethereum
}
/**
* @param contractAddress{string}
* @param abi{Array}
* @param web3{Web3}
*/
constructor (contractAddress, abi, web3) {
this.contractAddress = contractAddress;
this.abi = abi;
this.contract = new web3.eth.Contract(abi, contractAddress);
}
/**
* 加密 abi
* @param methodDefinition {string} - 方法名
* @param parameters {Array<{type: "uint256" | "address"; value: number | string}>} - 参数
* @return {string}
*/
encodeABI(methodDefinition, parameters) {
let m = Web3.utils.sha3(methodDefinition).slice(0, 10);
parameters.forEach((p) => {
switch (p.type) {
case "uint256":
case "address":
m += p.value
.toString()
.toLowerCase()
.replace("0x", "")
.padStart(64, "0");
break;
}
});
return m;
}
handleError(err) {
const message = err ? err.message ? err.message : err.toString() : 'Unknown Error'
if (message.includes("User denied")) {
uni.showToast({
title: 'failed',
icon: 'none'
})
}
}
/**
* 相当于 contract.methods.xx.send
* @param contractSendMethod
* @param sendOptions
* @return {PromiEvent<Eth.Contract>}
*/
send (contractSendMethod, sendOptions) {
const promiEvent = contractSendMethod.send(sendOptions);
promiEvent.catch(err => this.handleError(err));
return promiEvent;
}
/**
* 获取代币余额
*
* @param account{string}
*
* @return Promise<number>
*/
async $balanceOf (account) {
const balance = await this.contract.methods.balanceOf(account).call({from: this.selectedAddress})
const decimals = await this.$decimals()
return Number(fromDecimals(Number(balance), decimals))
}
/**
* 获取授权给某个合约的数量
* @param owner {string} - 钱包地址
* @param spender {string} - 授权地址
* @return {Promise<number>}
*/
$allowance(owner, spender) {
return this.contract.methods.allowance(owner, spender).call()
.then(async (res) => {
return Number(fromDecimals(Number(res), await this.$decimals()))
})
}
/**
* 获取 vnft 合约的精度
*
* @return Promise<number>
*/
$decimals () {
if (this.decimals) {
return Promise.resolve(this.decimals)
}
return this.contract.methods
.decimals()
.call()
.then(res => {
this.decimals = Number(res)
return this.decimals
})
}
/**
* 授权代币给某个合约
* @param spender - 授权地址
* @param amount - 授权金额
* @return {PromiEvent<Contract>}
*/
$approve(spender, amount) {
amount = toDecimals(amount, this.decimals);
return this.send(this.contract.methods.approve(spender, scientificNotationToString(amount)), {from: this.selectedAddress})
}
/**
* 授权最大数量
* @param spender
* @return {PromiEvent<Eth.Contract>}
*/
approveMAX(spender) {
return this.send(this.contract.methods.approve(spender, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), {from: this.selectedAddress})
}
async $burn(amount, sendOptions) {
amount = toDecimals(amount, await this.$decimals());
return this.send(this.contract.methods.burn(amount), sendOptions);
}
/**
* 转移 token
* @param recipient - 接受者
* @param amount - 转移数量
* @return {PromiEvent<Eth.Contract>}
*/
async $transfer(recipient, amount) {
amount = toDecimals(amount, await this.$decimals());
// debugger
return this.send(this.contract.methods.transfer(recipient, amount.toString()), {from: this.selectedAddress});
}
}
class USDT extends ERC20 {
decimals = 18;
}
class IPPT extends ERC20 {
/**
* 调用IPPT 合约绑定上级关系
* creatCode(address superior, address plivate) public returns (bool)
*/
async $creatCode (superior, plivate) {
const data = this.encodeABI("creatCode(address,address)", [
{ type: "address", value: superior },
{ type: "address", value: plivate },
]);
const params = [
{
data: data,
from: this.selectedAddress,
to: this.contractAddress,
},
];
return this.ethereum
.request({
method: "eth_sendTransaction",
params,
})
}
}
/**
* // 链接钱包, 所有 web3x.xx 方法都需要先执行 connectViaInPage 成功
* web3x.connectViaInPage()
* .then(res => {
* // 当前钱包地址
* console.log("当前钱包地址", web3x.selectedAddress);
*
* // 授权当前钱包的 USDT 给 IPPT (参数为 IPPT 合约地址)
* web3x.usdt.approveMAX("0x6b175474e89094c44da98b954eedeac495271d0f")
* .on("confirmation", hash => {
* console.log("授权成功", hash);
* })
*
* // 调用IPPT 合约绑定上级关系
* // 0xFb4FC7Ddb8c4aa6b944703CE1e89D2B9Aa67a400: 上级地址
* // web3x.selectedAddress: 当前钱包地址
* web3x.ippt.$creatCode("0xFb4FC7Ddb8c4aa6b944703CE1e89D2B9Aa67a400", web3x.selectedAddress)
* .on("confirmation", hash => {
* console.log("绑定上级关系成功", res);
* })
* })
* // 切换到 BSC 测试网, 需先调用 web3x.addBscTestnet()
* web3x.switchToBscTestnet
* // 添加 BSC 测试网
* web3x.addBscTestnet
**/
class Web3X {
/**
* 是否已链接
* @type {boolean}
*/
connected = false;
/**
* web3 实例
* @type {Web3}
*/
#web3;
/**
* @type {IPPT}
*/
ippt
/**
* @type {ERC20}
*/
usdt
/**
* @type {string}
*/
ipptContractAddress
/**
* usdt 合约地址
* @type {string}
*/
usdtContractAddress
constructor (ipptContractAddress, usdtContractAddress) {
this.ipptContractAddress = ipptContractAddress;
this.usdtContractAddress = usdtContractAddress;
}
// 当前的钱包地址, 当 connected 为 true 时可用
get selectedAddress () {
return window.ethereum.selectedAddress;
}
/**
* 是否存在运行环境中
* @return {boolean}
*/
hasRuntime () {
return !!window.ethereum;
}
// 调用 rpc 方法
async #request (method, params) {
if (!this.hasRuntime()) {
throw new Error('No runtime');
}
return await window.ethereum.request({
method,
params,
});
}
/**
* switch to a new chain (by chain ID)
*/
async switchChain (chainId) {
return await this.#request('wallet_switchEthereumChain', [{ chainId }]);
}
/**
* add a new chain (by chain object)
*/
async addChain (chainParams) {
return await this.#request('wallet_addEthereumChain', [chainParams]);
}
/**
* 切换到BSC 测试网, 需先调用 web3x.addBscTestnet()
* @example
* web3x.connectViaInPage()
* .then(() => {
* web3x.addBscTestnet()
* .then(() => {
* web3x.switchToBscTestnet()
* })
* })
*/
async switchToBscTestnet () {
return await this.switchChain('0x61');
}
/**
* 添加 BSC 测试网
* @example
* web3x.connectViaInPage()
* .then(() => {
* web3x.addBscTestnet()
* .then(() => {
* web3x.switchToBscTestnet()
* })
* })
*/
async addBscTestnet () {
return await this.addChain({
chainId: '0x61',
chainName: 'Binance Smart Chain Testnet',
nativeCurrency: {
name: 'BNB',
symbol: 'bnb',
decimals: 18,
},
rpcUrls: ['https://data-seed-prebsc-1-s1.binance.org:8545/'],
blockExplorerUrls: ['https://testnet.bscscan.com'],
});
}
/**
* 链接钱包
* @return {Promise<string[]>} - 16 进制的钱包地址数组
*/
async connectViaInPage () {
return this.#request('eth_requestAccounts')
.then(res => {
this.#web3 = new Web3(window.ethereum);
this.ippt = new IPPT(this.ipptContractAddress, IPPTAbi, this.#web3);
this.usdt = new USDT(this.usdtContractAddress, USDTAbi, this.#web3);
this.connected = true;
return Promise.resolve(res)
});
}
/**
* 监听钱包地址改变
* @param callback{() => void} - 回调函数, 参数为新的钱包地址
*/
onAccountsChanged (callback) {
return window.ethereum.on('accountsChanged', callback)
}
}
export const web3x = new Web3X(
// BSC链连接是https://endpoints.omniatech.io/v1/bsc/mainnet/public
// BSC链Id是56
// ippt 合约
"0x622d7b79a904e00e5fcab06396ff009e441f0186",
// usdt 合约
"0x0a70dDf7cDBa3E8b6277C9DDcAf2185e8B6f539f"
);

21
uview-ui/LICENSE

@ -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
uview-ui/README.md

@ -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,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
- 众多的常用页面和布局,让您专注逻辑,事半功倍
- 详尽的文档支持,现代化的演示效果
- 按需引入,精简打包体积
## 安装
```bash
# npm方式安装
npm i uview-ui
```
## 快速上手
1. `main.js`引入uView库
```js
// main.js
import uView from 'uview-ui';
Vue.use(uView);
```
2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
```css
/* App.vue */
<style lang="scss">
@import "uview-ui/index.scss";
</style>
```
3. `uni.scss`引入全局scss变量文件
```css
/* uni.scss */
@import "uview-ui/theme.scss";
```
4. `pages.json`配置easycom规则(按需引入)
```js
// pages.json
{
"easycom": {
// 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应用到您的产品中。

190
uview-ui/components/u-action-sheet/u-action-sheet.vue

@ -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>

256
uview-ui/components/u-alert-tips/u-alert-tips.vue

@ -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>

290
uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue

@ -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>

1265
uview-ui/components/u-avatar-cropper/weCropper.js

File diff suppressed because it is too large

244
uview-ui/components/u-avatar/u-avatar.vue

@ -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>

153
uview-ui/components/u-back-top/u-back-top.vue

@ -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>

216
uview-ui/components/u-badge/u-badge.vue

@ -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>

596
uview-ui/components/u-button/u-button.vue

@ -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>

640
uview-ui/components/u-calendar/u-calendar.vue

@ -0,0 +1,640 @@
<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)">{{ i18n.confirm }}</u-button>
</view>
</view>
</view>
</u-popup>
</template>
<script>
/**
* calendar 日历
* @description 此组件用于单个选择日期范围选择日期等日历被包裹在底部弹起的容器中
* @tutorial http://uviewui.com/components/calendar.html
* @property {String} mode 选择日期的模式date-为单个日期range-为选择日期范围
* @property {Boolean} v-model 布尔值变量用于控制日历的弹出与收起
* @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
* @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
* @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
* @property {String Number} max-year 可切换的最大年份(默认2050)
* @property {String Number} min-year 最小可选日期(默认1950)
* @property {String Number} min-date 可切换的最小年份(默认1950-01-01)
* @property {String Number} max-date 最大可选日期(默认当前日期)
* @property {String Number} 弹窗顶部左右两边的圆角值单位rpx(默认20)
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
* @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
* @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
* @property {String} color 日期字体的默认颜色(默认#303133)
* @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
* @property {String Number} z-index 弹出时的z-index值(默认10075)
* @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
* @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
* @property {String} range-color 选择范围内字体颜色(默认#2979ff)
* @property {String} start-text 起始日期底部的提示文字(默认 '开始')
* @property {String} end-text 结束日期底部的提示文字(默认 '结束')
* @property {String} btn-type 底部确定按钮的主题(默认 'primary')
* @property {String} toolTip 顶部提示文字如设置名为tooltip的slot此参数将失效(默认 '选择日期')
* @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
* @example <u-calendar v-model="show" :mode="mode"></u-calendar>
*/
export default {
name: 'u-calendar',
props: {
safeAreaInsetBottom: {
type: Boolean,
default: false
},
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
//
value: {
type: Boolean,
default: false
},
// z-index
zIndex: {
type: [String, Number],
default: 0
},
//
changeYear: {
type: Boolean,
default: true
},
//
changeMonth: {
type: Boolean,
default: true
},
// date-range-+
mode: {
type: String,
default: 'date'
},
//
maxYear: {
type: [Number, String],
default: 2050
},
//
minYear: {
type: [Number, String],
default: 1950
},
// ()
minDate: {
type: [Number, String],
default: '1950-01-01'
},
/**
* 最大可选日期
* 默认最大值为今天之后的日期不可选
* 2030-12-31
* */
maxDate: {
type: [Number, String],
default: ''
},
//
borderRadius: {
type: [String, Number],
default: 20
},
//
monthArrowColor: {
type: String,
default: '#606266'
},
//
yearArrowColor: {
type: String,
default: '#909399'
},
//
color: {
type: String,
default: '#303133'
},
// |
activeBgColor: {
type: String,
default: '#2979ff'
},
// |
activeColor: {
type: String,
default: '#ffffff'
},
//
rangeBgColor: {
type: String,
default: 'rgba(41,121,255,0.13)'
},
//
rangeColor: {
type: String,
default: '#2979ff'
},
// mode=range
startText: {
type: String,
default: '开始'
},
// mode=range
endText: {
type: String,
default: '结束'
},
//
btnType: {
type: String,
default: 'primary'
},
//
isActiveCurrent: {
type: Boolean,
default: true
},
// mode=date
isChange: {
type: Boolean,
default: false
},
//
closeable: {
type: Boolean,
default: true
},
//
toolTip: {
type: String,
default: ''
}
},
data() {
return {
// ,1-7
weekday: 1,
weekdayArr:[],
//
days: 0,
daysArr:[],
showTitle: '',
year: 2020,
month: 0,
day: 0,
startYear: 0,
startMonth: 0,
startDay: 0,
endYear: 0,
endMonth: 0,
endDay: 0,
today: '',
activeDate: '',
startDate: '',
endDate: '',
isStart: true,
min: null,
max: null,
weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
};
},
computed: {
dataChange() {
return `${this.mode}-${this.minDate}-${this.maxDate}`;
},
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
},
},
watch: {
dataChange(val) {
this.init()
}
},
created() {
this.init()
},
methods: {
getColor(index, type) {
let color = type == 1 ? '' : this.color;
let day = index + 1
let date = `${this.year}-${this.month}-${day}`
let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
let start = this.startDate.replace(/\-/g, '/')
let end = this.endDate.replace(/\-/g, '/')
if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
color = type == 1 ? this.activeBgColor : this.activeColor;
} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
color = type == 1 ? this.rangeBgColor : this.rangeColor;
}
return color;
},
init() {
let now = new Date();
this.year = now.getFullYear();
this.month = now.getMonth() + 1;
this.day = now.getDate();
this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
this.activeDate = this.today;
this.min = this.initDate(this.minDate);
this.max = this.initDate(this.maxDate || this.today);
this.startDate = "";
this.startYear = 0;
this.startMonth = 0;
this.startDay = 0;
this.endYear = 0;
this.endMonth = 0;
this.endDay = 0;
this.endDate = "";
this.isStart = true;
this.changeData();
},
//
initDate(date) {
let fdate = date.split('-');
return {
year: Number(fdate[0] || 1920),
month: Number(fdate[1] || 1),
day: Number(fdate[2] || 1)
}
},
openDisAbled: function(year, month, day) {
let bool = true;
let date = `${year}/${month}/${day}`;
// let today = this.today.replace(/\-/g, '/');
let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
let timestamp = new Date(date).getTime();
if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
bool = false;
}
return bool;
},
generateArray: function(start, end) {
return Array.from(new Array(end + 1).keys()).slice(start);
},
formatNum: function(num) {
return num < 10 ? '0' + num : num + '';
},
//
getMonthDay(year, month) {
let days = new Date(year, month, 0).getDate();
return days;
},
getWeekday(year, month) {
let date = new Date(`${year}/${month}/01 00:00:00`);
return date.getDay();
},
checkRange(year) {
let overstep = false;
if (year < this.minYear || year > this.maxYear) {
uni.showToast({
title: "日期超出范围啦~",
icon: 'none'
})
overstep = true;
}
return overstep;
},
changeMonthHandler(isAdd) {
if (isAdd) {
let month = this.month + 1;
let year = month > 12 ? this.year + 1 : this.year;
if (!this.checkRange(year)) {
this.month = month > 12 ? 1 : month;
this.year = year;
this.changeData();
}
} else {
let month = this.month - 1;
let year = month < 1 ? this.year - 1 : this.year;
if (!this.checkRange(year)) {
this.month = month < 1 ? 12 : month;
this.year = year;
this.changeData();
}
}
},
changeYearHandler(isAdd) {
let year = isAdd ? this.year + 1 : this.year - 1;
if (!this.checkRange(year)) {
this.year = year;
this.changeData();
}
},
changeData() {
this.days = this.getMonthDay(this.year, this.month);
this.daysArr=this.generateArray(1,this.days)
this.weekday = this.getWeekday(this.year, this.month);
this.weekdayArr=this.generateArray(1,this.weekday)
this.showTitle = `${this.year}${this.month}`;
if (this.isChange && this.mode == 'date') {
this.btnFix(true);
}
},
dateClick: function(day) {
day += 1;
if (!this.openDisAbled(this.year, this.month, day)) {
this.day = day;
let date = `${this.year}-${this.month}-${day}`;
if (this.mode == 'date') {
this.activeDate = date;
} else {
let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
if (this.isStart || compare) {
this.startDate = date;
this.startYear = this.year;
this.startMonth = this.month;
this.startDay = this.day;
this.endYear = 0;
this.endMonth = 0;
this.endDay = 0;
this.endDate = "";
this.activeDate = "";
this.isStart = false;
} else {
this.endDate = date;
this.endYear = this.year;
this.endMonth = this.month;
this.endDay = this.day;
this.isStart = true;
}
}
}
},
close() {
// v-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>

257
uview-ui/components/u-car-keyboard/u-car-keyboard.vue

@ -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>

299
uview-ui/components/u-card/u-card.vue

@ -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>

70
uview-ui/components/u-cell-group/u-cell-group.vue

@ -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>

316
uview-ui/components/u-cell-item/u-cell-item.vue

@ -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>

123
uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@ -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>

284
uview-ui/components/u-checkbox/u-checkbox.vue

@ -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>

220
uview-ui/components/u-circle-progress/u-circle-progress.vue

@ -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>

156
uview-ui/components/u-col/u-col.vue

@ -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>

204
uview-ui/components/u-collapse-item/u-collapse-item.vue

@ -0,0 +1,204 @@
<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.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>

99
uview-ui/components/u-collapse/u-collapse.vue

@ -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>

237
uview-ui/components/u-column-notice/u-column-notice.vue

@ -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>

318
uview-ui/components/u-count-down/u-count-down.vue

@ -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>

241
uview-ui/components/u-count-to/u-count-to.vue

@ -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>

153
uview-ui/components/u-divider/u-divider.vue

@ -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>

132
uview-ui/components/u-dropdown-item/u-dropdown-item.vue

@ -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>

298
uview-ui/components/u-dropdown/u-dropdown.vue

@ -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>

193
uview-ui/components/u-empty/u-empty.vue

@ -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>

384
uview-ui/components/u-field/u-field.vue

@ -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>

431
uview-ui/components/u-form-item/u-form-item.vue

@ -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>

134
uview-ui/components/u-form/u-form.vue

@ -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>

74
uview-ui/components/u-full-screen/u-full-screen.vue

@ -0,0 +1,74 @@
<template>
<u-modal v-model="show" :title-style="titleStyle" :confirm-text="i18n.confirm" confirm-color="#7081FF"
:title="i18n.UpdateTips" @confirm="confirm">
<view class="u-update-content">
<rich-text :nodes="tipUpdateContent"></rich-text>
</view>
</u-modal>
</template>
<script>
export default {
data() {
return {
show: false,
// :title-style="contentStyle" :content-style="contentStyle" :cancel-style="contentStyle" :confirm-style="contentStyle"
// contentStyle:{
// backgroundColor: '#111747'
// },
titleStyle: {
color: '#7081FF'
},
appUrl: '',
tipUpdateContent: ''
}
},
onLoad(option) {
this.show = true;
this.appUrl = option.appUrl
this.tipUpdateContent = option.tipUpdateContent
},
onHide() {
//退app
// #ifdef APP-PLUS
if (plus.os.name.toLowerCase() === 'android') {
plus.runtime.quit();
} else {
const threadClass = plus.ios.importClass("NSThread");
const mainThread = plus.ios.invoke(threadClass, "mainThread");
plus.ios.invoke(mainThread, "exit");
}
// #endif
},
methods: {
// cancel() {
// this.closeModal();
// },
confirm() {
plus.runtime.openURL(this.appUrl)
// this.closeModal();
}
// ,
// closeModal() {
// uni.navigateBack();
// }
},
computed: {
},
}
</script>
<style scoped lang="scss">
@import "../../libs/css/style.components.scss";
// .u-full-content {
// background-color: $primaryPageBoxColor !important;
// }
.u-update-content {
font-size: 26rpx;
color: #7081FF;
line-height: 1.7;
padding: 30rpx;
}
</style>

54
uview-ui/components/u-gap/u-gap.vue

@ -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>

126
uview-ui/components/u-grid-item/u-grid-item.vue

@ -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>

108
uview-ui/components/u-grid/u-grid.vue

@ -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>

336
uview-ui/components/u-icon/u-icon.vue

@ -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>

266
uview-ui/components/u-image/u-image.vue

@ -0,0 +1,266 @@
<template>
<view class="u-image" @tap="onClick" :style="[wrapStyle, backgroundStyle]">
<image
v-if="!isError"
:src="src"
:mode="mode"
@error="onErrorHandler"
@load="onLoadHandler"
:lazy-load="lazyLoad"
class="u-image__image"
:style="{
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius)
}"
></image>
<view
v-if="showLoading && loading"
class="u-image__loading"
:style="{
borderRadius: shape == 'circle' ? '50%' : $u.addUnit(borderRadius),
backgroundColor: this.bgColor
}"
>
<slot v-if="$slots.loading" name="loading" />
<u-icon v-else :name="loadingIcon" :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;
}
}
}
},
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>

89
uview-ui/components/u-index-anchor/u-index-anchor.vue

@ -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>

315
uview-ui/components/u-index-list/u-index-list.vue

@ -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>

387
uview-ui/components/u-input/u-input.vue

@ -0,0 +1,387 @@
<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"
@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
}
},
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
setTimeout(() => {
this.focused = false;
}, 100)
// vue return
this.$emit('blur', event.detail.value);
setTimeout(() => {
// bug()@input
// #ifdef MP-TOUTIAO
if(this.$u.trim(value) == this.lastValue) return ;
this.lastValue = value;
// #endif
// u-form-item
this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
}, 40)
},
onFormItemError(status) {
this.validateState = status;
},
onFocus(event) {
this.focused = true;
this.$emit('focus');
},
onConfirm(e) {
this.$emit('confirm', e.detail.value);
},
onClear(event) {
this.$emit('input', '');
},
inputClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
@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>

217
uview-ui/components/u-keyboard/u-keyboard.vue

@ -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>

244
uview-ui/components/u-lazy-load/u-lazy-load.vue

@ -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>

147
uview-ui/components/u-line-progress/u-line-progress.vue

@ -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>

84
uview-ui/components/u-line/u-line.vue

@ -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>

89
uview-ui/components/u-link/u-link.vue

@ -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>

25
uview-ui/components/u-loading-page/u-loading-page.vue

@ -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>

103
uview-ui/components/u-loading/u-loading.vue

@ -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>

203
uview-ui/components/u-loadmore/u-loadmore.vue

@ -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>

123
uview-ui/components/u-mask/u-mask.vue

@ -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>

311
uview-ui/components/u-message-input/u-message-input.vue

@ -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>

283
uview-ui/components/u-modal/u-modal.vue

@ -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>

315
uview-ui/components/u-navbar/u-navbar.vue

@ -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>

233
uview-ui/components/u-no-network/u-no-network.vue

File diff suppressed because one or more lines are too long

272
uview-ui/components/u-notice-bar/u-notice-bar.vue

@ -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>

371
uview-ui/components/u-number-box/u-number-box.vue

@ -0,0 +1,371 @@
<template>
<view class="u-numberbox">
<view @click="iconminus" 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="number" :style="{
color: color,
fontSize: size + 'rpx',
background: bgColor,
height: inputHeight + 'rpx',
width: inputWidth + 'rpx'
}" />
<view @click="iconplus" 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: {
iconminus(){
// this.$emit("minus",'minus')
this.handleChange('value', 'minus');
},
iconplus(){
this.handleChange('value', 'plus');
},
// 退
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>

158
uview-ui/components/u-number-keyboard/u-number-keyboard.vue

@ -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>

100
uview-ui/components/u-parse/libs/CssHandler.js

@ -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;
}

580
uview-ui/components/u-parse/libs/MpHtmlParser.js

@ -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;

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

Loading…
Cancel
Save